41 questions · Updated

JavaScript Interview Questions and Answers

JavaScript interviews rarely test trivia for its own sake. Interviewers want to see that you understand how the language actually executes: how scope and closures capture variables, how the event loop orders callbacks, microtasks and rendering, how this is bound, and how prototypes back the class syntax. This page collects 41 JavaScript interview questions and answers, grouped from beginner to advanced, followed by the JS coding interview questions that come up most often — debounce, deep clone, flattening arrays, implementing Promise.all and similar. Answers reflect modern JavaScript (ES2024 and later), so you will see Object.groupBy, Promise.withResolvers, structuredClone and the immutable array methods alongside the classics. Each answer is short enough to say out loud in an interview, and where code helps, there is a small snippet you can type from memory. Use the table of contents to jump to a level, open an answer only after you have tried to answer it yourself, and pair this list with the JavaScript array methods cheat sheet for quick revision before the call.

Beginner JavaScript interview questions

Fundamentals every candidate is expected to explain clearly.

1.

What is the difference between var, let and const?

var is function-scoped, is hoisted and initialised to undefined, and can be redeclared in the same scope. let and const are block-scoped and are hoisted too, but they sit in the temporal dead zone until their declaration runs, so reading them early throws a ReferenceError. const prevents reassignment of the binding, not mutation of the value: a const object or array can still be changed. Default to const, use let when you must reassign, and avoid var in new code.

scopevariablesLink to this question
2.

What are the primitive types in JavaScript?

There are seven primitives: string, number, bigint, boolean, undefined, null and symbol. Everything else, including arrays and functions, is an object. Primitives are immutable and compared by value, while objects are compared by reference. typeof null returns "object" for historical reasons, which is a common follow-up question.

3.

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

=== (strict equality) compares value and type without conversion. == (loose equality) applies type coercion first, so 0 == "" and null == undefined are both true. Use === by default; the one idiom some teams allow is value == null, which checks for both null and undefined. Object.is is stricter still: it treats NaN as equal to NaN and distinguishes +0 from -0.

equalitycoercionLink to this question
4.

What is the difference between null and undefined?

undefined means a value was never assigned: an uninitialised variable, a missing property, a missing argument or a function without a return value. null is an intentional empty value that you assign yourself. They are loosely equal to each other but not strictly equal. The nullish coalescing operator ?? and optional chaining ?. treat both the same way.

5.

What is hoisting?

Hoisting is the behaviour where declarations are processed before code in a scope runs. Function declarations are hoisted with their body, so you can call them before they appear. var declarations are hoisted and initialised to undefined. let, const and class declarations are hoisted but stay uninitialised in the temporal dead zone, so accessing them before the declaration throws.

greet(); // works: function declarations are hoisted
console.log(a); // undefined
var a = 1;
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;
function greet() { console.log("hi"); }
6.

What is a closure?

A closure is a function bundled with references to the variables of the scope where it was created. The inner function keeps access to those variables even after the outer function has returned. Closures power data privacy, factories, memoisation and callbacks that remember state. Because they hold references rather than copies, they see later updates to the captured variables.

function counter() {
  let count = 0;
  return () => ++count;
}
const next = counter();
next(); // 1
next(); // 2
7.

How do arrow functions differ from regular functions?

Arrow functions do not have their own this, arguments, super or new.target; they use the values from the enclosing scope. They cannot be used as constructors and have no prototype property. That makes them ideal for callbacks inside methods, but a poor fit for object methods that need this to be the object. They also allow an implicit return when the body is a single expression.

8.

What are truthy and falsy values?

When a value is used in a boolean context it is converted to true or false. The falsy values are false, 0, -0, 0n, "" (empty string), null, undefined and NaN. Everything else is truthy, including "0", "false", empty arrays and empty objects. This matters for || defaults: use ?? when 0 or an empty string are valid values.

9.

What is the difference between the spread and rest syntax?

Both use three dots, but they do opposite jobs. Spread expands an iterable or object into individual elements, for example in function calls, array literals or object literals. Rest collects remaining elements into an array or object, in function parameters or destructuring. Object spread makes a shallow copy, so nested objects are still shared.

