12.10 — Live Coding Playbook
Opening scenario
Screen share opens. Interviewer says: “Implement an LRU cache in Swift, generic over key and value.” You have 30 minutes. The cursor blinks. You feel your heart rate spike. What you do in the next 60 seconds matters more than the code you eventually write.
The narration rule
Talk continuously. Silent typing is hostile to the interviewer — they can’t tell if you’re stuck or thinking. Narrate at three altitudes:
- What you’re about to do: “I’ll start with the signature.”
- Why you’re choosing it: “Generic over Key and Value; Key needs Hashable.”
- What you’d revisit: “I’ll come back to thread safety after correctness.”
Even silent thinking takes voice: “Let me think for ten seconds about the eviction order.” That signals scope and gives the interviewer permission to wait.
The clarification ritual
Before typing, ask 3–5 questions. This is expected — not asking is the red flag.
For LRU cache:
- “Fixed capacity at init, or resizable?”
- “Thread safety required?”
- “Should
getcount as a use (move to front)?” - “Are we optimizing for read-heavy or balanced workload?”
- “Should
setof existing key update the value or just bump recency?”
Write the answers in a comment. They become your spec.
// Spec:
// - Fixed capacity at init
// - Single-threaded (caller's problem)
// - get bumps recency
// - set of existing key updates value AND bumps recency
// - Evict least-recently-used on overflow
The common iOS live-coding patterns
You should be able to write each of these blind in 10 minutes.
LRU cache
final class LRUCache<Key: Hashable, Value> {
private let capacity: Int
private var dict: [Key: Node] = [:]
private var head: Node? // most recently used
private var tail: Node? // least recently used
private final class Node {
let key: Key
var value: Value
var prev: Node?
var next: Node?
init(_ k: Key, _ v: Value) { key = k; value = v }
}
init(capacity: Int) {
precondition(capacity > 0)
self.capacity = capacity
}
func get(_ key: Key) -> Value? {
guard let node = dict[key] else { return nil }
moveToFront(node)
return node.value
}
func set(_ key: Key, _ value: Value) {
if let node = dict[key] {
node.value = value
moveToFront(node)
return
}
let node = Node(key, value)
dict[key] = node
addToFront(node)
if dict.count > capacity, let lru = tail {
removeNode(lru)
dict.removeValue(forKey: lru.key)
}
}
private func addToFront(_ node: Node) {
node.next = head
head?.prev = node
head = node
if tail == nil { tail = node }
}
private func removeNode(_ node: Node) {
node.prev?.next = node.next
node.next?.prev = node.prev
if head === node { head = node.next }
if tail === node { tail = node.prev }
node.prev = nil; node.next = nil
}
private func moveToFront(_ node: Node) {
guard head !== node else { return }
removeNode(node)
addToFront(node)
}
}
Narration cues: “I’m using a doubly-linked list + dictionary for O(1) get and set; dictionary maps key to node, list maintains LRU order.”
Debounce
actor Debouncer {
private let delay: Duration
private var task: Task<Void, Never>?
init(delay: Duration) { self.delay = delay }
func call(_ action: @escaping @Sendable () async -> Void) {
task?.cancel()
task = Task {
try? await Task.sleep(for: delay)
guard !Task.isCancelled else { return }
await action()
}
}
}
// Usage:
let d = Debouncer(delay: .milliseconds(300))
await d.call { await search(query: text) }
Thread-safe counter (actor)
actor Counter {
private(set) var value = 0
func increment() { value += 1 }
func decrement() { value -= 1 }
}
If asked for a pre-actor version: DispatchQueue with .barrier flag for writes.
Simple @Observable from scratch
@propertyWrapper
struct Tracked<Value> {
private var storage: Value
var wrappedValue: Value {
get { storage }
set { storage = newValue; notify() }
}
init(wrappedValue: Value) { storage = wrappedValue }
var listeners: [(Value) -> Void] = []
mutating func subscribe(_ cb: @escaping (Value) -> Void) {
listeners.append(cb)
}
private func notify() { listeners.forEach { $0(storage) } }
}
Useful when interviewer asks “how does @Observable work under the hood?”
Async image fetcher with cancellation
actor ImageLoader {
private var cache: [URL: UIImage] = [:]
private var inFlight: [URL: Task<UIImage, Error>] = [:]
func image(for url: URL) async throws -> UIImage {
if let cached = cache[url] { return cached }
if let task = inFlight[url] { return try await task.value }
let task = Task<UIImage, Error> {
let (data, _) = try await URLSession.shared.data(from: url)
guard let img = UIImage(data: data) else { throw URLError(.cannotDecodeContentData) }
return img
}
inFlight[url] = task
defer { inFlight[url] = nil }
let img = try await task.value
cache[url] = img
return img
}
}
SwiftUI live coding expectations
For SwiftUI questions (“build a TODO app live”):
- Start with the model (
struct Todo: Identifiable, Hashable). - Then
@Observablestore (TodoStore). - Then root view with
@Statestore,NavigationStack. - Then
List + ForEachwith add/delete. - Comment on
@Observablevs@StateObjectif iOS 17 is allowed. - Talk about persistence: “If we wanted to persist, I’d reach for SwiftData.”
The 3-step stuck recovery
You’re 15 minutes in. Stuck. What to do:
- Verbalize the gap: “I’m stuck because I’m not sure how to handle the case where X.” Naming the obstacle often reveals the fix.
- Reduce the problem: “Let me solve the simpler version first — without thread safety / generics / cancellation — and then add the missing piece.”
- Ask for a small hint: “Could I get a nudge on the data structure?” — Interviewers expect this; they’re rooting for you.
Never silently flounder. Silence past 30 seconds is the red flag.
What interviewers score
- Speed to first working code: 10–15 min for a working naive solution beats 30 min of perfect code that doesn’t run.
- Test mentality: even a quick
let cache = LRUCache<String, Int>(capacity: 2); cache.set("a",1); cache.set("b",2); cache.set("c",3); assert(cache.get("a") == nil)shows discipline. - Tradeoff verbalization: “If write-heavy, I’d switch to…”
- Composure under correction: when the interviewer says “what if capacity is 0?” your reaction tells them everything.
Common misconceptions
- “Live coding tests algorithms.” Mostly it tests communication under uncertainty. The algorithm is the medium.
- “You should code in silence to focus.” Silence loses you points even with perfect code.
- “Optimize prematurely.” Get it working first. Optimization talk comes after.
- “Tests are skippable.” A 30-second sanity test impresses more than another minor optimization.
- “Compile errors lose points.” Honest typos are fine; live-fix and move on. Conceptual errors are what matter.
Seasoned engineer’s take
Live coding is performance art with code. The interviewer is hiring a teammate — they’re judging “do I want to pair with this person on a hard problem?” The code matters; the collaboration vibe matters more. Be the person who narrates clearly, asks good questions, recovers gracefully, and tests their work. The exact algorithm choice rarely decides the outcome.
TIP: Practice in a code editor without autocompletion. Your future interview will be in a shared web editor with mediocre tooling. Build the muscle of writing Swift from memory.
WARNING: Do not use AI assistance during live interviews unless explicitly invited. Even if not banned, it reads as poor judgment.
Interview corner
Junior: “What’s the first thing you do when given a live coding problem?” Restate the problem and ask 2–3 clarifying questions to lock down the spec. Then write the type signature.
Mid: “How do you handle being stuck mid-interview?” Verbalize the obstacle, reduce to a simpler problem, then either solve the simpler version or ask for a small hint. Silence is what hurts.
Senior: “How do you balance speed vs correctness in live coding?” I aim for a working naive solution first — even O(n²) is fine for round one — then iterate to optimal once tests pass. I narrate the tradeoff: ‘this is O(n²) but readable; if perf matters I’d switch to a heap.’ The interviewer learns that I think in tradeoffs and can ship a correct-but-imperfect solution under pressure, which mirrors actual production work.
Red-flag answer: “I just code it in silence; talking slows me down.” Senior interviews look for collaboration, not code-golf speed.
Lab preview
Lab 12.3 (mock technical interview) includes a live-coding section with 5 problems, timer, and rubric. Pair with a friend and switch roles each week.