Study Index

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.

Length ~45 minMode answer, then reveal
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.
answer aloud → then expand

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.

SignalAsk yourself
ClarityDid the intro land in under 90s without rambling?
Trade-offsDid every technical answer include a "…and here's when it bites"?
DepthCould you go one layer deeper when pushed, or did you top out?
StoriesDid you lead with tension and decision, not a feature list?
CodeDid you narrate edge cases while typing, not code in silence?
ComposureWhen 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.