const merged = { ...defaults, ...options }; // spread
function sum(...nums) { return nums.reduce((a, b) => a + b, 0); } // rest
const [first, ...others] = [1, 2, 3]; // rest in destructuring
10.

What does destructuring do?

Destructuring unpacks values from arrays or properties from objects into variables in a single statement. You can rename properties, give defaults for undefined values, and nest patterns. It is widely used in function parameters to accept an options object, and in React for props and hook return values.

const { name, role = "user", address: { city } } = account;
const [x, y] = point;
11.

What is the difference between map, filter, reduce and forEach?

map returns a new array of the same length with each element transformed. filter returns a new array containing only elements for which the callback returns truthy. reduce folds the array into a single value using an accumulator. forEach runs a callback for side effects and returns undefined, and it cannot be stopped early except by throwing — use for...of, some or find when you need to break.

12.

What are template literals?

Template literals are strings delimited by backticks. They support interpolation with ${expression}, multi-line strings without escape characters, and tagged templates, where a function receives the string parts and values separately. Tagged templates are used by libraries for things like SQL query builders, styled components and safe HTML escaping.

Intermediate JavaScript interview questions

Asynchronous code, this, prototypes and modern language features.

13.

Explain the event loop.

JavaScript runs on a single thread with a call stack. Asynchronous work (timers, network, I/O) is handled by the host environment, which queues callbacks when the work completes. When the call stack is empty, the event loop first drains the entire microtask queue (promise reactions, queueMicrotask, MutationObserver), then takes one task from the task (macrotask) queue, such as a setTimeout callback or a UI event. In browsers, rendering can happen between tasks, which is why a long synchronous loop freezes the page.

