Dictionaries
Swift dictionaries are typed key/value maps. Hash-based; lookup is O(1) average. Use brackets to access, keys / values to iterate, updateValue to swap.
Create, access, iterate, transform
EXAMPLE
// 1) Create
var scores: [String: Int] = ["Ada": 95, "Bo": 88, "Cy": 92]
var empty: [String: Int] = [:]
var inferred = ["name": "Ada", "role": "admin"] // [String: String]
let user: [String: Any] = [
"name": "Ada",
"age": 32,
"admin": true,
]
// 2) Read — subscript returns Optional
let bo: Int? = scores["Bo"] // Optional(88)
let di: Int? = scores["Di"] // nil
// Default value
let score = scores["Di"] ?? 0 // 0
let score2 = scores["Di", default: 0] // 0 (Swift 4+)
// 3) Modify
scores["Di"] = 70 // insert
scores["Ada"] = 96 // update
scores["Bo"] = nil // remove
scores.removeValue(forKey: "Cy") // remove + return old value
// updateValue returns the OLD value (or nil if new)
let old = scores.updateValue(75, forKey: "Di")
// 4) Iterate
for (name, score) in scores {
print("\\(name): \\(score)")
}
for name in scores.keys {
print(name)
}
for score in scores.values {
print(score)
}
// Sorted iteration (dictionaries are unordered)
for (name, score) in scores.sorted(by: { $0.value > $1.value }) {
print("\\(name): \\(score)")
}
// 5) Functional — map / filter / reduce
let doubled = scores.mapValues { $0 * 2 } // [String: Int]
let passing = scores.filter { $0.value >= 90 } // dictionary
let total = scores.values.reduce(0, +) // sum
// Transform keys + values
let uppercaseNames = Dictionary(
uniqueKeysWithValues: scores.map { ($0.key.uppercased(), $0.value) }
)
// Or use reduce(into:) for accumulation
let bins = scores.reduce(into: [String: [String]]()) { result, entry in
let bin = entry.value >= 90 ? "A" : "B"
result[bin, default: []].append(entry.key)
}
// 6) Sequence → Dictionary
let users = [User(id: 1, name: "Ada"), User(id: 2, name: "Bo")]
let byId = Dictionary(uniqueKeysWithValues: users.map { ($0.id, $0) })
// [1: User(...), 2: User(...)]
// Group by key (multiple values per key)
let byCity = Dictionary(grouping: users, by: { $0.city })
// ["Sydney": [User, User], "Brisbane": [User]]
// Merge — handle key conflicts
let a = ["a": 1, "b": 2]
let b = ["b": 20, "c": 3]
let merged = a.merging(b, uniquingKeysWith: { current, new in current + new })
// ["a": 1, "b": 22, "c": 3]
// 7) Count
scores.count // 4
scores.isEmpty // false
// 8) Contains?
scores.keys.contains("Ada") // true
scores.values.contains(95) // true
scores["Ada"] != nil // true
// 9) Multi-level nested update
struct Person {
var name: String
var address: [String: String]
}
var people: [Int: Person] = [
1: Person(name: "Ada", address: ["city": "Sydney"]),
]
people[1]?.address["city"] = "Melbourne" // works because dict values aren't COW-shared
// 10) Pattern: counting occurrences
let words = ["hi", "hello", "hi", "go", "hello", "hi"]
var counts: [String: Int] = [:]
for w in words {
counts[w, default: 0] += 1
}
// ["hi": 3, "hello": 2, "go": 1]
// 11) Pattern: index by
let productById = Dictionary(uniqueKeysWithValues: products.map { ($0.id, $0) })
// 12) Pattern: cache
struct Cache<K: Hashable, V> {
private var store: [K: V] = [:]
mutating func get(_ key: K, _ compute: () -> V) -> V {
if let v = store[key] { return v }
let v = compute()
store[key] = v
return v
}
}
var cache = Cache<Int, String>()
let name = cache.get(42) { fetchNameFromDb(id: 42) }
// 13) Codable — dictionaries serialise to / from JSON
let json = try JSONEncoder().encode(scores)
let back = try JSONDecoder().decode([String: Int].self, from: json)
// 14) Convert to / from arrays
let keys = Array(scores.keys)
let values = Array(scores.values)
let pairs = scores.map { ($0.key, $0.value) } // [(String, Int)]
// 15) Performance
// - Lookups, insertions, deletions: average O(1)
// - Iteration: ordered NOT GUARANTEED — sort if order matters
// - Hash collisions degrade to O(N) worst case — rare
// - Key type MUST conform to Hashable
// 16) Custom Hashable key types
struct UserId: Hashable {
let id: Int
let realm: String
}
var users: [UserId: User] = [:]
users[UserId(id: 42, realm: "prod")] = User(...)
// Auto-synthesised Hashable works when all fields are Hashable.
// 17) When dictionaries aren't enough
// - Need ordered insertion → OrderedDictionary (swift-collections)
// - Need many keys for one value → reach for [K: [V]]
// - Need value comparable + sorted → [K: V] + sort, or SortedDictionary
// - Need concurrent access → DispatchQueue serial + plain dict, or actor + dict
// 18) Common bugs
// • Subscript returns Optional — easy to forget the `?`
// • Forgetting Hashable on key type → compile error
// • Mutating a dictionary while iterating → undefined
// • Dictionary iteration order changes across runs — don't assume order
// • Comparing `nil` to absence vs `nil` to stored nil value (for [K: V?])
Why it matters
Swift dictionaries are value types with O(1) lookup. updateValue returns the old; removeValue returns it too. Dictionary(grouping:by:) is the cleanest way to bucket items by key.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
var ages = ["Ada": 36, "Bo": 28]
ages["Cy"] = 22
for (name, age) in ages { print("\(name) = \(age)") }
Try it Yourself »
Discussion
Loading…