← All articles
13 min read

JavaScript Interview Questions and Answers for 2026

The JavaScript questions interviewers actually ask in 2026, each paired with a build-it-from-scratch exercise and output-prediction puzzles that test whether you really understand the runtime.

JavaScript interview questions in 2026 still center on a small set of runtime concepts: scope and closures, how this is bound, the event loop, promises and async/await, and prototypes. Interviewers rarely stop at the definition. They ask you to implement something from scratch (debounce, Promise.all, a deep clone, an EventEmitter) or to predict the output of a tricky snippet. This guide pairs every concept with the exercise that tests it, so you practice both halves.

Key Takeaways

  • Five topics carry most JavaScript rounds: closures and scope, this, the event loop, promises, and prototypes.
  • Every concept has a matching "build it" task. Closures lead to debounce, this leads to bind, promises lead to Promise.all, prototypes lead to new.
  • The event loop rule to memorize: after each task, the runtime drains all microtasks before taking the next macrotask.
  • Output-prediction puzzles reward a method, not memory. Mark sync code first, then microtasks, then timers.
  • Edge cases score points: empty input to Promise.all, cycles in deep clone, this inside a debounced function.

Which JavaScript Topics Do Interviewers Test Most?

JavaScript rounds test whether you understand the runtime, not whether you remember API trivia. The table below maps each common question to the exercise interviewers use to check that understanding. Use it as a study plan: if you can answer the left column and build the right column, you are ready for most frontend engineer interviews.

Concept questionWhat it really testsMatching "implement it" exercise
What is a closure?Lexical scope, captured statedebounce, throttle, once, memoize
How is this determined?Call-site binding rulesFunction.prototype.bind polyfill
Explain the event loopTask and microtask orderingPredict the log order of mixed async code
How do promises work?States, chaining, error flowPromise.all, promisify, retry with backoff
How does inheritance work?Prototype chain, newmyNew, instanceof polyfill
Shallow vs deep copy?References, recursion, cyclesdeepClone with WeakMap
Pub/sub pattern?Data structures, API designEventEmitter with on, off, once, emit

Senior candidates get the same topics with harder follow-ups: cancellation, memory leaks, concurrency limits, and API design trade-offs.

Scope, Hoisting, and Closures

Scope is the set of variables a piece of code can see, and JavaScript decides it lexically, by where code is written. var is function-scoped. let and const are block-scoped.

Hoisting means declarations are registered before code runs. var declarations are hoisted and initialized to undefined. Function declarations are hoisted with their body. let, const, and class are hoisted too, but they sit in the temporal dead zone (TDZ) until their line executes, so touching them early throws.

console.log(greet()); // "hi"
function greet() { return "hi"; }

console.log(a); // undefined
var a = 1;

console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 2;

A closure is a function plus the variables it captured from the scope where it was defined. The inner function keeps those variables alive after the outer function returns.

function makeCounter() {
  let count = 0;
  return {
    increment: () => ++count,
    value: () => count,
  };
}

const counter = makeCounter();
counter.increment();
counter.increment();
console.log(counter.value()); // 2

count is private: only the returned functions can reach it. That is the module pattern, the classic answer for data privacy before #private fields existed.

Implement it: debounce

Debounce delays a function until calls stop arriving for wait milliseconds. It is the most common closure exercise in frontend loops because the timer ID has to survive between calls.

function debounce(fn, wait) {
  let timerId;
  function debounced(...args) {
    clearTimeout(timerId);
    timerId = setTimeout(() => fn.apply(this, args), wait);
  }
  debounced.cancel = () => clearTimeout(timerId);
  return debounced;
}

const onResize = debounce(() => console.log("resized"), 200);
window.addEventListener("resize", onResize);

The details interviewers look for:

  • debounced is a regular function, so this comes from the caller. The arrow inside setTimeout forwards it to fn.apply.
  • Arguments from the latest call win.
  • A cancel method shows you think about component unmounts.