console.log("1");
setTimeout(() => console.log("4"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("2");
// 1, 2, 3, 4
event loopasyncLink to this question
14.

What is a Promise and what states can it be in?

A Promise is an object representing the eventual result of an asynchronous operation. It is pending until it settles as either fulfilled with a value or rejected with a reason, and once settled it never changes. You consume it with then, catch and finally, each of which returns a new promise, enabling chaining. Unhandled rejections are reported by the runtime and can crash Node.js processes by default.

15.

What is the difference between Promise.all, allSettled, race and any?

Promise.all fulfils with an array of results when every input fulfils, and rejects as soon as any input rejects. Promise.allSettled always fulfils with an array of {status, value | reason} objects once all inputs settle. Promise.race settles with the first input to settle, whether it fulfils or rejects. Promise.any fulfils with the first fulfilment and only rejects, with an AggregateError, if every input rejects.

16.

How does async/await work?

An async function always returns a promise. Inside it, await pauses the function until the awaited promise settles, then resumes with the value or throws the rejection reason, so you can use ordinary try/catch. It is syntax over promises and the microtask queue, not threads. Awaiting independent operations one after another serialises them; start them together and await Promise.all to run them concurrently.

// Sequential: total time = a + b
const a = await fetchA();
const b = await fetchB();
// Concurrent: total time = max(a, b)
const [a2, b2] = await Promise.all([fetchA(), fetchB()]);
17.

How is the value of this determined?

For regular functions, this depends on how the function is called. Called as obj.method(), this is obj. Called as a plain function, this is undefined in strict mode (and modules) or the global object in sloppy mode. With new, this is the newly created object; with call, apply or bind, it is whatever you pass. Arrow functions ignore all of these rules and use the this of their enclosing scope.

18.

What is the difference between call, apply and bind?

All three set this explicitly. call invokes the function immediately with arguments listed individually, apply invokes it immediately with arguments as an array, and bind returns a new function with this (and optionally leading arguments) permanently fixed. A bound function's this cannot be changed by a later call or bind, although new still creates a fresh object.

19.

How does prototypal inheritance work?

Every object has an internal [[Prototype]] link to another object or null. When you read a property that is not on the object itself, the engine walks up this prototype chain until it finds it or reaches null. Constructor functions share methods through their prototype property, and class syntax is a cleaner way to set up the same chain. Object.create(proto) creates an object with a chosen prototype directly.

20.

Are JavaScript classes just syntactic sugar?

Mostly, but not entirely. Classes build on prototypes, yet they add real semantics: class bodies are always in strict mode, classes cannot be called without new, and they support private fields and methods (#name), static blocks and public field declarations that have no direct equivalent in old constructor functions. Private fields are truly inaccessible from outside, unlike underscore conventions.

class Account {
  #balance = 0;
  static #count = 0;
  constructor() { Account.#count++; }
  deposit(n) { this.#balance += n; return this.#balance; }
}
21.

What is the difference between a shallow and a deep copy?

A shallow copy duplicates the top-level properties, so nested objects are still shared between the copy and the original. Spread, Object.assign and Array.prototype.slice make shallow copies. structuredClone makes a deep copy and handles Dates, Maps, Sets, typed arrays and circular references, but it cannot clone functions, DOM nodes or class instances with their prototype. JSON.parse(JSON.stringify(x)) is a lossy older trick that drops undefined, functions and Dates.

22.

What are debounce and throttle, and when do you use each?

Both limit how often a function runs in response to frequent events. Debounce delays execution until events stop for a given wait time, which suits search-as-you-type and resize handlers where you only care about the final value. Throttle guarantees the function runs at most once per interval while events keep coming, which suits scroll position tracking or rate-limiting button presses.

performanceeventsLink to this question
23.

What is event delegation?

Event delegation attaches a single listener to a common ancestor instead of one listener per child. Because most events bubble, the ancestor receives them and uses event.target (often with closest()) to work out which child was involved. It uses less memory and automatically covers elements added later. Events that do not bubble, such as focus, need focusin or capture-phase listeners instead.

24.

What are ES modules and how do they differ from CommonJS?

ES modules use import and export, are statically analysable, run in strict mode, and are loaded asynchronously, which enables tree-shaking and top-level await. CommonJS uses require and module.exports, loads synchronously and is evaluated at runtime. ES module imports are live bindings, while require returns a copy of the exports object at that moment. Node.js supports both, and recent versions can require() synchronous ES modules.

25.

What are Map and Set, and when are they better than objects and arrays?

Map is a key-value collection that accepts any value as a key, preserves insertion order and has a size property. Set stores unique values. Use Map when keys are not strings, when you add and delete keys frequently, or to avoid prototype key collisions. Use Set for fast membership checks and de-duplication, for example [...new Set(arr)]. Recent engines also add Set methods such as union, intersection and difference.

Advanced JavaScript interview questions

Engine behaviour, memory, iteration protocols and newer APIs senior candidates should know.

26.

What is the difference between microtasks and macrotasks in practice?

Microtasks (promise callbacks, queueMicrotask) run immediately after the current script finishes and before the next task or render, and the queue is drained completely, including microtasks queued by other microtasks. Macrotasks (setTimeout, setInterval, I/O, message events) run one per loop iteration. A recursive chain of microtasks can therefore starve rendering entirely, while chained setTimeout calls give the browser chances to paint.

event loopperformanceLink to this question
27.

How does garbage collection work, and what causes memory leaks?

Engines use reachability-based, generational garbage collection: objects that cannot be reached from roots such as the global object and the current call stack are freed. Leaks happen when something keeps an unwanted reference alive: forgotten timers and event listeners, growing caches or global arrays, closures holding large objects, and detached DOM nodes still referenced from JavaScript. WeakMap, WeakSet and WeakRef let you associate data without preventing collection.

memoryperformanceLink to this question
28.

What are generators and iterators?

An iterator is an object with a next() method that returns {value, done}; an iterable exposes one via Symbol.iterator, which is what for...of, spread and destructuring consume. Generator functions (function*) produce iterators and pause at each yield, so you can write lazy, potentially infinite sequences. Async generators combine this with promises and are consumed with for await...of, which suits paginated APIs and streams.

function* ids() {
  let i = 0;
  while (true) yield i++;
}
const gen = ids();
gen.next().value; // 0
gen.next().value; // 1
iteratorsgeneratorsLink to this question
29.

What is a Proxy and what is Reflect for?

A Proxy wraps an object and intercepts fundamental operations such as get, set, has, deleteProperty and function calls through handler traps. Frameworks like Vue use proxies for reactivity, and they are also used for validation, logging and default values. Reflect provides methods that perform the default behaviour of each operation, so a trap can do its extra work and then forward correctly with Reflect.get or Reflect.set.

metaprogrammingLink to this question
30.

What are WeakMap and WeakRef used for?

WeakMap holds keys weakly: if nothing else references a key object, the entry can be garbage collected. That makes it ideal for attaching private data or caches to objects you do not own without leaking memory. WeakMap keys are not enumerable, so you cannot list them. WeakRef and FinalizationRegistry give lower-level control, but collection timing is non-deterministic, so they should not drive program logic.

memorycollectionsLink to this question
31.

Which ES2023–ES2025 features should you know?

ES2023 added non-mutating array methods (toSorted, toReversed, toSpliced, with) and findLast/findLastIndex. ES2024 added Object.groupBy and Map.groupBy, Promise.withResolvers, the /v regex flag, resizable ArrayBuffers and Atomics.waitAsync. ES2025 added iterator helpers (map, filter, take on iterators), new Set methods (union, intersection, difference), Promise.try, RegExp.escape and JSON modules via import attributes. Mentioning one or two with a use case shows you keep current.

const byRole = Object.groupBy(users, (u) => u.role);
const sorted = scores.toSorted((a, b) => b - a); // original untouched
const { promise, resolve, reject } = Promise.withResolvers();
es2024modern jsLink to this question
32.

How do you cancel an asynchronous operation?

Promises themselves cannot be cancelled, so the standard pattern is AbortController. You pass controller.signal to fetch or to your own async functions, and calling controller.abort() makes fetch reject with an AbortError and fires the signal's abort event for custom code. AbortSignal.timeout(ms) creates a signal that aborts automatically, and AbortSignal.any combines several signals.

const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
33.

What is the difference between Web Workers and async code?

Async code still runs on the main thread; it only avoids blocking while waiting for I/O. CPU-heavy work such as parsing a large file or image processing still blocks the UI. Web Workers run scripts on a separate thread with their own event loop and no DOM access, communicating via postMessage with structured cloning or transferable objects. SharedArrayBuffer and Atomics allow shared memory when the page is cross-origin isolated.

performanceconcurrencyLink to this question
34.

How do you prevent XSS when working with the DOM?

Never insert untrusted strings as HTML. Use textContent or framework bindings, which escape by default, instead of innerHTML, outerHTML, insertAdjacentHTML or document.write. If you must render user HTML, sanitise it with a well-maintained sanitiser and consider the Trusted Types API to enforce this. A Content Security Policy that blocks inline scripts limits the damage if something slips through.

JavaScript coding interview questions

The JS coding interview questions asked most often, with compact reference solutions.

35.

Implement a debounce function.

Keep a timer in a closure. Each call clears the previous timer and starts a new one, so the wrapped function only runs once calls stop for the wait period. Use a regular function for the wrapper and apply so this and arguments are forwarded. Interviewers often follow up with a leading-edge option or a cancel method.

function debounce(fn, wait) {
  let timer;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), wait);
  };
}
closurescodingLink to this question
36.

Implement a throttle function.

Track the time of the last run. If enough time has passed, run immediately and record the time; otherwise ignore the call. This version runs on the leading edge only; a trailing call can be added with a timer if the last event must not be lost.

function throttle(fn, interval) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= interval) {
      last = now;
      fn.apply(this, args);
    }
  };
}
closurescodingLink to this question
37.

