Swift for Coding Interviews: The Standard Library That Fights Back

[key, default: 0]subscript is the safe frequency map idiom in Swift; force-unwrapping a missing dictionary key crashes your interview.Array.removeFirst()is O(n): BFS written the obvious way becomes quadratic. Use an index pointer instead.String.countis O(n) because Swift counts extended grapheme clusters. Convert to[Character]once at the start of any indexed traversal.- There is no built-in heap in LeetCode's Swift environment: bring a 35-line
MinHeapstruct and know how to flip comparisons for a max-heap. inoutis required for struct mutation: forgetting it on a recursive DFS silently breaks your visited set with no compiler warning.- Sort comparators must be strict weak orderings: using
<=instead of<causes crashes or garbage output. - Swift arithmetic traps on overflow in debug builds: use
&+when you need wrapping behavior, and know thatIntis 64-bit so most overflow problems don't bite you.
Swift for coding interviews sounds like a plan. You know iOS. You know the language. You'll just apply those skills. Twenty minutes into your first interview, you're staring at the compiler because s[2] doesn't compile and your BFS just became O(n²) because you called removeFirst().
That's the Swift experience. Expressive, modern, and full of landmines that are completely invisible until they blow up at exactly the wrong moment.
When to Use Swift for Coding Interviews
If you write iOS or macOS code day-to-day, interview in Swift. Fluency beats features every time. You already know the standard library. You're not translating idioms from Java in your head. That cognitive budget goes toward the actual problem. The language fluency argument applies here just as much as it does for Python or Java.
Swift is accepted on LeetCode and most interview platforms. Apple expects it for platform-facing roles. Meta and other companies with iOS teams will take it without blinking. For general software engineering roles at backend-focused shops, check what the platform supports. Most will be fine. A few legacy interviewers will stare blankly at guard let. That's their problem, not yours. If you're an iOS engineer wondering which patterns matter most for your specific interviews, DSA for iOS Engineers covers the topic role-by-role.
Where Swift starts to hurt: you need a heap. There is no heapq. There is no PriorityQueue. The swift-collections package added Heap<T> but LeetCode doesn't import it. You are writing your own, every time.
The Core Collections
Swift's four workhorses cover most interview scenarios. Know their characteristics cold.
| Type | Lookup | Insert | Notes |
|---|---|---|---|
Array<T> | O(1) by index | O(1) amortized append | removeFirst() is O(n) |
Dictionary<K, V> | O(1) avg | O(1) avg | Returns Optional on lookup |
Set<T> | O(1) avg | O(1) avg | T must be Hashable |
String | O(n) | O(n) | count walks grapheme clusters |
All four are value types with copy-on-write semantics. Passing an array to a function doesn't copy it until the function mutates it. This is the good news. The bad news is in the next section.
The Missing Piece: No Built-In Heap
You need a heap for Dijkstra, K closest points, the sliding window median, and a dozen frequency problems. You need to bring one. If you want to understand the underlying mechanics before memorizing the implementation, the heap data structure guide covers the sift-up and sift-down proofs in full.
The interview-ready version below is 35 lines. Memorize it once and re-derive it from the invariants.
struct MinHeap<T: Comparable> { private var data: [T] = [] var isEmpty: Bool { data.isEmpty } var count: Int { data.count } var peek: T? { data.first } mutating func push(_ val: T) { data.append(val) siftUp(data.count - 1) } mutating func pop() -> T? { guard !data.isEmpty else { return nil } data.swapAt(0, data.count - 1) let val = data.removeLast() if !data.isEmpty { siftDown(0) } return val } private mutating func siftUp(_ i: Int) { var i = i while i > 0 { let parent = (i - 1) / 2 guard data[i] < data[parent] else { break } data.swapAt(i, parent) i = parent } } private mutating func siftDown(_ i: Int) { var i = i let n = data.count while true { let left = 2 * i + 1, right = 2 * i + 2 var smallest = i if left < n && data[left] < data[smallest] { smallest = left } if right < n && data[right] < data[smallest] { smallest = right } if smallest == i { break } data.swapAt(i, smallest) i = smallest } } }
For a max-heap, flip the comparisons to >. For a heap on a custom key, make T a tuple or struct that implements Comparable.
There is no sorted map (no TreeMap, no SortedDictionary in stdlib). When you need sorted keys, store them in a separate array and binary search it. Expensive, but it works.

