How to Iterate Through a Dictionary in Swift

How to Iterate Through a Dictionary in Swift

Written by

in

Quick answer: Use a for-in loop with tuple decomposition, such as for (key, value) in dictionary, to traverse key-value pairs. Because Swift dictionaries are unordered hash maps, sort keys or elements first via dictionary.sorted(by: { $0.key < $1.key }) if you require deterministic execution order.

Why this happens

When developers iterate over a Swift dictionary, they often expect elements to appear in the order they were inserted or declared in code. For example, processing a collection of grouped numbers in sequence might evaluate conditional statements five times in one run and three times in another. This discrepancy happens because Swift’s standard Dictionary type is an unordered collection.

Under the hood, a Swift dictionary is implemented as an open-addressing hash table. Key-value pairs are stored in storage buckets based on the hashValue of the key. In Swift, hash values include per-execution randomization (hash seed randomized at every app launch to defend against hash-collision denial-of-service attacks). Consequently, the internal bucket arrangement changes across program executions and mutations.

Because traversal iterates through internal memory storage bucket by bucket, the iteration sequence is non-deterministic. If your logic depends on evaluating elements in a specific sequence—such as tracking a running maximum or calculating cumulative deltas—traversing an unsorted dictionary directly can produce unexpected side effects.

How to fix it

Depending on whether you need simple enumeration, isolated access to keys or values, or deterministic ordering, Swift provides several approaches.

1. Traverse key-value pairs with a for-in loop

The standard approach to iterate through a dictionary in Swift is tuple decomposition inside a for-in loop. Each element in the dictionary conforms to a (key: Key, value: Value) tuple.

let studentScores: [String: Int] = [
    "Alex": 88,
    "Taylor": 95,
    "Jordan": 72
]

for (name, score) in studentScores {
    print("\(name) scored \(score)")
}

If you do not need one of the elements, replace it with an underscore (_) to avoid compiler warnings about unused variables:

for (_, score) in studentScores {
    print("Recorded score: \(score)")
}

2. Iterate using the forEach closure

If you prefer functional programming patterns or want to chain collection operations, use the forEach method. The closure passes each key-value pair as a named tuple or an indexed argument ($0.key, $0.value).

let configurations: [String: String] = [
    "environment": "production",
    "logLevel": "verbose",
    "region": "us-east-1"
]

configurations.forEach { key, value in
    print("Config parameter: \(key) = \(value)")
}

Keep in mind that forEach cannot use break or continue control statements. Calling return inside a forEach closure only exits that specific closure invocation, equivalent to a continue in a traditional loop.

3. Loop over keys or values individually

When your logic only requires the keys or the values, access the .keys or .values collection views directly. This avoids destructuring unused tuple components and communicates intent clearly.

let userRoles: [String: String] = [
    "usr_101": "Admin",
    "usr_102": "Editor",
    "usr_103": "Viewer"
]

// Iterate only over dictionary keys
for userId in userRoles.keys {
    print("Validating permissions for ID: \(userId)")
}

// Iterate only over dictionary values
for role in userRoles.values {
    print("Active assigned role: \(role)")
}

Both keys and values are lightweight lazy views conforming to Collection, so accessing them does not allocate new storage arrays.

4. Sort pairs for deterministic iteration order

If your code relies on consistent sequence execution (such as tracking maximum values, processing steps alphabetically, or presenting UI lists), sort the dictionary before iterating. Calling .sorted(by:) returns an array of (key: Key, value: Value) tuples.

let interestingNumbers: [String: [Int]] = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25]
]

var largest = 0
var updatesCount = 0

// Guarantee alphabetical order by key: "Fibonacci", "Prime", "Square"
for (kind, numbers) in interestingNumbers.sorted(by: { $0.key < $1.key }) {
    print("Processing group: \(kind)")
    for number in numbers {
        if number > largest {
            largest = number
            updatesCount += 1
        }
    }
}

print("Largest number: \(largest), updated \(updatesCount) times")

Sorting transforms the unordered dictionary into a stable [(key: String, value: [Int])] array, ensuring identical iteration order every time your code executes.

5. Maintain insertion order with OrderedDictionary

If you need the performance characteristics of a dictionary combined with deterministic insertion ordering, import the Apple Collections package (swift-collections) and use OrderedDictionary.

import OrderedCollections

var tasks = OrderedDictionary<String, Bool>()
tasks["Download assets"] = true
tasks["Parse payloads"] = true
tasks["Render interface"] = false

for (step, isComplete) in tasks {
    print("\(step): \(isComplete ? "Done" : "Pending")")
}

OrderedDictionary preserves insertion sequence during iteration while retaining fast $O(1)$ key lookups.

How to prevent it

  • Never write algorithms that assume dictionary keys will be evaluated in the order they were defined in source code.
  • Use .sorted(by:) whenever business logic, algorithms, or snapshot unit tests depend on reproducible iteration sequences.
  • Choose .keys or .values views instead of ignoring elements with _ when only one component of the key-value pair is needed.
  • Opt for for-in over forEach if the loop body contains asynchronous operations, error throws, or early termination with break.
  • Adopt OrderedDictionary from swift-collections if preserving input order without sorting overhead is a core requirement.

FAQ

Can I mutate a Swift dictionary while iterating over it?

No. Attempting to add or remove keys inside a standard for-in loop over that same dictionary produces a compiler error or runtime mutation trap. Instead, iterate over a static array copy of keys using for key in Array(dictionary.keys) or collect changes in a temporary dictionary to merge afterward.

Does dict.keys.enumerated() give the index of elements in insertion order?

No. The .enumerated() method simply provides an integer counter starting from zero paired with elements in their current hash table order. It reflects the order of iteration in that particular run, not the sequence in which keys were added.

Is forEach faster than a for-in loop when iterating over a dictionary?

No, performance is effectively identical. Both approaches walk through the underlying storage buffer using the same collection iterators. Choose for-in when you need flow control like break, continue, or throw, and forEach when using functional chaining.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.