Implement Promise.all.

Return a new promise, track results by index so order is preserved, and count settled inputs. Wrap each input in Promise.resolve so plain values work. Reject immediately on the first rejection, and resolve straight away for an empty input.

function promiseAll(items) {
  return new Promise((resolve, reject) => {
    const results = [];
    let done = 0;
    const list = [...items];
    if (list.length === 0) return resolve(results);
    list.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        results[i] = value;
        if (++done === list.length) resolve(results);
      }, reject);
    });
  });
}
promisescodingLink to this question
38.

Flatten a nested array without using Array.prototype.flat.

Recurse into elements that are arrays and collect everything else. The built-in arr.flat(Infinity) does the same thing, so mention it first and then show you can implement it. For very deep nesting, an explicit stack avoids call-stack limits.

function flatten(arr) {
  return arr.reduce(
    (acc, item) => acc.concat(Array.isArray(item) ? flatten(item) : item),
    []
  );
}
flatten([1, [2, [3, [4]]]]); // [1, 2, 3, 4]
arraysrecursioncodingLink to this question
39.

Write a deep clone function.

In real code use structuredClone. In an interview, show a recursive version that handles arrays and plain objects and tracks visited objects in a WeakMap so circular references do not cause infinite recursion. Mention what it does not handle: Dates, Maps, Sets, class prototypes and functions.

