AI EngineeringZero to ProductionHome·About·Contact
Career & Interview Prep · Chapter CR3

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.

⏱️ ~2 hours🧪 2 labs🎯 Beginner→Tech-lead

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.
▶ Runnable companionThe tools here are saved under 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.

Clarify ask questions Approach before coding Code think aloud Test edge cases Complexity big-O
🗺️ How to read this diagram

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

  1. Clarify: restate the problem, ask about inputs/edge cases/constraints. Never code immediately.
  2. Approach: propose an approach out loud, state its complexity, get a nod before coding.
  3. Code: implement cleanly, narrating as you go.
  4. Test: walk through an example + edge cases; fix bugs you find.
  5. 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.

Example code for learning — review, test, and adapt it before running against real or production systems. Commands can create, change, or delete resources. See the Terms & Disclaimer.
Python · solve + self-test + complexity (runs)
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
▶ How this works

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.

  1. The line in triple quotes right under def is 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.
  2. 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 index i and the value n.
  3. 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. Otherwise seen[n] = i records this number for later.
  4. Below the function, the three assert lines 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.

A working brute force beats a broken 'optimal'If the elegant solution isn't coming, code the O(n²) one, get it working and tested, then say "now let me optimize." A correct simple answer + clear path to better often scores higher than a half-finished clever one.

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 interviewerStrong interviewer
gotcha triviarealistic problems
stares silentlyhints to unblock, observes recovery
"gut feel" verdictrubric + concrete evidence
tests memorizationtests 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.

Exercise 1 · State complexity before you codeBeginner

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 seen set, O(n) time, O(n) space)
  • Implement the set-based version
  • Return the first value already in seen, else None
  • 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.

Exercise 2 · Think aloud through the two-pointer trickIntermediate

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.

Exercise 3 · Write tests that expose your own bugsAdvanced

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.

Exercise 4 · Recover out loud when you’re stuckExpert

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.

Exercise 5 · Handle the take-home / practical variantProfessional

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.

Exercise 6 · Grade a candidate from the interviewer’s seatIndustry scenario

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
📋 Interviewer scoring grid
SignalCandidate ACandidate B
Clarifying QsAsked: only these 3 bracket types? ignore other chars?Assumed, started coding
Complexity stated“O(n) time, O(n) space” up frontOnly when asked
Edge casesTested “”, “(]”, “(((” unpromptedTested happy path only
CommunicationNarrated the stack invariantCoded 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

✓ Knowledge check

What are the five steps of the coding-interview process that interviewers grade, in order?

Show answer
Clarify (restate, ask about inputs/edge cases/constraints), Approach (propose out loud, state complexity, get a nod), Code (implement while narrating), Test (trace examples and edge cases), and Complexity (state time/space).
✓ Knowledge check

If the elegant solution isn't coming, why does a working brute force beat a broken "optimal" one?

Show answer
A correct, tested simple answer (even O(n²)) plus a clear path to optimize often scores higher than a half-finished clever solution — and methodical recovery when stuck is itself graded, while going silent fails.
© 2026 studybydoing.in · AI Engineering: Zero to Production · All rights reserved. · About · Privacy Policy · Terms · Contact
Educational content, provided as-is and without warranty. Code samples are examples — review, test, and adapt them before using in production. See the Terms of Use & Disclaimer. Use at your own risk.
© studybydoing.in