Why take-homes are back
Take-home projects are returning because interviewers have lost confidence in isolated coding puzzles as a complete signal. AI tools can solve many contained tasks. Candidates can practise common algorithm patterns until the interview becomes a memory test. A take-home, followed by a serious review, can show product judgement, code organisation, test habits and ownership in a way that a 45-minute whiteboard cannot.
That does not make every take-home fair. Some are unpaid work. Some have vague scope. Some ask for production-grade polish while claiming to take "two hours". Candidates on Reddit and Hacker News have been blunt about long processes and take-homes that feel like Jira tickets pulled straight off a team's backlog. The useful stance is not "always refuse" or "always comply". It is to qualify the assignment, time-box it, document your decisions and prepare for the follow-up.
The market research points in this direction. Hacker News interviewers discuss modifying a take-home in a follow-up, data engineering candidates report friction around practical assignments, and The Pragmatic Engineer has written about the reality of tech interviews. At the same time, AI interview policy is splitting, with examples such as Meta allowing AI in some interviews and CodeSignal launching AI-assisted assessments.
It helps to know what the company is actually testing. A take-home is not one format but several, and the right strategy changes with each.
| Format | What it really assesses | Where it goes wrong |
|---|---|---|
| Greenfield mini-app | Structure, naming, testing instinct | Scope creep, gold-plating |
| Bug-fix in an existing repo | Reading unfamiliar code, debugging | Overconfident rewrites |
| Data pipeline or ETL task | Data quality, reproducibility | Skipping validation and lineage |
| Design document, no code | Communication, tradeoff reasoning | Vague hand-waving, no numbers |
| Pair-extend a starter repo | Working within constraints | Fighting the existing patterns |
Read the brief twice and decide which of these you are facing before you write a single line.
Qualify the assignment before you start
Ask four questions before accepting a take-home:
- What is the expected time box?
- What criteria will be used to review it?
- Will there be a live follow-up where I explain and modify the work?
- What is the policy on AI tools, libraries and boilerplate?
This is a reasonable email:
Thanks for sending the exercise. Before I start, could you confirm the expected time box, review criteria and AI/tooling policy? I am happy to complete a representative exercise, but I want to keep the scope aligned with what the team intends to assess.
If the company cannot answer, treat that as data. It may still be worth doing the assignment, especially for a role you want, but you should make a conscious decision rather than drifting into an open-ended build.
How you weigh that decision should shift with seniority and role. The cost of a take-home is not the same for everyone.
| Level | Default posture | Reasoning |
|---|---|---|
| Junior | Usually complete a fair assignment | Practical proof can offset a thin CV |
| Mid | Complete it, but enforce the time box hard | Your CV already carries some signal |
| Senior | Negotiate, prefer a paid or live exercise | Your time is scarce and your record speaks |
| Staff and above | Push for a design conversation instead | Coding samples under-test the actual job |
Role matters too. A frontend take-home that asks for an accessible, responsive component is testing different muscles from a data engineering task that asks you to make a flaky pipeline idempotent. For everyone, avoid multi-day unpaid builds unless the role is unusually attractive or the company pays for the exercise.
Build for review, not for fantasy production
A good take-home is not a startup MVP. It is a reviewable sample of your engineering judgement. You should optimise for clarity, correctness and explainable tradeoffs, not for breadth of features.
Use this structure:
- A short README with setup, assumptions and tradeoffs.
- A small domain model with clear boundaries.
- Tests around the important behaviour.
- Simple error handling at real boundaries.
- One or two deliberate extension points.
- No speculative infrastructure.
For a frontend assignment, that might mean a clean state model, accessible forms, loading and error states, and a few focused tests. For a backend assignment, it might mean validated inputs, a defensible persistence decision, idempotency where relevant and integration tests. For a data assignment, it might mean data quality checks, lineage notes and a reproducible pipeline that runs from a single command.
It helps to picture the difference between a weak and a strong submission of the same task.
| Signal | Weak submission | Strong submission |
|---|---|---|
| README | "Run npm start" | Setup, assumptions, tradeoffs, "with more time" |
| Tests | None, or one happy path | Validation and state transitions covered |
| Commits | One giant "final" commit | Small, readable, logically grouped |
| Errors | Swallowed or printed | Handled at real boundaries, surfaced clearly |
| Scope | Half-built extra features | One thing, done cleanly, with extension points |
The single most common mistake is gold-plating: adding a caching layer, a plugin system or a Docker Compose file nobody asked for, while the core behaviour stays under-tested. Reviewers read this as poor scope judgement, which is exactly the trait the exercise is meant to surface. The second most common mistake is the opposite, shipping code that was never run. Always do a clean checkout into a fresh directory and follow your own README before you submit.
Here is a small README structure that works:
# Take-home submission
## Setup
pnpm install
pnpm test
pnpm dev
## Assumptions
- The API returns stable IDs.
- Pagination is cursor-based.
- The exercise is scoped to authenticated users but auth is stubbed.
## Tradeoffs
- I used in-memory storage to keep the exercise runnable without external services.
- I added tests around validation and state transitions rather than snapshot-heavy UI tests.
- I did not add background jobs because the requested workflow is synchronous.
## With more time
- Add contract tests for the external API.
- Add persistence migrations.
- Add observability around failed imports.That format helps the reviewer see judgement quickly, which is the whole point.
A worked example: a CSV import endpoint
Imagine the brief is: "Build an endpoint that accepts a CSV of orders, validates each row and stores the valid ones. Two hours." A candidate who panics will reach for a queue, a database and a file-upload UI. A candidate with scope discipline writes the smallest thing that demonstrates the right instincts.
// Parse, validate per row, collect both outcomes. No database, no queue.
type Order = { id: string; amount: number; currency: string };
type RowResult =
| { ok: true; order: Order }
| { ok: false; line: number; error: string };
export function importOrders(rows: string[][]): {
imported: Order[];
rejected: RowResult[];
} {
const imported: Order[] = [];
const rejected: RowResult[] = [];
rows.forEach((cols, i) => {
const [id, rawAmount, currency] = cols;
const amount = Number(rawAmount);
if (!id) {
rejected.push({ ok: false, line: i + 1, error: "missing id" });
return;
}
if (Number.isNaN(amount) || amount <= 0) {
rejected.push({ ok: false, line: i + 1, error: "invalid amount" });
return;
}
imported.push({ id, amount, currency });
});
return { imported, rejected };
}This is deliberately small, and that is the strength. It validates per row, never throws away the whole file because of one bad line, and returns a structured result the reviewer can test directly. The matching test makes your priorities visible.
test("rejects bad rows without dropping good ones", () => {
const result = importOrders([
["A1", "10.00", "GBP"],
["", "5.00", "GBP"],
["A3", "-1", "GBP"],
]);
expect(result.imported).toHaveLength(1);
expect(result.rejected.map((r) => r.error)).toEqual([
"missing id",
"invalid amount",
]);
});Notice what is absent: no persistence, no HTTP framework wiring, no streaming for huge files. Those belong in the "with more time" section of the README, where they prove you saw the production path without spending your two hours building it. That contrast, a tight core plus an articulate list of what you deliberately left out, is the signal reviewers reward.
Use AI honestly, if allowed
AI is now part of the take-home conversation. Some companies allow it because it mirrors real work. Others ban it because they want your baseline skill. The worst option is hidden use that you cannot defend in the follow-up.
If AI is allowed, use it as a collaborator, not a ghostwriter.
| Good use | Bad use |
|---|---|
| Generate candidate test cases, then choose and edit them | Paste the prompt and submit the generated project unchanged |
| Ask for edge cases against your own design | Add libraries you cannot explain |
| Explain an unfamiliar library API, then verify against docs | Ship code you did not run |
| Draft boilerplate you already understand | Let AI invent APIs without checking docs |
| Review your README for missing setup steps | Accept a design you cannot defend out loud |
Put a short disclosure in the README when appropriate:
## Tooling note
I used an AI assistant to brainstorm test cases and review the README for clarity.
All implementation decisions, code edits and verification were done by me.That statement is not a magic shield. It only helps if you can explain the code line by line. Built In's discussion of the AI job-interview cheating debate captures why employers are sensitive here: the issue is not tool use itself, but whether the submission signals real ability. The safe internal test is simple. If a reviewer points at any function and asks "why did you write it this way?", you should have an answer that is yours, not the model's.
Prepare for the change-request follow-up
The follow-up is where many take-homes become useful, and where weak submissions unravel. Interviewers may ask you to add a field, change a business rule, debug a failing test, improve performance or critique your own design while they watch.
Before the follow-up, prepare:
- A two-minute architecture walkthrough.
- The top three tradeoffs you made.
- One thing you would change with more time.
- The highest-risk edge case.
- Where you would add monitoring or logging in production.
Then practise modifying your own code quickly. If you cannot change it under light pressure, you probably overbuilt it. A useful drill is to set a timer and add a field end to end, from input validation through to the test, in ten minutes. If that feels hard in your own codebase, simplify before you submit.
A good follow-up answer sounds like:
I kept the importer synchronous because the sample input is small and it made the behaviour easier to review. If this moved to production, I would put imports onto a queue, make the operation idempotent by source file ID and expose import status to the UI.
That answer shows scope control and production awareness in three sentences. A weak answer to the same question is "I would probably refactor it" with no specifics, which tells the reviewer you have not thought past the submission.
If the interviewer asks you to make a live change, narrate as you go. Say what you are about to do, make the smallest edit that works, run the test, then talk about what you would do with more time. The visible loop of edit, run, verify is itself a strong signal, often stronger than the change.
Know when to decline
Declining a take-home is reasonable when:
- The work clearly resembles a real unpaid company task.
- The time expectation is more than a working day without compensation.
- The company refuses to share review criteria.
- The process already has many rounds and the assignment is additive.
- The role is not attractive enough to justify the cost.
Use a polite decline:
I appreciate the opportunity, but I am going to withdraw from the process at this point. The assignment appears larger than I can reasonably take on unpaid alongside other commitments. I would be open to a shorter live exercise or a paid project if that is available.
Candidates report success with firm but professional boundaries, especially at senior levels. There is no guarantee. In a tight market, some companies will simply move on. The point is to make a deliberate tradeoff rather than resentfully grinding through a build you have already decided is unfair. A counter-offer, suggesting a shorter live session or a paid trial, often lands better than a flat no and signals that you are interested but value your time.
Frequently asked questions
How long should a take-home actually take? Treat the stated time box as a budget, not a target. If the brief says two hours, stop at two hours and document what you would have done next. A submission that respects the time box and explains its limits beats a polished one that clearly took a weekend.
Should I tell them how long it took me? Only if asked, and then be honest. Overstating speed sets a trap for the follow-up. Understating it can read as showing off. The README "with more time" section communicates this more gracefully than a stopwatch boast.
What if I run out of time mid-feature? Leave the codebase in a working state, even if a feature is incomplete. A clean, runnable submission with one missing feature is far better than a broken submission with everything half-wired. Note the gap explicitly.
Do I need 100 percent test coverage? No. Test the behaviour that matters: validation, state transitions and the core logic. Coverage as a number is noise. Tests that document your intent are the signal.
Can I reuse a take-home for multiple companies? Be careful. Tailor the README and the framing to each brief, and never reuse one company's proprietary starter code for another. The judgement on display should match the specific task in front of you.
Continue your prep
Take-homes are easier when your core role prep is current: