The coding interview
Not the problems (that's DSA) but the process interviewers grade: clarify→approach→code→test→complexity, thinking aloud, and recovering when stuck — with a solve-and-self-test example.
Learning objectives
- Run the coding-interview process, not just solve the problem.
- Think aloud and communicate your approach.
- Test your solution and state complexity.
- Recover gracefully when stuck; coach others through it.
code/cr3-coding-interview/ — run them against your own resume, stories, and offers.1 · The process beats the answer essential
The DSA track taught you to solve the problems. This is the process interviewers actually grade: how you clarify, communicate, and verify — many candidates who reach the answer still fail on process.
This strip is the process a coding interview grades — not the puzzle itself, but the five steps you should visibly walk through every time. Read the boxes left to right; the small text under each is what that step involves.
- Clarify — before touching code, restate the problem and ask questions about inputs, edge cases and limits. Jumping straight to code is the classic mistake.
- Approach — say your plan out loud before coding and get the interviewer's nod. This is where you mention the trade-off ("brute force is O(n²), a hash map is O(n)").
- Code — write it cleanly while thinking aloud so the interviewer follows your reasoning, not just your keystrokes.
- Test — trace an example and the edge cases (empty, one item, duplicates) and fix bugs you spot. Complexity — finish by stating the big-O time and space. The arrows mean you move through them in this order.
In short: Reaching the right answer isn't enough — interviewers hire on this loop. Practise narrating all five steps aloud, because silence and skipping straight to code are what actually sink candidates.
The 5 steps, every time
- Clarify: restate the problem, ask about inputs/edge cases/constraints. Never code immediately.
- Approach: propose an approach out loud, state its complexity, get a nod before coding.
- Code: implement cleanly, narrating as you go.
- Test: walk through an example + edge cases; fix bugs you find.
- Complexity: state time/space, and whether you could do better.
2 · Think aloud essential
Silence is the killer. Interviewers hire for how you think, so narrate: "I could brute force this O(n²), but a hash map gets O(n) — let me do that." Even when stuck, voice your options.
3 · Test & state complexity intermediate
Before saying "done," trace an example and the edges (empty, one element, duplicates). Then give big-O — the analysis skill from DS1. Here's a clean solution with its own test, the way you should verify live.
two_sum.pydef two_sum(nums, target):
"""Return indices of two numbers summing to target. O(n) time, O(n) space."""
seen = {} # value -> index
for i, n in enumerate(nums):
if target - n in seen:
return [seen[target - n], i]
seen[n] = i
return []
# TEST like you would out loud in the interview:
assert two_sum([2, 7, 11, 15], 9) == [0, 1] # normal
assert two_sum([3, 3], 6) == [0, 1] # duplicates
assert two_sum([1, 2], 10) == [] # no answer (edge)
print("all cases pass — O(n) time, O(n) space")
all cases pass — O(n) time, O(n) space
This is a model of what to produce live: a clean solution to the classic "two-sum" problem (find the two numbers that add up to a target) plus its own tests and a stated complexity. It shows the whole Test + Complexity habit, not just the algorithm.
- The line in triple quotes right under
defis a docstring — a short note saying what the function does and its cost (O(n)time and space). Stating cost up front is exactly what interviewers want. seen = {}is an empty dictionary that will remember each number and the position (index) where we saw it.for i, n in enumerate(nums)loops over the list giving both the indexiand the valuen.if target - n in seen:is the trick: for the current number, we check whether the number we'd need to reach the target has already been seen. If so, we return the two positions. Otherwiseseen[n] = irecords this number for later.- Below the function, the three
assertlines are tests — each says "this must be true or stop". They cover a normal case, duplicates, and a no-answer edge case, mirroring how you'd verify aloud in the room.
What the output means: all cases pass — O(n) time, O(n) space prints only if all three assert checks held. If any assertion failed, Python would stop with an AssertionError pointing at the broken case.
Try this: Add one more assert for an empty list — two_sum([], 5) == [] — and re-run. Writing a failing edge case first, then confirming it passes, is the habit that scores points.
4 · Advanced — recovering when stuck advanced
Getting stuck is normal; how you recover is graded. Say what you know, try a smaller example, state a brute force and improve from there, or ask a targeted question. Panicking or going silent fails; methodical recovery can still pass.
5 · Professional — the remote/practical variants professional
Beyond whiteboard algorithms: take-home projects (treat like real code — tests, README, clean commits: DF/TQ!), pair-programming rounds (collaborate, ask, use the tools), and debugging rounds (reproduce, isolate, fix — TQ4's bug-fix-first). Your engineering habits are the differentiator here.
6 · Tech-lead — the interviewer's seat tech-lead
As a lead you'll give these interviews. Good interviewing is a skill: a consistent rubric, signal over trivia, making candidates comfortable enough to show their best, and calibrated notes. Knowing the rubric from this side also makes you a far better candidate.
| Weak interviewer | Strong interviewer |
|---|---|
| gotcha trivia | realistic problems |
| stares silently | hints to unblock, observes recovery |
| "gut feel" verdict | rubric + concrete evidence |
| tests memorization | tests thinking + collaboration |
Exercise CR3.1 — Run the full loop
Context: The five-step loop — clarify, approach + complexity, code, test, complexity — only becomes automatic by running it aloud on a real problem and catching where you go silent.
Your task: Take any DSA problem you've solved and do a mock interview out loud (record yourself): clarify, state approach + complexity, code, then write asserts covering normal + edge cases, and state final big-O. Note where you went silent — that's what to practice.
Requirements:
- Clarify: restate the problem and ask about inputs, edge cases, constraints
- Approach: propose out loud and state complexity before coding
- Code: implement while narrating
- Test: trace normal and edge cases with assertions
- Complexity: state final time and space; record yourself and note the silent stretches
💡 Hint: The silence is the signal — the moments you stop narrating are exactly the habits to rehearse until they're automatic.
🪜 Practice ladder beginner → industry
Six graded exercises, easy to real-world. Try each before opening its solution.
Context: Coding interviews grade the process, not just the answer. Stating the brute-force and the better approach with their complexities before coding signals you're choosing an approach on purpose.
Your task: You're asked: "Return the first value that appears twice in a list, or None." Before writing code, state the brute-force and the better approach with their complexities, then implement the better one.
Requirements:
- Name brute force (nested loops, O(n²) time, O(1) space) out loud first
- Name the better approach (a
seenset, O(n) time, O(n) space) - Implement the set-based version
- Return the first value already in
seen, elseNone - Lead with complexity to show a deliberate choice, not a stumble
💡 Hint: Track what you've already seen in a set and return on the first repeat — the trade is spending O(n) space to buy O(n) time.
Show solution
Say this first: “Brute force is nested loops — O(n²) time, O(1) space. I can do O(n) time with O(n) space by remembering what I’ve seen in a set.” Then code the better one:
def first_repeat(nums):
seen = set()
for x in nums:
if x in seen:
return x
seen.add(x)
return None
print(first_repeat([3, 1, 4, 1, 5])) # 1
print(first_repeat([1, 2, 3])) # None
Leading with complexity signals you’re choosing an approach on purpose, not stumbling into one.
Context: On a sorted array, the two-pointer trick beats a hash map on space — and narrating why the sorted property enables it is what the interviewer scores, more than the code.
Your task: "Given a sorted array and a target, return indices of two numbers that sum to the target." Narrate the key realization (why sorted lets you avoid the hash map) and implement it in O(n)/O(1).
Requirements:
- Explain aloud that sortedness lets you converge two pointers from the ends
- Move the right pointer left when the sum is too big, the left pointer right when too small
- Achieve O(n) time and O(1) space — no hash map
- Return the pair of indices when the sum matches
- Emphasise stating WHY the property enables the technique
💡 Hint: Let the sum tell you which pointer to move — too big means shrink from the right, too small means grow from the left.
Show solution
Narration: “Because it’s sorted, I can put one pointer at each end. If the sum is too big I move the right pointer left; too small, move left pointer right. That’s O(n) time and O(1) space — no hash map needed, which beats the unsorted version on space.”
def two_sum_sorted(nums, target):
lo, hi = 0, len(nums) - 1
while lo < hi:
s = nums[lo] + nums[hi]
if s == target:
return (lo, hi)
if s < target:
lo += 1
else:
hi -= 1
return None
print(two_sum_sorted([1, 3, 4, 5, 7, 11], 9)) # (2, 3) -> 4 + 5
Saying why the property (sorted) enables the technique is the signal the interviewer scores, more than the code itself.
Context: Volunteering edge-case tests before being asked proves you think about correctness the way production does. After solving "merge two sorted lists", enumerate the cases that catch a subtle bug.
Your task: After solving "merge two sorted lists," enumerate the edge cases out loud and write assertions that would catch a subtle bug (e.g. an off-by-one or a dropped tail).
Requirements:
- Name edge cases: empty inputs, one list exhausted first, duplicates, single elements, interleaved vs ordered
- Assert both-empty and one-side-empty cases
- Assert an interleaved case that also exercises the trailing tail
- Assert a duplicates case
- Call out the classic bug: forgetting to append the remaining tail
💡 Hint: The dropped-tail bug hides until one list outlasts the other — a test where the lists have different lengths is what surfaces it.
Show solution
Edge cases to name: empty inputs, one list exhausted first, duplicates, single elements, already-ordered vs interleaved.
def merge(a, b):
i = j = 0
out = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
out.append(a[i]); i += 1
else:
out.append(b[j]); j += 1
out.extend(a[i:]) # the dropped-tail bug lives here if you forget it
out.extend(b[j:])
return out
assert merge([], []) == []
assert merge([], [1, 2]) == [1, 2] # one side empty
assert merge([1, 5], [2, 3, 9]) == [1, 2, 3, 5, 9] # interleave + tail
assert merge([1, 1], [1]) == [1, 1, 1] # duplicates
print("all tests passed")
Volunteering the tail and empty-input cases proves you think about correctness the way production does.
Context: Getting unstuck methodically — state a correct baseline, name the wasted work, then optimize — often scores higher than reaching the perfect idea instantly.
Your task: You blanked on the optimal approach for "longest substring without repeating characters." Show the recovery script — how to talk and what to write — that turns being stuck into a passing signal, then land the sliding-window solution.
Requirements:
- Say it aloud: start with something correct even if slow
- Name the brute-force baseline and its cost
- Identify the wasted work (re-scanning) as the thing to eliminate
- Pivot to a window that remembers each character's last index
- On a repeat, jump the window start past the previous occurrence
💡 Hint: Track the last index of each character and advance the window start past any repeat — the pivot is naming the re-scan as the waste to cut.
Show solution
Recovery script: “Let me start with something correct even if slow, then optimize.” Brute force: check every substring — O(n³). “The repeated work is re-scanning; I can keep a window and remember the last index of each char.” That’s the insight; now code it:
def longest_unique(s):
last = {}
start = best = 0
for i, ch in enumerate(s):
if ch in last and last[ch] >= start:
start = last[ch] + 1 # jump the window past the repeat
last[ch] = i
best = max(best, i - start + 1)
return best
print(longest_unique("abcabcbb")) # 3 ("abc")
print(longest_unique("bbbbb")) # 1
print(longest_unique("")) # 0
Interviewers pass candidates who get unstuck methodically (state a correct baseline, name the wasted work, optimize) far more than ones who freeze waiting for the perfect idea.
Context: A take-home is scored on engineering, not just the algorithm. Knowing what to add that a whiteboard skips — structure, a docstring, input validation, a test — is the difference between "works" and "would merge this".
Your task: A take-home asks you to "parse a log file and report the top-3 slowest endpoints." List what to add that a whiteboard answer skips, then give a clean solution.
Requirements:
- Add readable structure, a docstring, input validation, and at least one test
- Tolerate malformed lines (skip them) rather than crashing
- Aggregate durations per endpoint (e.g. a
defaultdict(int)) - Select the top-k efficiently (e.g.
heapq.nlargest) - Note complexity and assumptions; don't over-engineer
💡 Hint: The malformed-line guard, the test, and the docstring are exactly the parts a whiteboard lets you skip — and exactly what a take-home is grading.
Show solution
What take-homes score that whiteboards don’t: readable structure, a docstring, input validation, a test, and a note on complexity/assumptions. Add those — don’t over-engineer.
from collections import defaultdict
import heapq
def top_slow_endpoints(lines, k=3):
# Given "METHOD /path DURATION_MS" lines, return the k endpoints with
# the highest total duration as (path, total_ms), descending.
totals = defaultdict(int)
for ln in lines:
parts = ln.split()
if len(parts) != 3:
continue # tolerate malformed lines
_, path, dur = parts
totals[path] += int(dur)
return heapq.nlargest(k, totals.items(), key=lambda kv: kv[1])
sample = ["GET /a 120", "GET /b 300", "GET /a 200", "bad line", "GET /c 50"]
assert top_slow_endpoints(sample, 2) == [("/a", 320), ("/b", 300)]
print("passed")
The malformed-line guard, the test, and the docstring are the difference between “works” and “would merge this.” That’s what a take-home is actually measuring.
Context: Two candidates can reach the same correct code and earn very different signals. Building the rubric yourself proves you understand interviews score the process, because it predicts on-the-job behaviour.
Your task: You're interviewing for "validate balanced parentheses." Two candidates both reach the correct stack solution. Write the rubric you'd use and decide who gets the higher signal — and why.
Requirements:
- Rubric dimensions: clarifying questions, complexity stated, edge cases tested, communication
- Candidate A clarifies, states complexity up front, tests edge cases unprompted, narrates the invariant
- Candidate B assumes, codes silently, states complexity only when asked, tests only the happy path
- Give A the higher signal despite identical code
- Justify it: the process predicts behaviour on a harder problem
💡 Hint: Correct-but-silent reads as a coin flip on the next, harder question — the clarifying, narrating, self-testing candidate is the safer bet.
Show solution
Both produced the same working code:
def balanced(s):
pairs = {")": "(", "]": "[", "}": "{"}
stack = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
return not stack
| Signal | Candidate A | Candidate B |
|---|---|---|
| Clarifying Qs | Asked: only these 3 bracket types? ignore other chars? | Assumed, started coding |
| Complexity stated | “O(n) time, O(n) space” up front | Only when asked |
| Edge cases | Tested “”, “(]”, “(((” unprompted | Tested happy path only |
| Communication | Narrated the stack invariant | Coded silently, correct |
A gets the higher signal despite identical code. Interviews score the process — clarifying, stating complexity, self-testing, narrating — because that predicts on-the-job behavior. Correct-but-silent (B) reads as a coin flip on a harder problem.
✓ Checkpoint — you can move on when you can…
- Run clarify→approach→code→test→complexity.
- Think aloud throughout.
- Self-test and state big-O.
- Recover methodically; understand the interviewer's rubric.
Knowledge check check yourself
What are the five steps of the coding-interview process that interviewers grade, in order?
Show answer
If the elegant solution isn't coming, why does a working brute force beat a broken "optimal" one?