Skip to main content

Frontend Coding Interview Prep with JavaScript and React

How to prepare for frontend coding interviews, from core JavaScript behaviour to building a small React feature live, with worked examples and the signals interviewers look for.

By Vikas D11 min readUpdated 30 July 2026

Software engineer with ~6 years experience, MSc Computer Science (in progress), London

Connect on LinkedIn
frontendjavascriptreact
On this page

What frontend interviews actually test

Frontend coding interviews are not algorithm puzzles with a React logo on top. They test whether you understand how JavaScript really behaves, whether you can build a small interactive feature without fighting the framework, and whether you make sensible calls about state, rendering, and accessibility under time pressure. The same person who can reverse a linked list can still struggle to explain why a stale closure broke an event handler, and that gap is exactly what these rounds find.

There are three common formats, and companies mix them freely:

RoundWhat it looks likeWhat is really being measured
Core JavaScriptPredict output, explain this, reason about the event loopDo you understand the runtime, not just the syntax
BuildImplement a typeahead, star rating, or todo app liveCan you model state and async without creating bugs
UtilityWrite debounce, an event emitter, or a promise pool from scratchDo you know the language well enough to build its tools

A useful mental model: the interviewer is not grading the final artefact, they are grading the decisions you make on the way there. A half-finished feature built with clear reasoning beats a complete one built by guessing. Treat every choice, even small ones like where state lives, as something to say out loud.

The single biggest predictor of a strong result is not raw speed. It is whether the interviewer can follow your thinking well enough to help you when you stall. Silence removes their ability to do that.

The signals that separate a hire from a pass

Before the techniques, it helps to know what the two ends of the spectrum sound like, because the difference is rarely about knowing more APIs.

SignalWeak candidateStrong candidate
Starting a buildTypes immediately, refactors three timesSpends 60 seconds naming the state and the events first
Hitting a bugGoes quiet, adds random changesNarrates a hypothesis, then tests it
Async workFires a fetch in an effect with no cleanupMentions cancellation and race conditions unprompted
OptimisationWraps everything in memo reflexivelySays "I would measure first" and explains the tradeoff
Edge casesBuilds only the happy pathCalls out empty, loading, and error states early

None of this requires senior experience. It requires treating the interview as a collaboration rather than a test you have to survive.

Get the language fundamentals solid

Before any framework, the interviewer wants to know that you understand the runtime. The topics that come up again and again are closures, the event loop, this binding, and how promises schedule work.

A classic trap is the loop with var. Interviewers usually write it with setTimeout, but the real bug is scope: because var is function scoped, every closure created in the loop shares one binding and reads its final value. You can prove that without any timer by collecting the closures and calling them:

js
const withVar = [];
for (var i = 0; i < 3; i++) withVar.push(() => i);
console.log(withVar.map((f) => f())); // [3,3,3] - one shared i, left at 3

const withLet = [];
for (let j = 0; j < 3; j++) withLet.push(() => j);
console.log(withLet.map((f) => f())); // [0,1,2] - each iteration gets its own j

Swapping var for let gives each iteration its own binding, which is why the second loop reads 0, 1, 2. The classic setTimeout(() => console.log(i), 0) version prints 3, 3, 3 for exactly this reason: the callbacks run after the loop finishes, by which point the single var i is already 3. Talking through the scope, rather than reciting the output, is what the round rewards. If the interviewer pushes further, show that you can fix the var version without let too, by capturing the value in an IIFE or by passing it as a third argument to setTimeout.

Microtasks versus macrotasks is the next layer up. When promises and timers are mixed, the order is not obvious, and interviewers love to probe it. The canonical question mixes a console.log, a setTimeout(..., 0), and a Promise.resolve().then(...), and the answer is start, end, promise, timeout. The mechanism is easier to hold onto if you model the three queues explicitly:

js
const output = [];
const microtasks = []; // Promise.resolve().then(...) lands here
const macrotasks = []; // setTimeout(..., 0) lands here

output.push("start");
macrotasks.push(() => output.push("timeout"));
microtasks.push(() => output.push("promise"));
output.push("end");

while (microtasks.length) microtasks.shift()(); // drain microtasks first
while (macrotasks.length) macrotasks.shift()(); // then the next macrotask
console.log(output.join(", ")); // start, end, promise, timeout

The key sentence is: synchronous code runs first, then the microtask queue drains completely (the promise callbacks), then the next macrotask runs (the timer). The MDN microtask guide puts it precisely: a microtask runs "after the function or program which created it exits and only if the JavaScript execution stack is empty, but before returning control to the event loop." If you can say that cleanly, you have answered most of what this round is checking.

this binding is the other reliable topic. Know how a plain function call, a method call, an arrow function, bind, and new each resolve this. The trap to watch for is passing a method as a callback and losing its receiver, then fixing it with an arrow wrapper or bind.

Practise the small utilities

Implementing a utility from scratch is a favourite because it is quick to set up and reveals a lot. Debounce is the most common. Practise writing it without looking, including the part where you preserve this and the arguments.