Common follow-ups are a leading option (fire on the first call, then ignore until quiet) and throttle, which runs at most once per interval instead of waiting for silence.

How Does this Work in JavaScript?

this is decided by how a function is called, not where it is defined. Arrow functions are the exception: they have no this of their own and use the one from the enclosing scope. Check the rules in this order of precedence:

Call styleExamplethis is
new bindingnew User()The newly created object
Explicit bindingfn.call(obj), fn.apply(obj), fn.bind(obj)()obj
Implicit bindingobj.method()obj
Default bindingfn()undefined in strict mode, globalThis in sloppy mode
Arrow function() => thisInherited from the surrounding scope

call and apply invoke immediately and differ only in how they take arguments: call(ctx, a, b) versus apply(ctx, [a, b]). bind returns a new function with this locked in and runs nothing.

The classic bug is losing this when you pass a method as a callback:

class Timer {
  seconds = 0;
  tick() {
    this.seconds++;
  }
}

const timer = new Timer();
const tick = timer.tick;
tick(); // TypeError: Cannot read properties of undefined (class bodies are strict)

The same thing happens when you pass timer.tick to addEventListener or a promise .then. Fix it with .bind(timer), a wrapper arrow such as () => timer.tick(), or a class field arrow method. Saying why each works earns more credit than picking one.

Implement it: bind

Function.prototype.myBind = function (context, ...boundArgs) {
  const original = this;
  return function (...callArgs) {
    return original.apply(context, [...boundArgs, ...callArgs]);
  };
};

function greet(greeting, name) {
  return `${greeting}, ${name}. I am ${this.role}.`;
}
const adminGreet = greet.myBind({ role: "admin" }, "Hello");
console.log(adminGreet("Sam")); // "Hello, Sam. I am admin."

A strong answer mentions what this version skips: native bind ignores the bound this when the result is called with new. You do not need to implement that unless asked, but naming it shows depth.

The Event Loop: Microtasks vs Macrotasks

The event loop is the scheduler that lets single-threaded JavaScript handle async work. The runtime runs the current task until the call stack is empty, then drains the whole microtask queue, then (in browsers) may render, then picks the next macrotask. MDN's microtask guide describes the same model.

  • Macrotasks (tasks): setTimeout, setInterval, I/O callbacks, UI events, MessageChannel messages.
  • Microtasks: promise then/catch/finally callbacks, await continuations, queueMicrotask, MutationObserver callbacks.

The consequence: microtasks always jump ahead of timers, and a microtask that keeps scheduling microtasks can starve rendering and timers entirely.

Node.js adds two wrinkles worth mentioning if you interview for backend or full-stack roles. In a CommonJS script, process.nextTick callbacks run before promise microtasks. And setImmediate runs in the check phase, after I/O polling, so inside an I/O callback it fires before a setTimeout(fn, 0). Our Node.js interview questions guide covers the libuv phases in detail.

Output puzzle 1: the standard ordering question

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

Output: A E C D B. Walk the interviewer through it in three passes: synchronous logs (A, E), then microtasks in the order they were queued (C, D), then the timer (B).

Output puzzle 2: async functions

async function run() {
  console.log("1");
  await null;
  console.log("2");
}

console.log("3");
run();
console.log("4");

Output: 3 1 4 2. An async function runs synchronously until its first await. Everything after the await is scheduled as a microtask, so 4 prints first.

This is exactly the kind of round where nerves wipe out knowledge you actually have. Narrating the three-pass method out loud helps, and our guide to thinking out loud in coding interviews shows how to structure it.

Blanking on microtask order mid-interview? TechScreen runs invisibly during screen shares on Zoom, Google Meet, Teams, and CoderPad, and gives you a real-time explanation of the snippet in front of you. Try it free with 3 tokens, no credit card.

Get started free →

Promises and async/await

