Swift Interview Idioms: The Shortcuts That Actually Save Time

- Swift has no built-in heap — memorize a compact generic struct with
push,pop, and a comparator closure before your interview. - Swift strings can't be integer-indexed — convert to
Array(s)at the start of every string problem to avoid fighting the type system. freq[c, default: 0] += 1is the idiomatic frequency counter; the subscript with default writes back in one pass, no Optional unwrapping.ArraySliceindices are not zero-based —arr[2...5][0]crashes; useArray(slice)or iterate withslice.indicesto reset.stride(from:through:by:)replaces C-style reverse loops; use it whenever you need reverse iteration or a non-unit step.compactMapmaps and filters nils in one pass, cleaner thanmapfollowed byfilter { $0 != nil }.- Integer overflow panics in debug mode — guard large sums and BFS distances; use
&+for intentional wrapping.
Swift is a reasonable choice for a coding interview, and most Apple-focused engineers already think in it. The type system catches a whole class of bugs before you run a single line. The standard library is expressive. The compiler error messages are genuinely helpful, which is rare enough to be worth mentioning.
The problem? Swift's standard library is simultaneously very good and weirdly incomplete. The gaps show up at exactly the wrong moments, like when you are twelve minutes into a 45-minute interview and you just realized there is no heap.
Three Collections Cover 90% of What You Need
Array is the default. Appending is amortized O(1), random access is O(1), and almost everything in the standard library is designed around it. Initialize with a repeated value using Array(repeating: 0, count: n), which you will use constantly for DP tables, frequency counts, and visited arrays.
var dp = Array(repeating: Int.min, count: n + 1) var grid = Array(repeating: Array(repeating: false, count: cols), count: rows)
Dictionary is O(1) average for get, set, and delete. The idiom you want for frequency counting is the subscript with a default value:
var freq: [Character: Int] = [:] for c in s { freq[c, default: 0] += 1 }
Without the default:, you get an Optional back and the code gets ugly fast. The default: form returns a non-optional and writes back through the subscript in one shot. Use it every time.
Set gives O(1) membership testing and three algebraic operations that earn their keep in graph and interval problems. The algebraic ops are worth knowing because a.intersection(b) reads better than a manual loop, and in an interview you want to be explaining your algorithm, not narrating collection mechanics.
let a: Set = [1, 2, 3] let b: Set = [2, 3, 4] a.union(b) // [1, 2, 3, 4] a.intersection(b) // [2, 3] a.subtracting(b) // [1]
Sorting Without Surprises
Swift's sort is stable as of Swift 5, which means equal elements keep their original relative order. This matters for problems that sort by one key and then need to preserve secondary ordering. It also means you can stop mentally checking whether your comparator needs to handle equal elements carefully. One less thing to get wrong under pressure.
sort() mutates the array in place and requires var. sorted() returns a new array and works on let. Pick based on whether you need the original.
var nums = [3, 1, 4, 1, 5] nums.sort() // mutates: [1, 1, 3, 4, 5] let sorted = nums.sorted() // returns copy, nums unchanged let desc = nums.sorted(by: >) // descending
For custom comparators, the closure receives two elements and returns true if the first should come before the second:
pairs.sort { a, b in if a.1 != b.1 { return a.1 > b.1 } return a.0 < b.0 }
One trap: sort(by:) requires strict weak ordering. Returning true for equal elements causes undefined behavior. Use < and >, never <= or >=, as your primary comparators. This rule is not negotiable and the resulting bug is not reproducible on demand, which is the worst kind.
Why String Indexing Fights You
You cannot subscript a Swift String with an integer index. The reason is correct (Swift strings are Unicode-aware and characters can be multi-byte, so O(1) random access isn't possible), but it means you will fight the type system on every string problem at exactly the moment you want to be thinking about the algorithm.
The fastest fix: convert to an array of Character at the start and never look back.
let s = "hello" var chars = Array(s) // [Character] // Now you can do chars[i], chars[i] = "x", etc.
Do this reflexively. Don't try to use String.Index in an interview unless the problem specifically requires it. The cognitive overhead isn't worth it.
When you need the ASCII value of a character, asciiValue returns UInt8?, not Int. You need an explicit cast when doing arithmetic, which is another small tax the language collects from you:
let c: Character = "A" let val = Int(c.asciiValue!) // 65 let offset = Int(c.asciiValue!) - Int(Character("a").asciiValue!)
For checking character type, use the built-in properties instead of ASCII arithmetic when you can: c.isLetter, c.isNumber, c.isWhitespace, c.isUppercase, c.isLowercase.
Building a result string back from characters: String(chars) works. String concatenation in a loop is O(n²), same as every other language. Same lesson, different syntax.

ArraySlice[0] in production.
The Heap Is Missing. Here Is One.
Swift's standard library has no heap and no priority queue. This is the gap that bites Swift interviewees the hardest, because heap problems show up often and there is no workaround that doesn't require writing your own.
Apple's swift-collections package includes a Heap type, but most online judges running Swift will not have it available. Write this from memory:
struct Heap<T> { var data: [T] let isHigherPriority: (T, T) -> Bool init(_ compare: @escaping (T, T) -> Bool) { self.data = [] self.isHigherPriority = compare } 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 if isHigherPriority(data[i], data[parent]) { data.swapAt(i, parent) i = parent } else { break } } } private mutating func siftDown(_ i: Int) { var i = i while true { var best = i let l = 2 * i + 1, r = 2 * i + 2 if l < data.count && isHigherPriority(data[l], data[best]) { best = l } if r < data.count && isHigherPriority(data[r], data[best]) { best = r } if best == i { break } data.swapAt(i, best) i = best } } } // Min-heap var minH = Heap<Int>(<) // Max-heap var maxH = Heap<Int>(>)
Practice writing this until it takes three minutes. The interviewer will not give you partial credit for being surprised by its absence. For a deeper look at how the heap works under the hood, see this explainer.

