JavaScript interview preparation by experience level

JavaScript Interview Questions for Frontend Developers

Practical code, output questions and real frontend production scenarios—organised by the experience level an interviewer expects you to demonstrate.

0–2 years

JavaScript questions for 0–2 years of experience

Focus on predictable language behaviour and the async basics used in everyday frontend work.

01

How do var, let and const differ?

Short interview answer

var is function-scoped and can be redeclared. let and const are block-scoped and cannot be used before their declaration. const prevents reassignment of the binding, but an object held by const can still be mutated.

Code example

for (var i = 0; i < 3; i += 1) {
  setTimeout(() => console.log(i), 0);
}

Expected output

3
3
3

Why it happens

All callbacks close over the same function-scoped i. By the time they run, the loop has finished and i is 3. Replacing var with let creates a new binding for each iteration, producing 0, 1, 2.

What the interviewer is testing

Block scope, closure timing and whether you can predict a common loop bug.

02

What is the difference between == and ===?

Short interview answer

=== compares type and value without conversion. == applies coercion first, so values of different types can compare as equal. Prefer === unless you have a deliberate, well-understood coercion case.

Code example

console.log(0 == false);
console.log(0 === false);
console.log(null == undefined);

Expected output

true
false
true

Why it happens

Loose equality converts 0 and false to comparable numeric values, and it has a special rule for null and undefined. Strict equality performs none of those conversions.

What the interviewer is testing

Whether you understand coercion well enough to avoid accidental conditions.

03

What are scope and the scope chain?

Short interview answer

Scope decides where a variable can be accessed. When JavaScript cannot find a name in the current lexical scope, it searches each outer scope until it finds the name or reaches the global scope.

Code example

const team = "frontend";

function describe() {
  const level = "junior";
  return team + " " + level;
}

Why it happens

describe can read both its local level and the outer team binding. Code outside describe cannot read level.

What the interviewer is testing

Lexical scope, name lookup and your ability to explain variable visibility.

04

What is a Promise, and how does async/await use it?

Short interview answer

A Promise represents a future success or failure. An async function always returns a Promise; await pauses that function until the Promise settles without blocking the browser's main thread.

Code example

async function loadUser() {
  try {
    const response = await fetch("/api/user");
    if (!response.ok) throw new Error("Request failed");
    return await response.json();
  } catch (error) {
    console.error(error);
  }
}

What the interviewer is testing

Basic async control flow, HTTP error handling and the difference between pausing a function and blocking the UI.

3–5 years

JavaScript questions for 3–5 years of experience

Expect practical questions about functions, object identity and the browser's execution order.

05

What is a closure? Show a practical use.

Short interview answer

A closure is a function that retains access to variables from the scope where it was created. It is useful when each function instance needs private state.

Code example

function createCounter() {
  let count = 0;
  return () => ++count;
}

const next = createCounter();
console.log(next());
console.log(next());

Expected output

1
2

Why it happens

The returned function keeps count reachable after createCounter has finished. Another call to createCounter would create an independent count.

What the interviewer is testing

Lexical scope, retained state and whether you can connect closures to real application code.

Senior follow-up: How can a closure accidentally retain a large object and contribute to a memory leak?

06

How does this differ in regular and arrow functions?

Short interview answer

A regular function receives this from how it is called. An arrow function does not create its own this; it captures this from the surrounding scope. That makes arrows useful for callbacks but usually unsuitable as object methods that need their receiver.

Code example

const user = {
  name: "Ada",
  regular() { return this.name; },
  arrow: () => this.name,
};

Why it happens

user.regular() receives user as its receiver. The arrow was created in the surrounding scope, so calling it through user does not rebind this.

What the interviewer is testing

Call-site binding and whether you choose function forms intentionally.

07

What is the output order for Promise and setTimeout callbacks?

Short interview answer

Synchronous statements run first, then queued Promise microtasks, then timer tasks.

Code example

console.log("A");

setTimeout(() => console.log("B"), 0);
Promise.resolve().then(() => console.log("C"));

console.log("D");

Expected output

A
D
C
B

Why it happens

A and D are synchronous. The resolved Promise queues C as a microtask. The timer queues B as a task (often called a macrotask), and microtasks are drained before the next task.

What the interviewer is testing

The event loop, synchronous execution, the microtask queue and the task queue.

Senior follow-up: What happens to rendering if a microtask continually queues another microtask?

08

What is the difference between a shallow copy and reference mutation?

Short interview answer

A spread copy creates a new top-level object, but nested objects still share references. Updating a shared nested object can therefore change both the original and the copy.

Code example

const original = { profile: { name: "Ada" } };
const copy = { ...original };

copy.profile.name = "Grace";
console.log(original.profile.name);

Expected output

Grace

Why it happens

Only the outer object was copied. original.profile and copy.profile still point to the same nested object. Copy each changed level when an immutable update is required.

What the interviewer is testing

Object identity, immutable state updates and bugs caused by shared references.

JavaScript assessment

Think you're ready?

Take a 20-question JavaScript assessment calibrated to your experience level.

Choose your question style:

MCQ · Input / Output · Multi-select · Mixed

Test My JavaScript Readiness →
6–8 years

JavaScript questions for 6–8 years of experience

These scenarios test debugging discipline, cleanup and reliable async behaviour in production interfaces.

09

How do you stop an older API response from overwriting newer data?

Short interview answer