A promise is an object representing a value that will exist later. It is in one of three states: pending, fulfilled, or rejected. Once settled, it never changes. then returns a new promise, which is why chains work and why returning a value inside then passes it down the chain.

Know the four combinators cold, because "which one would you use?" is a frequent promise interview question:

CombinatorResolves whenRejects whenTypical use
Promise.allAll fulfill (array of values)Any rejects (first reason)Load everything a page needs
Promise.allSettledAll settle (array of status objects)NeverBatch jobs where partial failure is fine
Promise.raceFirst one settlesFirst one settles as rejectedTimeouts
Promise.anyFirst one fulfillsAll reject (AggregateError)Fastest of several mirrors

The most common async/await mistake interviewers probe is accidental serialization:

const user = await fetchUser(id);
const posts = await fetchPosts(id);

const [user2, posts2] = await Promise.all([fetchUser(id), fetchPosts(id)]);

The first version waits for the user before even starting the posts request. The second runs both at once. A related trap: array.forEach(async (item) => { await save(item); }) does not wait for anything, because forEach ignores returned promises. Use for...of for sequential work or Promise.all(array.map(...)) for parallel work.

Recent additions are fair game in 2026. Promise.withResolvers() returns a promise together with its resolve and reject functions, and Promise.try() (standardized in ECMAScript 2025 alongside iterator helpers and new Set methods, per the TC39 finished proposals list) wraps a function that might throw synchronously or return a promise.

Implement it: Promise.all

This is the single most requested promise exercise. Grading focuses on order, empty input, and non-promise values.

function promiseAll(iterable) {
  return new Promise((resolve, reject) => {
    const items = Array.from(iterable);
    const results = new Array(items.length);
    let remaining = items.length;

    if (remaining === 0) {
      resolve(results);
      return;
    }

    items.forEach((item, index) => {
      Promise.resolve(item).then((value) => {
        results[index] = value;
        remaining -= 1;
        if (remaining === 0) resolve(results);
      }, reject);
    });
  });
}

Points to say out loud:

  • Results are stored by index, not pushed, so output order matches input order regardless of finish order.
  • Promise.resolve(item) handles plain values and thenables.
  • Empty input resolves immediately with [], matching the native behavior.
  • Calling reject more than once is harmless, because a promise settles only once.

A frequent follow-up is a concurrency limiter: run N tasks at most K at a time. The trick is a worker pool where each worker pulls the next index from a shared counter in a loop.

Prototypes and Classes

Every JavaScript object has an internal link to another object, its prototype. When you read a property that is not on the object, the engine walks up this prototype chain until it finds it or reaches null. That chain is the entire inheritance system.

function Animal(name) {
  this.name = name;
}
Animal.prototype.speak = function () {
  return `${this.name} makes a sound`;
};

const rex = new Animal("Rex");
console.log(rex.speak()); // "Rex makes a sound"
console.log(Object.getPrototypeOf(rex) === Animal.prototype); // true
console.log(Object.hasOwn(rex, "speak")); // false

class syntax builds the same structure: methods go on Animal.prototype, and extends links the prototypes. Classes are not purely cosmetic, though. They throw if called without new, run in strict mode, and support truly private #fields that cannot be read from outside, even with reflection.

Be ready to explain the difference between prototype (a property on constructor functions, used as the template for instances) and Object.getPrototypeOf(obj) (the actual link on an instance, historically exposed as __proto__).

Implement it: the new operator

function myNew(Constructor, ...args) {
  const instance = Object.create(Constructor.prototype);
  const result = Constructor.apply(instance, args);
  const isObject =
    result !== null && (typeof result === "object" || typeof result === "function");
  return isObject ? result : instance;
}

The three steps are: create an object linked to the prototype, run the constructor with this set to it, and return the constructor's result if it returned an object, otherwise the new instance. Interviewers often ask about that last rule because it surprises people.