js
function debounce(fn, delayMs) {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delayMs);
  };
}

// A manual clock so we can watch a burst collapse into one call (no real timers).
let now = 0;
let scheduled = null;
const setTimeout = (fn, ms) => { scheduled = { fn, at: now + ms }; };
const clearTimeout = () => { scheduled = null; };
const tick = (ms) => { now += ms; if (scheduled && now >= scheduled.at) { const f = scheduled.fn; scheduled = null; f(); } };

let runs = 0;
const search = debounce(() => { runs++; }, 200);
search(); search(); search(); // three rapid keystrokes
tick(200);                     // let the clock pass the delay
console.log(runs);             // 1 - only the last call in the burst runs

The follow-up almost always raises the bar. A senior version of debounce supports a leading edge (fire immediately, then suppress) and a cancel method. You do not need to write all of it under pressure, but say that you know those variations exist and when each is appropriate. Search-as-you-type usually wants trailing debounce; a "save" button that must not double-submit usually wants leading.

Other utilities worth having in your fingers:

  • throttle, and be ready to explain how it differs from debounce. Throttle guarantees a call at a steady rate; debounce waits for quiet.
  • a simple EventEmitter with on, off, and emit. The off method is where people get tripped up, so practise removing the right listener.
  • deepClone, with an honest note that structuredClone exists in modern environments and handles cycles, while a naive recursive version does not.
  • a promise pool that runs N promises with limited concurrency. This one separates strong candidates because it forces you to reason about scheduling.

For each, be ready to discuss edge cases out loud: what happens if the wrapped function throws, what happens on rapid repeated calls, and how you would test it. A short verbal test plan ("I would assert it only fires once after a burst, and that arguments are forwarded") is a strong signal that costs ten seconds.

Building a React feature live: a worked example

The build round is where most of the time goes. You will be asked to make something interactive, such as a typeahead search, a star rating, a paginated list, or a small todo app. Rather than list tips in the abstract, here is how a strong candidate would actually walk through a typeahead.

Step 1: Name the state and events before typing. Out loud: "I need the query string and the results array. Loading is derived from whether a request is in flight. Suggestions visible is derived from results plus a focus flag. I have two events: the user types, and the user selects a result." This 60-second narration is the highest-leverage thing you can do.

Step 2: Model the data minimally. Decide what state you truly need and keep it small. Derive everything you can rather than storing it. For a typeahead, the state is the query and the results; the loading flag and the visible suggestions fall out of those.

JSX
function useSearch(query) {
  const [results, setResults] = useState([]);
  useEffect(() => {
    if (!query) {
      setResults([]);
      return;
    }
    const controller = new AbortController();
    fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
      .then((r) => r.json())
      .then(setResults)
      .catch((err) => {
        if (err.name !== "AbortError") setResults([]);
      });
    return () => controller.abort();
  }, [query]);
  return results;
}

Step 3: Call out the two bugs this design avoids before you are asked. The cleanup calls AbortController.abort() on the in-flight request, so an old slow response cannot overwrite a newer one. That is the race condition most candidates ship without noticing. The empty-query branch resets results so stale suggestions do not linger. Saying "I am aborting here specifically to prevent a stale response winning a race" is the line that separates a mid-level answer from a senior one.

Step 4: Add debouncing where it belongs. The query should be debounced before it reaches the network, not the keystrokes themselves. Mention that you would debounce the value with a small hook so typing stays responsive while requests stay cheap. If you have time, wire it; if not, leave a clear comment and say what it would do.

Step 5: Handle the states a real user hits. Wire the empty, loading, error, and no-results cases. Add keyboard support: arrow keys to move through suggestions, Enter to select, Escape to close. This is where you earn the accessibility signal, covered below.

This five-step shape generalises. For a star rating, the state is the selected value and a hovered value; the rest is derived. For a paginated table, the state is the page number and page size; the rows are fetched from those. Naming the minimal state first, then deriving, is the move that makes the whole round calmer.

Show that you understand rendering

Interviewers often probe whether you know why a component re-renders and when that matters. You do not need to reach for memoisation everywhere, and saying so is itself a good answer. Reach for it when you have measured a real cost.

Be ready to explain the dependency array of useEffect, why an object or function in that array can cause an effect to run every render, and how useCallback or useMemo stabilise those references. The trap is creating a new object literal or inline function and passing it as a dependency, which silently defeats the comparison and re-runs the effect on every render.

Be honest that premature optimisation with memo can make code harder to read for no benefit, and can even cost more than it saves once you add the comparison work and the stabilising hooks around it. The senior signal is knowing the tradeoff, not reciting the API. A clean answer sounds like: "I would reach for useMemo here only if this list got large enough to measure a delay; below that it is noise." The React useMemo reference makes the same call: memoisation is only worth adding when a calculation is "noticeably slow" or feeds a memo-wrapped child, and otherwise it just costs readability.

