Skip to content

Senior quality signals in a take-home

Reviewers run the error path first: truthful screens for every request, server-side validation, config from the environment, one-command setup, no dead files.

Predict first

Before you read: predict

A reviewer opens a candidate's ExamHall proctor-dashboard take-home and runs it against the mock API, which fails a seeded 20% of requests. She reloads the page 50 times: the incidents request failed on 10 loads, and 6 of the other 40 replies held no incidents. api.get resolves with the reply's list and rejects when the request fails. The incidents panel:

What does the incidents panel show across her 50 loads?

Code for this question
function IncidentsPanel() {  const [incidents, setIncidents] = useState(null);  useEffect(() => {    api.get('/incidents')      .then(setIncidents)      .catch(() => setIncidents([]));  }, []);  if (incidents === null) return <p>Loading incidents…</p>;  if (incidents.length === 0) return <p>No incidents reported</p>;  return <ul>{incidents.map((i) => <li key={i.id}>{i.title}</li>)}</ul>;}
Pick one

The 30-second version

A reviewer runs a take-home's error path before its happy path. Every request needs loading, data, empty and error screens; a failure stored as empty data shows a false “nothing here”. The API validates each field itself, config that varies per deploy comes from environment variables, setup is one command, and every file is reachable from a script.

Reviewers run the error path first: truthful screens for every request, server-side validation, config from the environment, one-command setup, no dead files.

How it works

Step 1 of 16

Error path first. The reviewer runs the app with requests failing, slow and empty before judging its features.

Analogy: A passenger lift's acceptance test before handover

The installer rides the car up and down: it works. The inspector cuts the mains, to see whether the display says “out of service” rather than going dark. She reads the display while the car travels and while no call waits, overloads the car to see it refuse and say why, and checks that the floors were set at commissioning, not in the firmware. Then she starts it from one labelled switch and follows the machine-room diagram.

At the lift's acceptance test In the take-home
The installer's ride up and down The happy path
“Out of service” on the display, not a dark panel An error screen, not a blank or false-empty one
The display while travelling, and with no call waiting The loading and empty screens
The car refusing an overload at the door The API checking each field
Floors set at commissioning, not in the firmware Config from the environment
One labelled switch; a diagram a stranger can follow One-command setup; every file reachable

Breaks down when: an inspector certifies against a written standard, while a take-home reviewer has none, and companies weigh these signals differently. Wrong inference: “there is one fixed checklist, so ticking it passes.”

In real terms: before you submit, run your own app against a failing mock and look at every screen.

Examples, run for real

EXH-2330 is a synthetic review of the ExamHall proctor-dashboard take-home. The reviewer reloads it 50 times against the mock API, which fails each request with probability 0.2 from a fixed seed. The candidates panel has no failure handler. The flags panel starts as an empty list and, on a failure, draws nothing. The re-submission, MR !2630, gives every panel one status value:

api.get(url).then((rows) => setState({ status: 'ready', rows }), () => setState({ status: 'error' }));
// 'loading' -> "Loading …"; 'error' -> "… could not load." + Retry; no rows -> its own message; else the list
EXH-2330 (as submitted) · seed 20261001 · 50 loads · each request fails with p = 0.2
  candidates replies: 30 with rows, 10 empty, 10 failed -> shows 30 list · 10 blank · 10 stuck on "Loading…"
  flags      replies: 26 with rows, 10 empty, 14 failed -> shows 26 list · 24 blank
  rejections nobody handled: 10
MR !2630 (one status per panel) · seed 20261001 · 50 loads · each request fails with p = 0.2
  candidates replies: 30 with rows, 10 empty, 10 failed -> shows 30 list · 10 "No candidates checked in yet" · 10 "Candidates could not load." + Retry
  flags      replies: 26 with rows, 10 empty, 14 failed -> shows 26 list · 14 "Flags could not load." + Retry · 10 "No flags raised"
  rejections nobody handled: 0

As submitted, none of the 24 failed requests produced a screen that said so. The candidates panel froze on its loading text after each failure. The flags panel was blank 24 times: a proctor cannot tell its 14 failures from its 10 quiet replies. After MR !2630 the same seed gives four distinct screens, and every failure says what failed.

A real reviewer may weigh these screens differently.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 6

A chat take-home's sidebar lists the user's conversations. To see a slow network, the reviewer holds back the mock's reply to /conversations; once she lets it go, it brings 3 conversations. api.get resolves with the reply's list and rejects when the request fails. The sidebar:

What does the sidebar show while the reply is held back?

