Take-Homes and Live Coding
Two formats with opposite rules — one rewards polish and scoping, the other rewards narration and recovery. Getting them the wrong way round costs offers.
These are the two most common practical formats and they reward opposite behaviours. Treating a live round like a take-home — heads down, polishing — is one of the most common avoidable failures.
The two formats
| Take-home | Live coding | |
|---|---|---|
| Graded on | the artefact | the process |
| Time | hours, unsupervised | 30-60 minutes, watched |
| Wins | structure, tests, README, scoping | narration, recovery, clarifying |
| Loses | over-building, no tests, no README | silence, coding before clarifying |
| Can you look things up | yes, obviously | usually, if you say so |
| Perfection expected | closer to it | no — working beats elegant |
Live coding: what is actually scored
The interviewer is filling in a rubric that mostly is not about the code:
Communication did they clarify before coding? narrate decisions?
Problem solving did they work an example? spot the pattern?
Coding is it readable? does it work?
Testing did they check their own work before being asked?
Recovery when stuck or corrected, what happened?
Only one of five rows is the code. A candidate who solves it silently in ten minutes often scores below one who solves 80% of it while thinking out loud — because four of the five rows have no evidence.
The recovery moment
This is the single most predictive part of a live round, and it can be rehearsed:
INTERVIEWER: "What happens if the list is empty?"
weak: "Oh." [starts typing silently]
weak: "It would crash I think. Should I handle that?"
(making the interviewer make your decision)
strong: "Good catch — max() raises ValueError on an empty sequence. Two options:
return None and make the caller handle it, or raise a domain-specific
error. For a library function I'd return None so the caller decides.
Let me add that and a test for it."
The strong version diagnoses, offers a choice with a recommendation, and acts. Same bug, three very different scores.
Being corrected works the same way:
INTERVIEWER: "That's O(n²) — can you do better?"
weak: "Um. Maybe... a hash map?" (guessing)
strong: "Yes. The waste is the inner scan re-checking the same elements, so I want
to answer 'have I seen this before' in constant time — that's a set. One
pass, O(n) time, O(n) space. Trading memory for time. Let me rewrite it."
The second names the waste, which is the general technique, not the specific answer.
The clock
0:00-0:05 clarify, example, state the approach
0:05-0:25 write the straightforward version
0:25-0:30 test it yourself
0:30-0:40 optimise or extend
0:40-0:45 your questions
At 20 minutes with nothing working, abandon the clever approach. A correct brute force at 35 minutes scores far above an unfinished optimal at 45. Say it out loud: “I’m going to write the straightforward version first so we have something correct, then improve it.” That sentence buys goodwill and protects you.
Live coding: the practical details
# Say what you are looking up, and why.
# fine: "let me check the argument order for heapq.nlargest"
# not: [silently searching for "sliding window python"]
# Write it readably first — you can always compress later.
def longest_unique(s):
last_seen = {}
start = best = 0
for i, ch in enumerate(s):
if ch in last_seen and last_seen[ch] >= start:
start = last_seen[ch] + 1
last_seen[ch] = i
best = max(best, i - start + 1)
return best
# Then test it in front of them, before being asked.
for case in ["abcabcbb", "bbbbb", "", "a", "au"]:
print(f"{case!r:<12} → {longest_unique(case)}")
'abcabcbb' → 3
'bbbbb' → 1
'' → 0
'a' → 1
'au' → 2
The empty string is the one that catches people, and running it unprompted is worth more than one more optimisation.
Naming variables well is free marks. last_seen and start need no explanation; d and i2
need a sentence each, and you pay that sentence every time you refer to them.
Take-homes: what reviewers check, in order
1. Does it run? git clone, follow the README, one command
2. Is the README good? can they understand it without reading code
3. Is it correct? edge cases, and do the numbers reconcile
4. Are there tests? meaningful ones, not assert True
5. Is it structured? can a feature be added without a rewrite
6. Is it clever? genuinely last — clarity beats cleverness
Two of the top three are not code. A project that cannot be run in one command is often scored without being run, which means the code you spent longest on is never read.
The README does most of the work
# Order statistics service
## Run it
make install
make run # writes output/summary.json
make test
Run `make run` twice — the output is unchanged.
## Approach
Three layers: extract validates the schema, transform is pure functions with no
I/O, load writes atomically. transform/ has no file access so it is testable
without fixtures.
## Decisions
| Decision | Why |
| --- | --- |
| Left join, `UNKNOWN` for misses | 142 rows reference a missing customer. An
| | inner join would silently drop £3,847. |
| Dedupe on order_id, keep latest | 19 duplicates in the input, differing status. |
| Parquet output | typed; ~8x smaller than CSV. |
## What I found in your data
- 142 rows (1.1%) reference a customer not in customers.csv
- 19 duplicate order_ids, all with differing status
- a `cancelled` status that is not in the provided spec
## Given more time
1. Incremental loads with a 3-day lookback and a merge on order_id
2. Volume check against a trailing average — catches a truncated input
3. DuckDB past a few GB; the pure-function structure means only the engine changes
## Time spent
~4 hours: 1h exploring the data, 1.5h pipeline, 1h tests, 0.5h README.
Every section earns its place. “What I found in your data” is the strongest — it proves you looked rather than just processed, and it often tells the reviewer something they did not know. “Given more time” pre-empts every “why didn’t you…” and converts a gap into evidence of judgement.
Tests that count
def test_deduplicates_keeping_latest():
rows = [{"id": 1, "ts": "2026-01-04", "status": "completed"},
{"id": 1, "ts": "2026-01-05", "status": "returned"},
{"id": 2, "ts": "2026-01-04", "status": "completed"}]
out = clean(rows)
assert len(out) == 2
assert out[0]["status"] == "returned" # the later one won
def test_missing_dimension_does_not_lose_revenue():
out = summarise(orders_with_orphan, customers)
assert sum(r["revenue"] for r in out) == 25.50 # nothing dropped
assert out[0]["country"] == "UNKNOWN"
def test_empty_input_returns_empty_not_error():
assert summarise([], []) == []
... [100%]
3 passed in 0.04s
Three tests, each asserting a decision rather than a line of code. The second is the most valuable in any data take-home, because silent row loss is the specific thing reviewers probe for. The third is the one nobody writes and everybody notices.
Scope to the stated time
brief says you deliver reviewer reads
"~4 hours" 4 hours + README on the rest good judgement
"~4 hours" 40 hours, Docker, Terraform, cannot scope work
a dashboard, 3 databases
"~4 hours" 2 hours, no tests, no README did not take it seriously
The over-built submission is the counterintuitive one. A reviewer seeing infrastructure for a four-hour exercise does not think “thorough” — they think this person will gold-plate a two-day ticket into two weeks, which is a harder objection to overcome than a missing feature.
If the brief genuinely needs twenty hours, say so and deliver the four-hour version. A company that penalises that has told you something useful.
The follow-up call
Most take-homes have one. Prepare three things:
- A two-minute walkthrough — the layers, the grain, the decisions. Do not read code aloud.
- The findings — the orphans, the duplicates, the undocumented status. Leading with what you found in their data changes the tone of the call.
- What you would change — have a real answer. “Nothing” reads as no critical distance.
Expect “how would this handle a hundred times the data?” — which is already in your README, which is why that section exists.
Common failures
| Failure | Format | Fix |
|---|---|---|
| Silence when stuck | live | narrate the fallback and the direction |
| Coding before clarifying | live | three questions, always |
| Optimising before it works | live | correct first, fast second |
| No tests | take-home | three meaningful ones beats broad coverage |
| Thin or missing README | take-home | it is the highest-return hour |
| Does not run | take-home | clone into a clean directory and try it |
| Over-building | take-home | scope to the stated time, document the rest |
| Notebook only | take-home | suggests you have not written production code |
The “clone into a clean directory” one is worth doing literally. A submission that depends on something already installed on your machine is the most common reason a take-home is scored without being run.
Practice
1. Rehearse being corrected on complexity.
"The waste is the inner scan re-checking the same elements, so I want to answer
'have I seen this' in constant time — that's a set. O(n) time, O(n) space."
Naming the waste generalises; naming the data structure does not. This is the single most useful thing to rehearse for a live round.
2. Run your take-home in a clean clone.
git clone <repo> /tmp/fresh && cd /tmp/fresh && make install && make run
ModuleNotFoundError: No module named 'pandas'
Something already installed on your machine was never in the requirements. This is why submissions get scored without being run.
3. Test the empty case in a live round, unprompted.
'' → 0
Ten seconds, and it covers a rubric row that most candidates leave blank. Do it before the interviewer asks.
4. Write the "Given more time" section before writing any code.
1. Incremental loads with a lookback
2. Volume check against a trailing average
3. DuckDB past a few GB
Writing it first tells you what to cut. It is also the section reviewers quote back to you on the follow-up call.
Next: code review interviews — reading someone else’s code under time pressure.