function deepClone(value, seen = new WeakMap()) {
  if (value === null || typeof value !== "object") return value;
  if (seen.has(value)) return seen.get(value);
  const copy = Array.isArray(value) ? [] : {};
  seen.set(value, copy);
  for (const key of Object.keys(value)) {
    copy[key] = deepClone(value[key], seen);
  }
  return copy;
}
objectsrecursioncodingLink to this question
40.

Write a memoize function.

Cache results keyed by the arguments. A Map with a serialised key works for primitive arguments; for object arguments you would need a WeakMap or a custom key function. Memoisation only makes sense for pure functions, and an unbounded cache can grow forever, so production versions often use an LRU limit.

function memoize(fn) {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (!cache.has(key)) cache.set(key, fn(...args));
    return cache.get(key);
  };
}
closuresperformancecodingLink to this question
41.

Fix the classic setTimeout-in-a-loop bug.

With var, all callbacks share one function-scoped i, so after the loop finishes they all log 3. Using let creates a fresh binding per iteration, so each callback captures its own value. Before let existed, the fix was an immediately invoked function expression that received i as a parameter.

for (var i = 0; i < 3; i++) setTimeout(() => console.log(i)); // 3 3 3
for (let j = 0; j < 3; j++) setTimeout(() => console.log(j)); // 0 1 2
closuresscopecodingLink to this question

Preparing for a JavaScript interview

Common questions about getting ready

How should I prepare for a JavaScript interview?

Start with the fundamentals interviewers probe most: scope and closures, this, prototypes, the event loop and promises. Practise explaining each in two or three sentences out loud. Then write the common coding exercises (debounce, throttle, Promise.all, deep clone, flatten) from memory without an editor, and finish with a few array and string problems timed at 20–30 minutes each.

Which JavaScript topics come up most often in interviews?

Closures, the event loop with microtasks versus macrotasks, promises and async/await, how this is bound, prototypal inheritance, equality and coercion, and array methods. Front-end roles add DOM events, event delegation and browser performance; Node.js roles add modules, streams and error handling.

Do I need to know ES2024 and newer features?

You will rarely be failed for not knowing them, but mentioning modern options signals that you keep up. Knowing structuredClone, Object.groupBy, the non-mutating array methods like toSorted, Promise.withResolvers and AbortController covers most of what interviewers expect in 2026.

Are JavaScript coding interviews different from algorithm interviews?

Often, yes. Many front-end and full-stack interviews ask you to implement utilities (debounce, an event emitter, a promise helper) or small UI features rather than pure algorithm puzzles. Larger companies still include data structures and algorithms rounds, which you can usually solve in JavaScript.

How many JavaScript interview questions should I practise?

Depth beats volume. Being able to explain the 41 questions on this page clearly and write the seven coding answers without help is a stronger position than skimming hundreds of questions you cannot explain.

Related resources

More interview questions