Coding interviews
In the coding round you solve one or two algorithm problems in Swift. Often there is no Xcode and no autocomplete. The interviewer cares about how you solve it, not only the final code.
The 5-step method
Use the same steps every time. They keep you calm and make your thinking visible.
- Clarify the problem.
- Examples: walk through 2–3 small inputs.
- Brute force: say the simple solution and its cost.
- Optimise: find a better solution, then code it.
- Test: run your code by hand on the examples and edge cases.
A good time split for a 45-minute round:
| Step | Minutes |
|---|---|
| Clarify + examples | 5 |
| Brute force + optimise (talking) | 5–8 |
| Code | 15–20 |
| Test + fix | 5–10 |
| Your questions | 5 |
A full example
Problem: "Return the index of the first character in a string that appears only once. Return -1 if there is none."
1. Clarify
Ask questions before you write code. Each answer can change the solution.
- "Is the input only lowercase English letters, or any Unicode?"
- "Is uppercase different from lowercase?"
- "What should I return for an empty string?" (-1)
- "How long can the string be?"
Say the answers back: "OK, any characters, case-sensitive, return -1 if none."
2. Examples
Write them as comments. This also gives you test cases for later.
// "swift" -> 0 ("s" appears once)
// "aabbc" -> 4 ("c")
// "aabb" -> -1
// "" -> -13. Brute force
Say it out loud first. You do not always need to code it.
"For each character, I check all the other characters. If none match, I return its index. This is O(n²) time and O(1) extra space."
func firstUniqueBrute(_ s: String) -> Int {
let chars = Array(s)
for i in 0..<chars.count {
var isUnique = true
for j in 0..<chars.count where i != j && chars[i] == chars[j] {
isUnique = false
break
}
if isUnique { return i }
}
return -1
}4. Optimise
Ask yourself: "What work am I repeating?" Here, we count the same characters again and again. A dictionary can count them once.
"I can count every character in one pass with a dictionary. Then a second pass finds the first character with count 1. That is O(n) time and O(k) space, where k is the number of different characters."
func firstUnique(_ s: String) -> Int {
let chars = Array(s)
var counts: [Character: Int] = [:]
for c in chars {
counts[c, default: 0] += 1
}
for (i, c) in chars.enumerated() where counts[c] == 1 {
return i
}
return -1
}5. Test
Do not say "I think it works". Walk through one example line by line.
"For
aabbc: counts are a:2, b:2, c:1. Second pass: index 0 is a, count 2, skip. ... Index 4 is c, count 1, return 4. Correct."
Then check edge cases:
- Empty string: the loops do not run, return -1. Good.
- One character: count is 1, return 0. Good.
- All repeated: return -1. Good.
If the editor can run code, add quick prints:
print(firstUnique("swift")) // 0
print(firstUnique("aabbc")) // 4
print(firstUnique("aabb")) // -1
print(firstUnique("")) // -1What to say while coding
Silence is the most common problem. Use short phrases to share your thinking.
- "I will use a dictionary here because lookups are O(1)."
- "Let me handle the empty case first."
- "I am not sure about this edge case. Let me check it after I finish."
- "This loop is O(n), so the total is O(n log n) because of the sort."
If you get stuck, say what you are thinking. The interviewer may give a hint. Taking a hint well is a good signal, not a bad one.
Common patterns
Most problems use one of a few patterns. Learn to spot them.
| If the problem says… | Try… |
|---|---|
| "find a pair", "seen before", "count" | Hash map or set |
| "sorted array", "find in O(log n)" | Binary search |
| "subarray", "substring", "window" | Sliding window |
| "pair in sorted array", "reverse", "palindrome" | Two pointers |
| "matching brackets", "next greater" | Stack |
| "shortest path", "level by level" | BFS with a queue |
| "all combinations", "all paths" | Backtracking (DFS) |
| "top k", "k smallest" | Heap, or sort if n is small |
Swift gotchas
Swift is safe and clear, but some things are slower to write than in other languages. Know these before the interview.
1. You cannot index a String with an Int
let s = "hello"
// let c = s[0] // Compile error
let chars = Array(s) // [Character]
let first = chars[0] // "h"Swift strings use String.Index because characters can have different sizes in memory. In interviews, convert to Array(s) first. Say why: "I convert to an array so I get O(1) access by index."
To build a string back:
let reversed = String(chars.reversed()) // "olleh"
let sortedWord = String("listen".sorted()) // "eilnst"2. Character is not String
"a" can be a Character or a String. Swift decides from the type.
let c: Character = "a"
let isLetter = c.isLetter // true
let isDigit = c.isNumber // false
let digit = Character("7").wholeNumberValue // Optional(7)
let code = c.asciiValue // Optional(97), type UInt8?
// Gotcha: lowercased() on a Character returns a String
let lower: String = Character("A").lowercased() // "a"For "only lowercase letters" problems, an array of 26 counts is often simpler than a dictionary:
var counts = Array(repeating: 0, count: 26)
for c in "apple" {
if let ascii = c.asciiValue {
counts[Int(ascii) - 97] += 1 // 97 is "a"
}
}3. split returns Substring
let words = "hi there swift".split(separator: " ") // [Substring]
let strings = words.map(String.init) // [String]
// Note: split skips empty pieces by default.Convert to String before you store it or pass it to a function that wants String.
4. No built-in queue (deque)
Array.removeFirst() is O(n), because it shifts every element. In BFS this can make your solution O(n²). Use an index instead:
func bfsOrder(_ graph: [Int: [Int]], start: Int) -> [Int] {
var queue = [start]
var head = 0
var visited: Set<Int> = [start]
while head < queue.count {
let node = queue[head]
head += 1
for next in graph[node, default: []] where !visited.contains(next) {
visited.insert(next)
queue.append(next)
}
}
return queue // also the visit order
}The Swift Collections package has a Deque type. But many interview editors do not include packages. Ask the interviewer, or use the index trick.
5. No built-in heap (priority queue)
For "top k" problems, first say: "Sorting is O(n log n). A heap gives O(n log k)." Then ask if you should write a heap. Many interviewers accept sorting first. If they want a heap, here is a short one:
struct Heap<Element> {
private var items: [Element] = []
private let areInOrder: (Element, Element) -> Bool
init(_ areInOrder: @escaping (Element, Element) -> Bool) {
self.areInOrder = areInOrder
}
var count: Int { items.count }
var peek: Element? { items.first }
mutating func push(_ value: Element) {
items.append(value)
var child = items.count - 1
while child > 0 {
let parent = (child - 1) / 2
guard areInOrder(items[child], items[parent]) else { break }
items.swapAt(child, parent)
child = parent
}
}
mutating func pop() -> Element? {
guard !items.isEmpty else { return nil }
items.swapAt(0, items.count - 1)
let top = items.removeLast()
var parent = 0
while true {
let left = 2 * parent + 1
let right = left + 1
var best = parent
if left < items.count && areInOrder(items[left], items[best]) { best = left }
if right < items.count && areInOrder(items[right], items[best]) { best = right }
if best == parent { break }
items.swapAt(parent, best)
parent = best
}
return top
}
}
var minHeap = Heap<Int>(<) // smallest on top
var maxHeap = Heap<Int>(>) // largest on topPractise writing it once or twice. It takes about 5 minutes when you know it.
6. Ranges can crash
let n = 0
// for i in 1...n { } // Crash: lower bound is bigger than upper bound
for i in 0..<n { print(i) } // Fine: runs zero times
// Loop backwards safely
for i in stride(from: n - 1, through: 0, by: -1) { print(i) }1...n crashes when n is 0. Prefer 0..<n, or check n first.
7. Tuples are not Hashable
You cannot put (row, col) in a Set. Use a small struct:
struct Point: Hashable {
let row: Int
let col: Int
}
var visited: Set<Point> = []
visited.insert(Point(row: 0, col: 1))8. Optionals from common calls
These all return optionals. Unwrap them on purpose.
let nums = [3, 1, 2]
let smallest = nums.min() // Int?
let firstEven = nums.first { $0 % 2 == 0 } // Int?
let value = ["a": 1]["b"] // Int?
let count = ["a": 1]["b", default: 0] // Int, no optionalAvoid ! in interview code. Use guard let or a default value. Interviewers notice force unwraps.
9. sort vs sorted
var nums = [3, 1, 2]
let copy = nums.sorted() // returns a new array
nums.sort() // sorts in place, needs var10. Recursion with shared state
Use a nested function. It can read and change the outer variables, so you avoid many inout parameters.
func subsets(_ nums: [Int]) -> [[Int]] {
var result: [[Int]] = []
var current: [Int] = []
func build(from start: Int) {
result.append(current)
for i in start..<nums.count {
current.append(nums[i])
build(from: i + 1)
current.removeLast()
}
}
build(from: 0)
return result
}Complexity: say it every time
At the end, always state time and space complexity, with a short reason.
"Time is O(n log n) because of the sort. Space is O(n) for the dictionary."
Know these Swift costs:
| Operation | Cost |
|---|---|
array[i], append | O(1) (append is O(1) on average) |
insert(at: 0), removeFirst() | O(n) |
contains on Array | O(n) |
contains on Set, dictionary lookup | O(1) on average |
sorted() | O(n log n) |
Array(string) | O(n) |
Common mistakes
- Starting to code before asking any questions.
- Coding the optimal solution in silence, then not explaining it.
- Forgetting edge cases: empty input, one element, duplicates, negative numbers.
- Using
removeFirst()in a loop and making the solution O(n²). - Saying "done" without testing.
How to practise on this site
- Coding questions run real Swift in the browser. Press "Run" for the visible tests, then "Submit" for all tests.
- Set a timer to the suggested time shown on the question before you start.
- Read the solution even if you passed. Compare your complexity and edge cases with it.
- Write the gotchas you hit in your mistakes list.