Swift: Remove duplicates from array
Video: Swift: Remove duplicates from array by Taught by Celeste AI - AI Coding Coach
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):
insertedistrueif the value was new.insertedisfalseif 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.