Keys in lists come up constantly. Explain why using an array index as a key breaks when items are inserted or reordered, because React reuses the wrong DOM node and component state attaches to the wrong row, and why a stable id fixes it. The React docs on rendering lists are blunt that "index as a key often leads to subtle and confusing bugs." A concrete example, like an input inside each row keeping the wrong value after a delete, makes the point land.

Do not forget accessibility and the small details

Frontend rounds reward candidates who remember that real users interact with the thing they are building. As you build, narrate the details that production code needs.

  • Make interactive elements keyboard reachable and use the right semantic element: a button for an action rather than a clickable div. A real button gives you focus, Enter and Space handling, and the correct role for free.
  • Handle the empty, loading, and error states, not only the happy path.
  • Manage focus when a dialog opens or results appear, so screen reader and keyboard users are not lost. Return focus to the trigger when the dialog closes.
  • For a typeahead specifically, the combobox pattern wants aria-expanded, aria-activedescendant, and arrow-key navigation. You do not need to recite every attribute, but naming the pattern shows depth. The WAI-ARIA Authoring Practices is the canonical reference if you want to study one source.

Even a sentence or two about accessibility sets you apart, because many candidates skip it entirely.

Where the same prompt is weighted differently

The same typeahead is not scored the same everywhere, and the difference is the team you are interviewing with, not a rung on a ladder. Read the posting and the interviewer's role before you decide where to spend your minutes.

Team you are interviewing withWhere the round's weight sitsWhat to over-invest in
Product or feature teamThe build round: state modelling, the states a real user hits, basic accessibilityGetting something interactive on screen, then narrating the empty, loading, and error cases
Design systems or platformComponent API design, generic reusable props, render behaviourClean prop contracts, why a component re-renders, controlled versus uncontrolled inputs
Full-stack splitA trimmed frontend round plus a data or API questionNot over-indexing on React trivia; being ready to move to a query or an endpoint

Seniority shifts the same prompt along a second axis: not what you build, but how much you frame it. A junior candidate is expected to get a working feature out with minimal state and to think aloud. A senior candidate is expected to name the failure modes before they appear, reason about the tradeoff of memoising versus measuring, and say what they would push back on in the requirements. Both build the same typeahead; the senior one keeps saying why. For the question banks tied to each track, see the frontend engineer interview questions and the broader full-stack engineer interview questions.

Where frontend rounds are quietly lost

  • Storing derived data in state, then fighting to keep it in sync. If a value can be computed from other state, compute it.
  • Forgetting to clean up effects, which leaks listeners or lets stale responses win the race.
  • Reaching for a global state library when local state or a single context would do.
  • Wrapping everything in memo and useCallback before measuring, which adds noise and can slow things down.
  • Going silent. Talk through your plan and your tradeoffs so the interviewer can redirect you early, while it still helps.
  • Over-engineering the first pass. Build the simplest thing that works, get it on screen, then improve. A blank screen at the halfway mark is the worst outcome.

How to practise

Build five small features end to end on a timer: a typeahead, a star rating, an accordion, a paginated table, and a todo list with filtering. Then implement five utilities from scratch: debounce, throttle, an event emitter, deep clone, and a promise pool. After each attempt, review three questions: was your state minimal, was your async cancellable, and was your component accessible. Keep a short log of bugs you hit so you stop repeating them.

A realistic schedule for someone working full time is three weeks: week one on language fundamentals and utilities, week two on build features, week three on mock rounds against the clock with a friend or a recording. Recording yourself is uncomfortable but it surfaces the silent stretches faster than anything else. A few weeks of this makes the build round feel routine, which is exactly the calm you want when the clock is running.

FAQ

Do I need to know data structures and algorithms for a frontend role? Usually a lighter dose than a generalist backend loop, but not zero. Expect maps, sets, recursion over trees (the DOM is a tree), and the occasional string problem. The utility round is closer to applied DSA than to LeetCode-hard puzzles.

Can I use a UI library in the build round? Ask. Many interviewers want plain React so they can see your state and event handling. Some allow a component library but will then probe what it is doing under the hood. Either way, do not let a library hide the logic they are trying to assess.

TypeScript or JavaScript? Use whichever you are faster in unless the company specifies. If you are comfortable, TypeScript can be a quiet positive because it forces you to name your data shapes, which is exactly the modelling they want to see. Do not pick it if fighting the types will slow you down.

How much should I talk? Enough that the interviewer always knows your current plan and your next step, but not a running monologue over every keystroke. Narrate decisions and tradeoffs; stay quiet during routine typing.

What if I do not finish? Common and survivable. Finish a clear slice, then state out loud what remains and how you would build it. A well-reasoned partial solution with a credible plan often scores above a rushed, buggy complete one.

Sources

Where to take this next

Refresh role-specific skills
Recommended
Short, focused courses on the exact frameworks and tools the role uses day-to-day.
Try LinkedIn Learning

An external resource we recommend. LinkedIn Learning is not affiliated with us and we earn nothing from this link.

Continue your prep

Pair this guide with role-specific question sets and sample answer outlines.