Code for this question
function ConversationList() {  const [conversations, setConversations] = useState([]);  const [failed, setFailed] = useState(false);  useEffect(() => {    api.get('/conversations')      .then(setConversations)      .catch(() => setFailed(true));  }, []);  if (failed) return <p>Conversations could not load. <button>Retry</button></p>;  if (conversations.length === 0) return <p>No conversations yet</p>;  return <ul>{conversations.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;}
Pick one

Question 2 of 6

A chat take-home lets each user set quiet hours, when no notifications are sent. Its settings form refuses an end before a start. The reviewer reads its API handler:

Which check does her review ask the candidate to add?

Code for this question
app.put('/me/quiet-hours', (req, res) => {  const { from, until, note } = req.body;  if (Number.isNaN(Date.parse(from)) || Number.isNaN(Date.parse(until)))    return res.status(400).json({ error: 'from and until must be timestamps' });  if (typeof note !== 'string' || note.length > 80)    return res.status(400).json({ error: 'note must be text, at most 80 characters' });  quietHours.save(req.user.id, { from, until, note });  res.status(204).end();});
Pick one

Question 3 of 6

A chat take-home ships one build to two deploys, a staging app and the live app; the two differ only in their host name. Its src/config.js:

Under Twelve-Factor, which of the four must be read from the environment?

Code for this question
export const ROUTES = { '/': Inbox, '/c/:id': Conversation, '/settings': Settings };export const HANDLERS = { send: onSend, read: onRead };   // which function handles each eventexport const PAGE_SIZE = 50;                              // messages per screenexport const PUBLIC_URL = 'https://chat.example';         // used in invite links
Pick one

Question 4 of 6

A chat take-home's repository, each file with its imports:

package.json                  "start": "node server/main.js", "test": "node --test test/
server/main.js                import './http.js'
server/http.js                import './routes/messages.js'
server/routes/messages.js     import '../store.js', '../validate.js'
server/store.js               (no imports)
server/validate.js            (no imports)
server/export/transcript.js   import './pdf.js'
server/export/pdf.js          import './fonts.js'
server/export/fonts.js        (no imports)
test/messages.test.js         import '../server/routes/messages.js'

How many of these .js files are dead code that a stranger reads for nothing?

Pick one

Question 5 of 6

A chat take-home's message pane. The reviewer's mock has four cases: a reply held back (a slow network), a failed request, an empty conversation, and a conversation with messages. api.get resolves with the reply's list and rejects when the request fails. Four versions of the pane:

Which version gives each of the four cases its own screen, and a screen that is true to that case?

Code for this question
function Version1() {  const [state, setState] = useState({ status: 'loading' });  useEffect(() => {    api.get('/messages').then(      (rows) => setState(rows.length ? { status: 'ready', rows } : { status: 'error' }),      () => setState({ status: 'error' }));  }, []);  if (state.status === 'loading') return <p>Loading messages…</p>;  if (state.status === 'error') return <p>Messages could not load</p>;  return <ul>{state.rows.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;}function Version2() {  const [messages, setMessages] = useState([{ id: 'hello', text: 'Say hello to start the chat' }]);  const [failed, setFailed] = useState(false);  useEffect(() => { api.get('/messages').then(setMessages, () => setFailed(true)); }, []);  if (failed) return <p>Messages could not load</p>;  if (messages.length === 0) return <p>No messages yet</p>;  return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;}function Version3() {  const [messages, setMessages] = useState([]);  const [loading, setLoading] = useState(true);  const [failed, setFailed] = useState(false);  useEffect(() => {    api.get('/messages').then(setMessages, () => setFailed(true)).finally(() => setLoading(false));  }, []);  if (loading) return <p>Loading messages…</p>;  if (failed) return <p>Messages could not load</p>;  if (messages.length === 0) return <p>No messages yet</p>;  return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;}function Version4() {  const [messages, setMessages] = useState(null);  const [failed, setFailed] = useState(false);  useEffect(() => { api.get('/messages').then(setMessages, () => setFailed(true)); }, []);  if (failed) return <p>Messages could not load</p>;  if (!messages || messages.length === 0) return <p>Loading messages…</p>;  return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;}
Pick one

Question 6 of 6

A chat take-home exports a conversation as a PDF by calling execFile('wkhtmltopdf', …), a program installed system-wide on the candidate's laptop with Homebrew. The README's setup is one command, npm install && npm start, and the reviewer's machine has Node, npm and Homebrew.

Which way of making the PDF fits a twelve-factor setup?

Pick one

Go deeper

Pro tip

Before you push, start the app once with the mock failing every request and once with every reply held, and read each screen aloud.

Checked against the tools and docs as of 2026-10-01.