iosNudge

Coding interviews

Chapter 5 of 10 6 min read

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.

  1. Clarify the problem.
  2. Examples: walk through 2–3 small inputs.
  3. Brute force: say the simple solution and its cost.
  4. Optimise: find a better solution, then code it.
  5. Test: run your code by hand on the examples and edge cases.

A good time split for a 45-minute round:

StepMinutes
Clarify + examples5
Brute force + optimise (talking)5–8
Code15–20
Test + fix5–10
Your questions5

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
// ""        -> -1

3. 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(""))       // -1

What 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 top

Practise 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 optional

Avoid ! 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 var

10. 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:

OperationCost
array[i], appendO(1) (append is O(1) on average)
insert(at: 0), removeFirst()O(n)
contains on ArrayO(n)
contains on Set, dictionary lookupO(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.