Part of Swift with Copilot

Swift: Remove duplicates from array

Sandy LaneSandy Lane

Video: Swift: Remove duplicates from array by Taught by Celeste AI - AI Coding Coach

Take the quiz on the full lesson page
Test what you've read · interactive walkthrough

Swift with Copilot: Remove Duplicates from Array

Array(Set(arr)) — unordered. For ordered: var seen = Set<T>(); arr.filter { seen.insert($0).inserted }. For complex objects, dedupe by a key.

A staple. Three common patterns; pick by whether order and Hashability matter.

The Copilot prompt

// Remove duplicates from an array
let numbers = [1, 2, 2, 3, 4, 4, 5]

Copilot completes:

let numbers = [1, 2, 2, 3, 4, 4, 5]
let unique = Array(Set(numbers))
print(unique)
// [1, 2, 3, 4, 5] — order NOT guaranteed

Quick: Array(Set(arr))

Array(Set([1, 2, 2, 3, 4, 4, 5]))
// some permutation: [3, 1, 4, 5, 2] etc.

Fast — O(n) — and one line. But order is not preserved. Sets are hash-based; iteration order varies.

If you don't care about order: this is the answer.

Order-preserving: insert-and-filter

var seen = Set<Int>()
let unique = numbers.filter { seen.insert($0).inserted }
print(unique)
// [1, 2, 3, 4, 5]

Set.insert(_:) returns (inserted: Bool, memberAfterInsert: Element):

  • inserted is true if the value was new.
  • inserted is false if it was already there.

.filter keeps elements where the closure returns true — first occurrence only.

Make it an extension for reuse:

extension Sequence where Element: Hashable {
  func uniqued() -> [Element] {
    var seen = Set<Element>()
    return filter { seen.insert($0).inserted }
  }
}

[1, 2, 2, 3].uniqued()         // [1, 2, 3]
"hello".uniqued()              // ["h", "e", "l", "o"]

Now any Hashable sequence has .uniqued().

Dedupe by key (for non-Hashable or by-property)

For objects where you want to dedupe by one field:

struct User {
  let id: Int
  let name: String
}

let users = [
  User(id: 1, name: "Alice"),
  User(id: 2, name: "Bob"),
  User(id: 1, name: "Alice2"),    // same id, different name
]

extension Sequence {
  func uniqued<T: Hashable>(by key: (Element) -> T) -> [Element] {
    var seen = Set<T>()
    return filter { seen.insert(key($0)).inserted }
  }
}

let unique = users.uniqued(by: \.id)
// [User(id: 1, name: "Alice"), User(id: 2, name: "Bob")]

\.id is a KeyPath. The first occurrence wins; the second id: 1 is dropped.

Dictionary trick (last-wins)

If you want the last occurrence to win:

let users = [...]
let unique = Array(Dictionary(grouping: users, by: \.id).values.compactMap { $0.last })

Hacky. Cleaner: reverse, dedupe, reverse:

let unique = Array(users.reversed().uniqued(by: \.id).reversed())

Or write a lastUniqued:

extension Sequence {
  func lastUniqued<T: Hashable>(by key: (Element) -> T) -> [Element] {
    var dict = [T: Element]()
    for item in self {
      dict[key(item)] = item
    }
    return Array(dict.values)
  }
}

Sorted + adjacent dedup

If your input is already sorted:

let sorted = [1, 1, 2, 3, 3, 3, 4, 5, 5]
var unique = [Int]()
for x in sorted {
  if unique.last != x {
    unique.append(x)
  }
}
// [1, 2, 3, 4, 5]

O(n), no Set. Useful for huge sorted streams.

Time complexity

Method Time Order?
Array(Set(arr)) O(n)
Filter + seen.insert O(n) ✓ first occurrence
lastUniqued (dict) O(n) ❌ (or ✓ via re-sort)
Adjacent dedup (sorted) O(n)
Naïve arr.contains loop O(n²)

Avoid the arr.contains approach for any non-trivial array.

In-place dedup

For a var array, modify in place using Set:

var arr = [1, 2, 2, 3, 4, 4, 5]
var seen = Set<Int>()
arr = arr.filter { seen.insert($0).inserted }

Or use the swap-based approach for huge arrays where you want to avoid allocating a Set:

extension Array where Element: Hashable {
  mutating func dedupe() {
    var seen = Set<Element>()
    var i = 0
    while i < count {
      if !seen.insert(self[i]).inserted {
        remove(at: i)
      } else {
        i += 1
      }
    }
  }
}

remove(at:) shifts elements — O(n) per removal. For arrays with many dupes, build a new array instead.

Counting duplicates

While we're here:

let arr = [1, 2, 2, 3, 4, 4, 4, 5]
let counts = arr.reduce(into: [:]) { dict, x in
  dict[x, default: 0] += 1
}
// [1: 1, 2: 2, 3: 1, 4: 3, 5: 1]

let dupes = counts.filter { $0.value > 1 }.keys
// [2, 4]

reduce(into:) for fast histogram building.

Common stumbles

Set requires Hashable. Custom types need Hashable conformance:

struct Item: Hashable {
  let id: Int
}

Auto-synthesized for structs whose stored properties are all Hashable.

Array(Set(arr)) order surprise. Order changes between runs (and Swift versions). Don't rely on it.

Equality vs identity. For class instances, Hashable defaults to identity (=== same instance). Override for value equality.

Floats and Hashable. Floats are Hashable but 0.1 + 0.2 != 0.3. Beware near-equal floats deduping incorrectly.

.uniqued() not a builtin. Swift doesn't ship one. Write the extension or use a third-party (Swift Algorithms package has uniqued()).

Swift Algorithms package

Apple's swift-algorithms provides uniqued() and many others:

import Algorithms

[1, 2, 2, 3].uniqued()            // [1, 2, 3]
[1, 2, 2, 3].uniqued(on: \.self)  // by KeyPath

For new projects, worth adding.

What's next

Episode 18: UUID to odd or even number. Hashing for parity.

Recap

Array(Set(arr)) — fast, unordered. Order-preserving: seen.insert(_:).inserted filter pattern. By key: extension uniqued(by: \.id). For sorted input, adjacent comparison is O(n). Hashable conformance needed; auto-synthesized for structs of Hashable properties. Consider swift-algorithms package's uniqued().

Next episode: UUID to odd/even.

Ready? Take the quiz on the full lesson page →
Test what you've learned. Watch the lesson and try the interactive quiz on the same page.