Take-homes and pairing sessions test the everyday job more directly than any whiteboard: can you read a brief, make sensible decisions, deliver something that works, and explain it. They are also where seniors most often lose points by doing too much. The reviewer has a rubric; this page is that rubric turned into a method.
useFetch, server/api) so the reviewer sees idioms, not inventions. No premature abstractions; one duplication beats a wrong abstraction.pnpm install && pnpm dev) and match the brief; code clarity and naming; correctness on edge cases; typed code; tests on logic that can break; accessibility and semantics of the UI; error, loading and empty states; the README's reasoning; commit history; and the honesty of "what I would do next". Over-engineering, ignored requirements, any everywhere, no error handling and a broken install are the common fails.pnpm test.feat: list todos with filter in URL, fix: keep focus on the input after adding, test: cover empty and error states. A reviewer reads the history to see how you work; squashing everything into "final" hides that.key; watch without cleanup; async setup without await of a composable (context loss); fetching in onMounted in a Nuxt page (no SSR data); useFetch without a key inside a loop; reading window at module scope; a store created at module scope on the server; v-html with user content; missing aria-labels; a promise not awaited; Array.shift in a loop; == comparisons; catching and swallowing errors; secrets in runtimeConfig.public. Say what breaks, when, and how to fix it.A README skeleton that hits every rubric item:
# Orders dashboard (take-home)
## Run
pnpm install && pnpm dev # http://localhost:3000
pnpm test # Vitest: unit + component tests
pnpm typecheck && pnpm lint
## What it does
Lists orders from the provided JSON API with filtering by status (kept in the URL), sorting, and a detail drawer.
Loading, empty and error states are handled; the list is keyboard-navigable and announced to screen readers.
## Assumptions
- The API is read-only for this exercise; mutations are out of scope.
- "Recent" means the last 30 days by `createdAt` in the viewer's time zone.
- The dataset fits in memory (< 5k rows); virtualisation would be the next step beyond that.
## Architecture
Nuxt 4 with a thin server route (`server/api/orders.get.ts`) proxying and validating the upstream API, so the page uses
`useFetch` with a key and gets SSR data. Filter state lives in the URL; UI state in component refs; no global store
was needed. Formatting goes through `Intl`. Components: `OrdersTable`, `OrderDrawer`, `StatusFilter`.
## Decisions and trade-offs
- Sorting client-side: the dataset is small; server sorting would be the choice with pagination.
- No Pinia: shared state is limited to the URL and the fetched data; a store would add indirection without benefit.
- Nuxt UI for primitives: accessible table/drawer out of the box; a custom design system was out of scope.
## Tests
- `orders.filter.spec.ts` — filtering/sorting logic incl. empty input and equal dates.
- `OrdersTable.spec.ts` — renders rows, announces sort, opens the drawer with keyboard.
## Not done / next steps
- Virtualised rows for large datasets; server-side pagination.
- E2E test with Playwright; visual regression for the drawer.
- Optimistic status updates once the API supports writes.
Time spent: ~5 h (1 h reading/planning, 2.5 h build, 1 h tests/a11y, 0.5 h README).
A code-review drill with the answer you should give:
<script setup lang="ts">
const props = defineProps<{ items: Item[] }>()
const cart = reactive({ lines: [] as Line[] }) // module-scope store lives in a separate file in the real snippet
onMounted(async () => {
const data = await $fetch('/api/prices') // 1
props.items.forEach(i => (i.price = data[i.id])) // 2
})
const total = ref(0)
watch(() => cart.lines, () => { total.value = cart.lines.reduce((s, l) => s + l.qty * l.price, 0) }) // 3
</script>
<template>
<li v-for="(item, index) in items" :key="index"> <!-- 4 -->
<div v-html="item.description" /> <!-- 5 -->
<button @click="cart.lines.push({ ...item, qty: 1 })">Add</button> <!-- 6 -->
</li>
</template>
1. Fetching in onMounted in a Nuxt page loses SSR and shows a flash; use useFetch at setup with a key.
2. Mutating props (and the parent's objects) breaks one-way data flow; compute a derived list or emit.
3. watch on `() => cart.lines` is not deep and the derived value should be a computed(); also no cleanup concern here, but derived state as ref+watch is a smell.
4. index as key breaks state when items reorder or are removed; use item.id.
5. v-html with server or user-provided description is an XSS vector; render text or sanitise.
6. No accessible name beyond "Add" repeated N times; add aria-label with the item name; also pushing the item object itself shares references with the list.
Plus: no loading/error state; the store snippet at module scope would leak between users in SSR.
A refactoring drill outline, as you would narrate it:
Goal "This 300-line component fetches, filters, sorts and renders; make it testable."
Step 0 Add a characterisation test: mount, assert rows and the sort behaviour as they are today.
Step 1 Extract pure logic: filterOrders(), sortOrders() into utils with unit tests (green).
Step 2 Extract a composable useOrders() holding fetch + derived state; component becomes a template with props/slots.
Step 3 Split the template: OrdersTable, OrdersFilters; pass data down, events up.
Step 4 Remove dead code and the duplicate formatting; run typecheck, lint, tests.
Stop The goal was testability; the visual redesign someone mentions is a separate ticket.
A reviewer who cannot start the app in two minutes stops reading. Test the install from a fresh clone (or a fresh directory) with the pinned Node version before submitting, and make pnpm dev and pnpm test the only commands needed.
When the interviewer suggests a different approach, they are testing collaboration as much as the idea. Consider it out loud, adopt it or explain briefly why not, and move on; ten minutes of debate is a failed session even if you were right.
"I read the brief twice and list the explicit requirements, the implicit ones and the ambiguities, which I resolve with stated assumptions in the README. Then I time-box: a working, idiomatic core in the first half, then correctness on edges, tests on the logic that can break, accessibility basics, and the README, with extras only if time remains. The architecture stays proportional to the problem, using the framework's conventions so the reviewer sees idioms rather than inventions. Commits are small and explain why. The README covers how to run and test it, the architecture, decisions with the alternatives I considered, what I left out and what I would do next, and the time I spent. Before submitting I install and run it from a clean clone."
Nothing yet. Notes filed here show up on this page and the section overview.
Algorithms at frontend depth
The algorithmic knowledge a senior frontend interview actually tests — complexity reasoning, hash maps and sets, two pointers and sliding windows, stacks and queues, trees and DOM traversal, recursion versus iteration, binary search, tries for autocomplete, light dynamic programming for fuzzy matching — with JavaScript implementations, the frontend situations each one shows up in, and how to talk about them.
From my notes
Questions and flashcards written from your own study notes, each linking back to the note it came from, with a fact check of every note. Questions from your Important note are marked and trained first.