Cancel the previous request with AbortController when starting the next one, or attach a monotonically increasing request ID and only commit the latest response. Treat cancellation separately from a real failure.

Code example

let activeController;

async function search(query) {
  activeController?.abort();
  activeController = new AbortController();
  const response = await fetch("/search?q=" + encodeURIComponent(query), {
    signal: activeController.signal,
  });
  return response.json();
}

What the interviewer is testing

Race conditions, cancellation and protecting UI state from out-of-order work.

Senior follow-up: When would ignoring stale responses be safer than cancelling the underlying request?

10

What causes memory leaks in a long-running single-page application?

Short interview answer

Common causes are listeners, timers, observers, subscriptions and detached DOM nodes that remain referenced after a screen is removed. Unbounded caches and closures retaining large data are other frequent sources.

Why it happens

Garbage collection only removes unreachable objects. A forgotten listener or cache can keep an entire object graph reachable even when the interface no longer uses it.

What the interviewer is testing

Lifecycle cleanup, reachability and whether you verify a leak with memory snapshots rather than guessing.

Senior follow-up: Which retained-object path would you inspect first in a heap snapshot?

11

When would you debounce or throttle an event?

Short interview answer

Debounce when only the final action matters after activity stops, such as a search query. Throttle when updates should continue at a controlled rate during activity, such as scroll-linked measurements.

Why it happens

Both reduce call frequency, but they preserve different user-visible behaviour. The right delay depends on the interaction, not a universal number.

What the interviewer is testing

Event-rate control, UX trade-offs, cancellation and leading/trailing execution choices.

12

How do you design error handling for a multi-step async flow?

Short interview answer

Define which failures are retryable, which steps can succeed independently and where compensation is needed. Preserve the original error for diagnostics, show an actionable user message, and avoid retrying non-idempotent operations blindly.

Code example

const results = await Promise.allSettled([
  saveProfile(profile),
  uploadAvatar(file),
]);

const failed = results.filter((result) => result.status === "rejected");

Why it happens

allSettled is useful when partial success is valid. Promise.all is better when any failed dependency makes the complete result unusable.

What the interviewer is testing

Failure boundaries, retry safety, partial success and observable errors.

9–12+ years

JavaScript questions for 9–12+ years of experience

Senior answers should identify the failure mode, protect correctness and explain how the fix will be measured.

13

Multiple requests receive 401 at once. How do you avoid multiple token-refresh calls?

Short interview answer

Keep one shared refresh Promise. The first 401 starts it; later 401 handlers await the same Promise. After refresh, replay only safe requests once, clear the shared Promise, and sign out if refresh fails.

Code example

let refreshInFlight;

function getFreshToken() {
  refreshInFlight ??= refreshToken().finally(() => {
    refreshInFlight = undefined;
  });
  return refreshInFlight;
}

Why it happens

The shared Promise acts as a single-flight gate. Without it, simultaneous refreshes can rotate credentials repeatedly and cause requests to fail with mismatched tokens.

What the interviewer is testing

Concurrency control, retry boundaries and authentication failure handling.

Senior follow-up: How would you prevent infinite replay loops and handle a request body that cannot be replayed?

14

A user changes filters rapidly and an older response overwrites a newer result. How do you prevent stale data?

Short interview answer

Give the filter state a stable query key, cancel obsolete requests where possible, and only write a response to the state version that requested it. A data-fetching layer can centralise caching and deduplication, but the latest-result rule must remain explicit.

Why it happens

Network completion order is independent of request order. Debouncing reduces traffic but does not guarantee that responses arrive in sequence.

What the interviewer is testing

Race-condition prevention across UI state, caching and request lifecycles.

Senior follow-up: How would your approach change if older results are still useful in a cache?

15

A large SPA becomes slower after several hours. How do you investigate JavaScript memory and performance?

Short interview answer

Reproduce a repeatable workflow, record performance traces and compare heap snapshots before and after several cycles. Inspect growing retained objects, detached nodes, listeners, timers, long tasks and unbounded caches, then isolate and re-measure one cause at a time.

Why it happens

A long-session slowdown can be memory growth, increasing work per interaction or both. Evidence from allocation timelines, retainers and long-task traces separates those causes.

What the interviewer is testing

A measurement-led investigation, knowledge of browser tooling and disciplined verification.

Senior follow-up: How would you distinguish a true leak from a cache that is large but intentionally bounded?

16

A CPU-heavy JavaScript operation freezes the UI. When do you use chunking, cooperative scheduling or a Web Worker?

Short interview answer

Chunk work and yield when it is small enough to stay on the main thread and needs nearby UI state. Use cooperative scheduling to prioritise urgent interactions. Use a Web Worker for sustained CPU work with data that can be transferred or copied efficiently.

Why it happens

JavaScript on the main thread competes with input, rendering and accessibility updates. Breaking work into tasks improves responsiveness; a worker provides real off-main-thread execution but adds messaging and data-transfer costs.

What the interviewer is testing

Main-thread budgeting, scheduling trade-offs, worker boundaries and measurement of responsiveness.

Senior follow-up: What data-transfer and cancellation design would you use for a worker processing a large dataset?

Run the examples

Want to try the code yourself?

Edit JavaScript and inspect console output or errors in the existing browser Playground.

Open JavaScript Playground →
Continue preparing

JavaScript preparation and practice

Back to InterviewGround