More Implement-from-Scratch Exercises

These two are common in frontend phone screens and onsites. If you have only a week, practice these plus debounce and Promise.all until you can write each in under ten minutes without bugs.

Deep clone with cycle handling

function deepClone(value, seen = new WeakMap()) {
  if (value === null || typeof value !== "object") return value;
  if (seen.has(value)) return seen.get(value);

  if (value instanceof Date) return new Date(value.getTime());
  if (value instanceof RegExp) return new RegExp(value.source, value.flags);

  if (value instanceof Map) {
    const copy = new Map();
    seen.set(value, copy);
    value.forEach((v, k) => copy.set(deepClone(k, seen), deepClone(v, seen)));
    return copy;
  }

  if (value instanceof Set) {
    const copy = new Set();
    seen.set(value, copy);
    value.forEach((v) => copy.add(deepClone(v, seen)));
    return copy;
  }

  const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value));
  seen.set(value, copy);
  for (const key of Reflect.ownKeys(value)) {
    copy[key] = deepClone(value[key], seen);
  }
  return copy;
}

The WeakMap is what separates a good answer from a great one. Without it, an object that references itself causes infinite recursion. Also mention that in real code you would reach for the built-in structuredClone(), which handles cycles, Maps, Sets, and Dates, but throws on functions and DOM nodes and does not preserve class prototypes.

EventEmitter

class EventEmitter {
  #listeners = new Map();

  on(event, listener) {
    if (!this.#listeners.has(event)) this.#listeners.set(event, new Set());
    this.#listeners.get(event).add(listener);
    return () => this.off(event, listener);
  }

  off(event, listener) {
    this.#listeners.get(event)?.delete(listener);
  }

  once(event, listener) {
    const wrapper = (...args) => {
      this.off(event, wrapper);
      listener(...args);
    };
    return this.on(event, wrapper);
  }

  emit(event, ...args) {
    const listeners = this.#listeners.get(event);
    if (!listeners || listeners.size === 0) return false;
    for (const listener of [...listeners]) listener(...args);
    return true;
  }
}

Design choices to call out:

  • Copying the set before iterating ([...listeners]) keeps once handlers from mutating the collection mid-loop.
  • Returning an unsubscribe function from on is the pattern React effects and most modern libraries use.
  • Using a Set means adding the same listener twice registers it once. Node's built-in EventEmitter allows duplicates, so ask which behavior the interviewer wants.

That last habit, asking about requirements before coding, matters as much as the code. Our breakdown of what interviewers look for in coding interviews explains why.

Output Prediction Puzzles to Practice

Output puzzles test whether your mental model matches the engine. Answer each before reading the solution.

Puzzle 3: var in a loop

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

Output: 3 3 3 0 1 2. All three var callbacks close over one shared i, which is 3 by the time timers run. let creates a fresh binding per iteration.

Puzzle 4: losing this

Assume this code runs as an ES module, where top-level this is undefined and strict mode is on.

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

console.log(user.regular());
console.log(detached());
console.log(user.arrow());

Output: "Ada", then undefined, then undefined. The detached call uses default binding, so this is undefined. The arrow function takes this from the surrounding scope, not from user. In a sloppy-mode browser script, those last two lines would read window.name instead, which is usually an empty string.

Puzzle 5: promise chain error flow

Promise.resolve(1)
  .then((x) => { throw new Error("boom " + x); })
  .then(() => console.log("skipped"))
  .catch((err) => { console.log(err.message); return 2; })
  .then((x) => console.log("after", x));

Output: boom 1, then after 2. A thrown error skips every then until a catch, and a catch that returns a value puts the chain back on the success path.

Puzzle 6: coercion classics

ExpressionResultWhy
typeof null"object"Historic bug kept for compatibility
0.1 + 0.2 === 0.3falseIEEE 754 floating point
[] + []""Both convert to empty strings
"5" - 23- forces numeric conversion
"5" + 2"52"+ with a string concatenates
NaN === NaNfalseUse Number.isNaN or Object.is

Interviewers who ask coercion questions mostly want to hear that you use === and why.

How to Prepare in Two Weeks

A focused two-week plan beats a scattered month. Split it by the table at the top of this page.