Swift stdlib: no heap. Swift engineer mid-interview: builds one, gives self the medal.
One-Liners Worth Memorizing
compactMap is the function you reach for when mapping might produce nils. It maps and filters nil results in one pass, which is almost always what you want:
let strings = ["1", "two", "3", "four"] let nums = strings.compactMap { Int($0) } // [1, 3]
enumerated() gives you (offset, element) pairs. The offset is always zero-based, even on a slice. Don't assume. If you forget this and your interleaving logic mysteriously produces wrong results on slices, this is why.
zip is the clean way to walk two sequences in parallel without an index:
for (a, b) in zip(left, right) { }
stride replaces the C-style reverse loop that Swift deliberately does not allow:
for i in stride(from: n - 1, through: 0, by: -1) { } for i in stride(from: 0, to: n, by: 2) { } // even indices
reduce(into:) is more efficient than reduce for building collections because it mutates in place rather than copying at every step:
let freq = s.reduce(into: [:]) { acc, c in acc[c, default: 0] += 1 }
Five Gotchas That Compile Fine
These are the ones that pass the type checker, crash at runtime, and look completely correct on a first read. None of them produce a compiler warning. The compiler is fine with all of it. It is you and the judge who suffer.
1. Character.asciiValue is UInt8, not Int. Arithmetic with it silently truncates or overflows. Always cast to Int before doing math. Always.
2. ArraySlice indices are not zero-based. When you take array[2...5], the resulting slice has indices 2 through 5, not 0 through 3. Accessing slice[0] crashes. This comes up when passing slices to recursive functions, which comes up constantly.
let arr = [10, 20, 30, 40] let slice = arr[1...] // slice[0] crashes. Use slice[1], or Array(slice) to reset indices.
3. Integer overflow panics in debug builds. Swift traps on overflow by default, unlike C or Java. Int.max + 1 crashes immediately rather than silently wrapping. In problems where you might overflow (BFS distances, large sums, anything where you're multiplying coordinates), clamp explicitly or use the overflow operators &+, &-, &* for intentional wrapping. The overflow behavior is correct design. It is just surprising the first time it kills your solution in a contest judge.
4. sort() inside a closure that captures a struct requires var. This shows up when a nested helper or closure tries to sort a captured array. Pass the array as an inout parameter instead. You will hit this right when you think you're done.
5. Closures cannot mutate self inside a struct. Either switch to a class for the solution type, or restructure to avoid closures that need mutation. The compiler's error message here is clear, but the fix isn't always obvious in the moment.
Swift Interview Quick Reference
| Task | Swift idiom |
|---|---|
| Frequency map | freq[c, default: 0] += 1 |
| Repeat-fill array | Array(repeating: val, count: n) |
| Sort descending | arr.sorted(by: >) |
| Convert String to chars | Array(s) |
| ASCII value | Int(c.asciiValue!) |
| Filter nils after map | compactMap { } |
| Step iteration | stride(from:to:by:) |
| Parallel iteration | zip(a, b) |
| Set membership | set.contains(x) |
| Min/max with closure | arr.min(by: { }) |
| First match | arr.first(where: { }) |
Two Things to Drill Before the Interview
The string indexing restriction and the missing heap are where Swift candidates lose the most time. Both are solvable with thirty minutes of deliberate prep. Convert strings to character arrays at the start of every string problem until it is reflex. Write the heap struct from scratch twice. Time yourself on the second attempt.
After that, Swift's expressiveness genuinely works in your favor. The type system prevents entire categories of runtime bugs. The closures are clean. The standard library methods read like English. You can focus on the algorithm instead of fighting the language, which is the whole point.
The part you cannot practice alone is narrating all of it out loud while someone watches and takes notes. SpaceComplexity runs voice-based mock interviews with rubric-based feedback so you can find out whether your habits hold under pressure before the actual interview tells you.
Further Reading
- Swift Standard Library Documentation, canonical reference for every collection type and method
- Swift.org: Swift Collections Package, official announcement covering Deque, Heap, and OrderedDictionary
- Swift Language Reference: Strings and Characters, the full explanation of why String subscripting works the way it does
- swift-collections on GitHub, source and docs for the Heap and Deque types
- Swift.org: Generics, useful background for writing reusable heap and graph code in an interview