Interview Prep · Senior Frontend · Self-Drill
Round 1 — Mock Interview
A timed, phased dry run. Read each question, answer out loud first (or record yourself), then expand to see what a strong answer hits and how the interviewer will push. Full model answers live in the Round 1 note.
Don't read the reveals first — that's rehearsing recognition, not recall. Say the answer aloud, then check
yourself. Grade on the "state it, then the trade-off" habit: if your answer had no "…and
here's when it bites," it wasn't a senior answer yet.
The shape of a real 45–60 min round-1 screen:
~8m intro
~10m JS/TS
~12m React
~8m migrations
~10m live build
~5m your questions
1
Intro & experience ~8 min
Q1.Tell me about yourself.
Hits: present → past → why-here in ~60–90s. Now (5 yrs, Bluesapling, BestWe = team-effectiveness
platform, React/TS), one proud thread (Genius streaming), what you care about (architecture & performance),
why this role. Trap: narrating the whole résumé chronologically. Follow-up: "Only one company?" →
frame it as depth & ownership, then pivot to the range within BestWe. See §1.1.
Q2.Walk me through a project you're proud of.
Hits: lead with the tension, not a feature list — "Genius had to work in four surfaces with
different UI but identical behaviour." Then the design (one shared SSE hook + core/wrapper split), then the
payoff (reuse, consistent UX, killed duplication). Follow-up: "Why SSE over WebSockets?" and "Where does
the shared logic end and per-surface UI begin?" — have both ready. See §1.3.
Q3.Tell me about the hardest bug you've debugged.
Hits: pick a surprising root cause — the stream-executor races are perfect (a dead stream's
watchdog killing its successor; abort-vs-timeout ambiguity). Walk the method: reproduce → isolate →
hypothesis → confirm → fix → add a guard. Trap: telling the bug without the method. See §1.4-B.
Q4.Describe a technical decision you had to defend.
Hits: the active-round scoping call — Context + localStorage over the codebase's default
Redux, because the requirement was "local, only here, only now," and global state you must remember to tear down
is a leak. "Smallest scope that satisfies; the scope is the cleanup." Show you weighed
the familiar option and rejected it on merit. See §1.4-A.
Q5.Why are you looking to move?
Hits: forward-facing — scope, ownership, a new domain, setting standards. One or two
sentences, then pivot to what excites you about this team. Trap: criticising a current/past
employer, even mildly.
2
JavaScript & TypeScript ~10 min
Q6.What's a closure, and where does it bite?
Hits: function + the lexical scope it was created in, kept alive after the outer returns.
Uses: data privacy, factories, hooks. Bite: closures capture variables, not values — the
for (var i…) setTimeout logging 3 3 3; fix with let. See §2.Q7.Walk the event loop: what logs first — a resolved Promise or setTimeout(0)?
Hits: the Promise. Sync stack drains → all microtasks (Promise callbacks) →
one macrotask (
setTimeout) → microtasks again. Consequence to name: a runaway
microtask chain starves rendering. See §2.Q8.Debounce vs throttle — when each?
Hits: debounce = wait for activity to stop (search-as-you-type); throttle = at most
once per interval during continuous activity (scroll). "Debounce waits for quiet; throttle rate-limits." Be ready
to write debounce in ~5 lines. See §2.
Q9.any vs unknown vs never?
Hits:
any = off switch (avoid); unknown = safe top type, must narrow
before use; never = bottom type, return of an always-throw, and the exhaustiveness-check tool in a
switch. See §3.Q10.How would you model loading/success/error state in TS?
Hits: a discriminated union with a literal
status tag, so a
switch narrows each case and a never default catches unhandled variants at compile
time. Mention types are erased at runtime, so validate data crossing trust boundaries. See §3.3
React depth ~12 min
Q11.What causes a component to re-render?
Hits: its own state/reducer update, its parent re-rendering, or a consumed context value
changing. Props changing = "the parent re-rendered." Nuance: re-render ≠ DOM change — React still diffs;
you optimise the wasted render work. See §4.
Q12.Why do keys matter, and what's wrong with the array index?
Hits: keys give list items stable identity so React can match/reorder and preserve state.
Index-as-key breaks on reorder/filter — state and inputs attach to the wrong row. See §4.
Q13.Common useEffect mistakes?
Hits: missing deps → stale closure; no cleanup → leaks/duplicate subscriptions; setting state
after unmount → use
AbortController for fetches. StrictMode's dev double-run exists to surface
missing cleanup. Trap: disabling the deps lint rule instead of fixing the dep. See §4.Q14.When should you not use useMemo/useCallback?
Hits: by default. They cost memory + comparisons; memoising a callback is pointless unless the
child is
React.memo'd. Add them when you've measured a real re-render cost or need stable
identity for a dep array. "Measure before memo." See §4.Q15.Why can Context hurt performance, and how do you fix it?
Hits: every consumer re-renders when the value changes, and a fresh object literal each
render counts as changed. Fixes: memoise the value, split contexts (state vs dispatch, or by domain), keep
fast-changing state out of a wide context, or use a store with selectors. See §4.
Q16.How would you stream AI responses in React — SSE, WebSocket, or fetch streams?
Hits: SSE for one-way server→client token streaming (plain HTTP, auto-reconnect); WebSockets
only for true bidirectional. Keep the transport in a custom hook that owns request/stream/routing, batch
incremental updates, clean up with
AbortController, and handle partial streams + retries + resume.
This is your Genius design — see §4 & §1.3.4
Migrations & tooling ~8 min
Q17.What actually changed between React 16 and 18?
Hits: automatic batching (across promises/timeouts/native handlers), concurrent rendering
(
useTransition/useDeferredValue), the new createRoot API, Suspense SSR
streaming, StrictMode effect double-invoke. Migration pain: batching + concurrency change when
updates flush → ordering bugs in old sync-ish code. See §5.3.Q18.Why did you move off webpack/CRA to Vite? How is it faster?
Hits: dev serves native ESM unbundled + esbuild pre-bundles node_modules once, so start/rebuild
stop scaling with app size; prod bundles with Rollup/Rolldown. Name the trade-offs: different dev/prod
engines, and esbuild doesn't type-check (so you add a checker plugin +
isolatedModules). See §5.1-B.Q19.How do you run a big migration without freezing the product?
Hits: incremental — build a bridge/compat layer so old and new coexist (react-router
v5-compat), keep the branch merge-able from main, change one axis at a time (Node bump ≠ React bump), take
debt knowingly and visibly (ratchets not gates), and rebuild critical-path coverage before
deleting the old test suite. "Version bump is 1%." See §5.2.
Q20.What are moduleResolution: "bundler" and isolatedModules?
Hits:
bundler resolves imports the way a bundler does (no required extensions,
respects exports) vs node16 emulating Node's runtime rules. isolatedModules
guarantees each file transpiles alone with no cross-file type info — which is how esbuild is fast, and why you
must export type when re-exporting types. See §5.3.5
Live build ~10 min
Pick one and code it in a shared editor — narrate as you go They're grading
clean, typed, correct code and your talk track (edge cases, API design), not cleverness.
Q21.Write a typed wrapper around localStorage.
Narrate: "feature-detect first — it throws in private mode and doesn't exist in SSR",
"JSON parse/stringify in try/catch so a corrupt value can't crash the app", "always return a fallback so callers
never get
null", "keys typed against a schema so you can't read a value at the wrong type." Full
implementation in §7.1.Q22.Write can(user, action, resource) for role-based permissions.
Narrate: role → allowed-action set, with a function rule for ownership cases (an
editor deletes only their own doc). Mention RBAC vs ABAC, and that permissions are a UX affordance, not
security — the server must re-check. Full implementation in §7.2.
Q23.Implement debounce (then extend to cancel/flush).
Narrate: a closed-over timer,
clearTimeout on each call, apply to
preserve this/args. Extension: expose .cancel() (clear the timer) and .flush()
(fire immediately). Full implementation in §2.6
Your questions ~5 min
Q24.What questions do you have for us?
Always have 2–3. "Biggest frontend pain point you'd want a senior to fix first?" · "How do
technical decisions get made and code get reviewed?" · "What does success look like at 3 and 6 months?" ·
"What's your testing/release confidence like?" Asking nothing reads as disengaged. See §9.
7
Self-debrief
After the run, score yourself honestly on each — anything under a 4 goes on tonight's revision list.
| Signal | Ask yourself |
|---|---|
| Clarity | Did the intro land in under 90s without rambling? |
| Trade-offs | Did every technical answer include a "…and here's when it bites"? |
| Depth | Could you go one layer deeper when pushed, or did you top out? |
| Stories | Did you lead with tension and decision, not a feature list? |
| Code | Did you narrate edge cases while typing, not code in silence? |
| Composure | When you didn't know something, did you reason aloud instead of freezing? |
If you blank on something Say what you do know and reason toward it
out loud — "I'd reach for X because…". Interviewers grade the thinking, not just the recall. A calm "let me reason
through it" beats a memorised answer delivered nervously.