Swift interviewers at both ends of the bell curve have made peace with implementing their own data structures.
String Handling Is a Trap
String.count is O(n). Swift strings are sequences of extended grapheme clusters, not bytes. Counting them means walking the entire string. Call it once and cache the result, or better, convert to [Character] at the start of any function that indexes into the string repeatedly.
// Dangerous in a loop for i in 0..<s.count { ... } // O(n) per iteration = O(n^2) total // Safe approach let chars = Array(s) // O(n) once for i in 0..<chars.count { ... } // O(n) total
You also can't subscript a String with an integer. This will not compile:
let c = s[2] // Error: 'subscript(_:)' is unavailable
Convert to [Character] (almost always the right call in an interview). Or use String.Index, which is expressive but verbose:
let idx = s.index(s.startIndex, offsetBy: 2) let c = s[idx]
Building a string character by character: use [Character] throughout, then convert at the end with String(chars). That avoids repeated string concatenation, which is O(n) per append.
Five Gotchas That Will Crash Your Interview
1. Dictionary lookup returns Optional.
This one gets everyone. Not once. Repeatedly, until freq["a"]! += 1 makes you wince on sight.
var freq: [Character: Int] = [:] // WRONG - crashes if "a" isn't in the dict yet freq["a"]! += 1 // RIGHT - subscript with default freq["a", default: 0] += 1
The [key, default: value] subscript is the idiomatic Swift frequency map. One line, no unwrapping, no crash. Use it everywhere. The underlying hash table mechanics are covered in the hash map time complexity guide.
2. Array.removeFirst() is O(n).
BFS written the obvious way has a hidden quadratic. You won't notice until your solution times out on a 10,000-node graph with two minutes left on the clock.
// WRONG var queue: [Int] = [start] while !queue.isEmpty { let node = queue.removeFirst() // O(n) shift every iteration ... } // RIGHT - use a head pointer var queue: [Int] = [start] var head = 0 while head < queue.count { let node = queue[head] head += 1 ... }
The head-pointer pattern trades a bit of memory for O(1) dequeue. Use it for every BFS in Swift.
3. Integer overflow traps (crashes in debug, wraps in release).
Swift arithmetic operators trap on overflow in debug builds. Int.max + 1 is a fatal error. On LeetCode, problems that test overflow behavior need explicit overflow operators:
let x: Int = Int.max // let y = x + 1 // Fatal error: arithmetic overflow let y = x &+ 1 // Wraps: -9223372036854775808
Swift's Int is 64-bit, so the Java-style "multiply two Int32s and overflow" problem mostly doesn't bite you. But know the trap behavior when it does.
4. Struct mutation needs inout.
Arrays and dictionaries are structs. If you pass one to a helper function and mutate it inside, the caller doesn't see the change:
func dfs(_ graph: [[Int]], _ visited: inout Set<Int>, _ node: Int) { visited.insert(node) ... }
Forgetting inout on a recursive DFS produces a silent bug where your visited set never updates. The compiler won't warn you. You'll sit there wondering why every node gets revisited.
5. Sort comparators must be strict weak orderings.
If your comparator returns true for equal elements, Swift will crash or produce garbage output. Always use < not <=:
// WRONG - violates strict weak ordering pairs.sort { $0.1 <= $1.1 } // RIGHT pairs.sort { $0.1 < $1.1 }

Swift interviews. There is no heap. The door is unlocked. You still can't leave.
Idiomatic Patterns That Save Time
Get these to muscle memory so you're not typing around them mid-interview.
Frequency map:
var freq: [Character: Int] = [:] for c in s { freq[c, default: 0] += 1 }
Enumerate (index + value):
for (i, val) in nums.enumerated() { ... }
Parallel iteration:
for (a, b) in zip(arrayA, arrayB) { ... }
Custom step:
for i in stride(from: 0, to: n, by: 2) { ... }
Sorting with custom key:
intervals.sort { $0[0] < $1[0] } // sort by first element words.sort { $0.count < $1.count } // sort by length
Safe optional unwrap with guard:
func process(_ node: TreeNode?) { guard let node = node else { return } // node is non-optional here }
Filter and transform:
let evens = nums.filter { $0 % 2 == 0 } let doubled = nums.map { $0 * 2 } let nonNil = optionals.compactMap { $0 }
Quick-Reference Cheat Sheet
| Task | Swift idiom |
|---|---|
| Frequency map | freq[c, default: 0] += 1 |
| String to char array | let chars = Array(s) |
| Char array to string | String(chars) |
| Sort ascending | nums.sort() |
| Sort descending | nums.sort(by: >) |
| Min of array | nums.min()! |
| Max of array | nums.max()! |
| Prefix sum | nums.reduce(into: [0]) { $0.append($0.last! + $1) } |
| BFS queue (O(1) dequeue) | index pointer on array |
| Dictionary default | dict[key, default: 0] |
| Set membership | set.contains(x) |
| Overflow-safe add | a &+ b |
| Int max/min | Int.max / Int.min |
| Stride | stride(from: 0, to: n, by: 2) |
| Tuple return | return (left, right) |
What About the Missing TreeMap?
For most LeetCode mediums, you don't need ordered keys. Graphs, trees, hashmaps, two pointers, sliding window, BFS, DFS, and dynamic programming all work cleanly in Swift without one.
When a problem genuinely needs sorted keys with O(log n) insert and lookup, your options are limited. A balanced BST isn't in stdlib. A sorted array with binary search works for read-heavy scenarios. For fully dynamic ordered operations you'd need to implement an AVL or red-black tree yourself. In a 45-minute interview, if a problem fundamentally requires a sorted map, say so and show you know what the structure would cost. That transparency earns you more points than fumbling through an implementation you haven't practiced.
The Swift interview experience is genuinely different from Python or Java. The language rewards people who know it cold, and punishes everyone else with crashes that look like logic errors. Practicing under timed conditions in Swift is how you find these gaps before they show up live. SpaceComplexity runs voice-based mock interviews in your language of choice, scores your solutions on the same rubric real interviewers use, and catches the O(n) removeFirst problem before the actual interview does.