  1. Days 1-3: Closures, hoisting, this. Write debounce, throttle, once, and myBind from memory.
  2. Days 4-6: Event loop and promises. Write promiseAll, promiseAllSettled, and a concurrency limiter. Solve ten output puzzles daily.
  3. Days 7-8: Prototypes and classes. Write myNew and an instanceof polyfill.
  4. Days 9-10: Deep clone, EventEmitter, and a small LRU cache using Map insertion order.
  5. Days 11-14: Mock interviews under time pressure, plus framework questions. Most JavaScript loops include a framework round, so pair this guide with our React interview questions.

If your loop also has an algorithms round, JavaScript is a reasonable choice, though choosing a coding interview language covers its weak spots, such as the lack of a built-in heap. The coding interview patterns cheat sheet covers that side of prep, and the full-stack developer interview guide explains how the rounds combine for hybrid roles.

Interviewing for a JavaScript or frontend role soon? TechScreen stays invisible on Zoom, Google Meet, Teams, HackerRank, and CoderPad while it helps with event loop puzzles and implement-it tasks in real time. Start with 3 free tokens, no credit card required.

Get started free →

Frequently Asked Questions

What are the most common JavaScript interview questions in 2026?

The questions that come up most are about closures, hoisting and the temporal dead zone, how this is bound, call versus apply versus bind, the event loop with microtasks and macrotasks, promises and async/await, and prototypal inheritance. Most frontend loops then add a practical task such as implementing debounce, throttle, Promise.all, a deep clone, or a small EventEmitter, plus one or two output-prediction snippets.

How do I explain the JavaScript event loop in an interview?

Say that JavaScript runs one call stack at a time. When the stack is empty, the runtime first drains the entire microtask queue (promise callbacks, queueMicrotask, and await continuations), then takes one task from the macrotask queue (timers, I/O, UI events), and repeats. That is why a resolved promise's then callback always runs before a setTimeout with zero delay. Back it up with a four-line console.log example.

What is a closure in JavaScript, in simple terms?

A closure is a function bundled with references to the variables from the scope where it was created. The function keeps access to those variables even after the outer function has returned. Closures power private state, factory functions, memoization, and utilities like debounce. The classic interview bug is a var loop variable shared by every callback, which let fixes by creating a new binding per iteration.

Should I use async/await or .then() in a coding interview?

Use async/await for most interview code because it reads top to bottom and lets you handle errors with try/catch. Switch to promise combinators when you need concurrency: Promise.all for all-or-nothing, Promise.allSettled when you want every result, Promise.race for timeouts, and Promise.any for the first success. Interviewers mainly check that you do not accidentally run independent requests one after another.

Are JavaScript classes real classes?

Not in the Java sense. A JavaScript class creates a constructor function, and methods live on its prototype object, so inheritance still works by walking the prototype chain. Classes do add real behavior beyond sugar: they must be called with new, their bodies run in strict mode, and they support true private fields with the # prefix. Explaining both halves of that answer is what interviewers want to hear.

Do I need to know TypeScript for a JavaScript interview?

Usually you are tested on JavaScript semantics, because TypeScript compiles away and the runtime behavior is still JavaScript. Many frontend teams do write TypeScript daily, so expect questions on basic types, generics, and narrowing if the job post mentions it. If you are allowed to choose, writing plain JavaScript in a live coding round is fine and often faster.

Ready to use AI assistance in your next interview?

TechScreen is the invisible AI assistant trusted by engineers interviewing at Google, Meta, Amazon, and hundreds of other companies. Start with 3 free tokens — no credit card required.

Ace your next interview →