Huru
Technical Interviews

JavaScript Interview Questions and Answers (2026): Concepts, Output Puzzles and Coding Exercises

JavaScript interview questions with answers checked against MDN and TC39: types and coercion, equality, hoisting and the temporal dead zone, closures, this, prototypes and classes, the event loop and async/await, promise combinators, Map, Set and structuredClone, modules, what ES2023 to ES2026 added, output-prediction puzzles with explanations, and coding exercises with solutions (debounce, throttle, Promise.all, curry, memoize).

By Amine Boukioud Updated 9 min read
The Huru mascot places a small glowing sphere on the fast inner lane of a circular conveyor that also carries larger blue cubes, next to a laptop showing code

The short answer: JavaScript (JS) interviews come in three shapes: concept questions (types and coercion, scope and hoisting, closures, this, prototypes, the event loop, promises), “what does this print?” puzzles that test the same concepts under pressure, and coding exercises where you implement a utility such as debounce or Promise.all without libraries. Explain why the language behaves as it does, not just what it does. The answers below are checked against MDN and the TC39 proposals list, with sources at the end.

For HTML, CSS and browser questions, see our front-end developer interview questions; for React, our React interview questions; for types, our TypeScript interview questions.

Types and coercion

“What are JavaScript’s data types?”

Seven primitive types (string, number, bigint, boolean, undefined, symbol, null) and objects (which include arrays, functions, dates, maps and sets). Primitives are immutable and compared by value; objects are compared by reference. Two classic traps: typeof null is "object" (a historical quirk), and typeof NaN is "number".

“==, === and Object.is: what’s the difference?”

MDN describes three comparisons:

  • === (strict equality): no type conversion; “if the types differ, false is returned.” MDN calls it “almost always the correct comparison operation to use.”
  • == (loose equality): converts the operands to a common type first. null and undefined equal each other and nothing else, so null == undefined is true but null == 0 is false. Booleans are converted to numbers, so 0 == false and '' == false are both true.
  • Object.is (SameValue): like ===, except that Object.is(NaN, NaN) is true and Object.is(0, -0) is false.

includes, Map and Set use a fourth rule, SameValueZero, which treats NaN as equal to NaN, so [NaN].includes(NaN) is true while [NaN].indexOf(NaN) is -1.

“What are truthy and falsy values?”

Falsy values: false, 0, -0, 0n, '', null, undefined and NaN (plus document.all in browsers). Everything else is truthy, including '0', [] and {}. Use ?? (nullish coalescing) instead of || when 0 or '' is a valid value: count ?? 10 keeps 0, count || 10 doesn’t.

Scope, hoisting and closures

“What is hoisting, and what is the temporal dead zone?”

Declarations are processed before code runs, but differently:

  • var is hoisted and initialized to undefined, so reading it before its line gives undefined.
  • let and const exist for the whole block but stay uninitialized until their declaration runs. MDN calls that window the temporal dead zone; reading the variable inside it “will result in a ReferenceError.”
  • Function declarations are hoisted with their body, so you can call them before they appear; function expressions assigned to let/const can’t be.
console.log(a); // undefined
var a = 1;

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

“Explain closures with a practical example.”

A closure is a function together with the variables of the scope it was created in. Interviewers like this use, private state:

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

const next = makeCounter();
next(); // 1
next(); // 2 (count lives on in the closure; nothing outside can change it)

Closures also explain once, memoization and the classic loop bug, which is puzzle 4 below.

this, prototypes and classes

“How is this determined?”

At call time, not where the function is written. MDN’s rules:

  • Method call obj.f(): this is obj.
  • Plain call f(): undefined in strict mode (modules and classes are always strict), globalThis otherwise.
  • call, apply, bind: set this explicitly; bind returns a new function whose this “doesn’t change,” even if you bind it again.
  • new: this is the new object.
  • Arrow functions have no this of their own: they “inherit this from the parent scope at the time they are defined,” and call, apply and bind can’t change it. That’s why arrows are good for callbacks inside methods and bad as object methods.

“Explain prototypes. Are classes different?”

Every object has an internal link to a prototype; when a property isn’t found on the object, JavaScript looks up the prototype chain. class syntax is built on the same mechanism: methods go on Class.prototype, extends links the prototypes, and super calls the parent. Classes add real conveniences: they’re always in strict mode, they must be called with new, and #private fields are truly private.

“call, apply or bind?”

All three set this. call(thisArg, a, b) and apply(thisArg, [a, b]) invoke the function immediately (arguments one by one or as an array); bind(thisArg) returns a new function to call later, which is how you keep this when passing a method as a callback.

Asynchronous JavaScript

“Explain the event loop and microtasks.”

JavaScript runs one job to completion at a time. Callbacks wait in queues: tasks (timers, I/O, events) and microtasks (promise reactions and the code after await). After each task, the microtask queue is drained completely before the next task runs. That’s why a resolved promise’s callback always runs before a setTimeout(..., 0) callback; puzzle 5 below tests it with async/await.

“Compare Promise.all, allSettled, any and race.”

MethodFulfills whenRejects when
Promise.allAll inputs fulfill (array of values)Any input rejects (first reason)
Promise.allSettledAll inputs settle (array of { status, value / reason })Doesn’t reject
Promise.anyAny input fulfills (first value)All inputs reject (AggregateError)
Promise.raceThe first input to settle fulfillsThe first input to settle rejects

Newer helpers: Promise.withResolvers() (ES2024) returns a promise with its resolve and reject functions, and Promise.try() (ES2025) wraps a callback that may throw or return, synchronously or not, in a promise.

“How do you handle errors with async/await?”

Wrap awaited calls in try/catch (or add .catch to the returned promise). Common follow-ups: awaiting in a for loop runs requests one after another, so start them first and await Promise.all(...) to run them in parallel; and use an AbortController to cancel a fetch that’s no longer needed.

Objects, collections and copying

“Map vs plain object? What’s a WeakMap?”

A Map accepts keys of any type (including objects), keeps insertion order, has a size, and is built for frequent additions and deletions; a plain object is best for fixed, string-keyed records and JSON. A WeakMap only takes objects (or non-registered symbols) as keys and holds them weakly, so an entry doesn’t keep its key alive: useful for caching data about objects without leaking memory. Set stores unique values; ES2025 added methods such as union, intersection and difference.

“How do you deep-copy an object?”

structuredClone(value) makes a deep copy and preserves circular references; it throws a DataCloneError for values it can’t serialize, such as functions, and class instances come back as plain objects. JSON.parse(JSON.stringify(value)) is the old trick, but it drops undefined and functions and turns dates into strings. The spread operator ({ ...obj }) only copies one level.

“What’s the difference between ES modules and CommonJS?”

ES modules use import/export, are analyzed before running (which enables tree-shaking), are always strict, can use top-level await, and load asynchronously; import() loads a module on demand. CommonJS (require, module.exports) is Node.js’s original, synchronous system. Node.js supports both; new code generally uses ES modules.

What’s new in JavaScript (ES2023 to ES2026)

Interviewers rarely expect every new API, but knowing what’s current shows you keep up. From TC39’s list of finished proposals:

EditionHighlights
ES2023findLast/findLastIndex; Change Array by Copy (toSorted, toReversed, toSpliced, with); symbols as WeakMap keys
ES2024Object.groupBy and Map.groupBy; Promise.withResolvers; resizable ArrayBuffers; the RegExp v flag
ES2025Iterator helpers (map, filter, take on iterators); new Set methods; Promise.try; RegExp.escape; import attributes and JSON modules; Float16Array
ES2026Array.fromAsync; Error.isError; Math.sumPrecise; Uint8Array to and from Base64; Map “upsert”; iterator sequencing

Temporal, the long-awaited replacement for Date, is finished and listed for ES2027.

Output puzzles (“what does this print?”)

Say your reasoning out loud; the explanation matters more than the answer.

1. Types

console.log(typeof null);   // "object"
console.log(typeof NaN);    // "number"
console.log([] + []);       // "" (both arrays become empty strings)
console.log([] == false);   // true ([] → "" → 0, false → 0)
console.log(null == 0);     // false (null only loosely equals undefined)
console.log(NaN === NaN);   // false

2. Hoisting

console.log(x); // undefined
var x = 1;
console.log(y); // ReferenceError: y is in the temporal dead zone
let y = 2;

3. this

'use strict';
const user = {
  name: 'Ada',
  greet() { return this.name; },
  greetArrow: () => this?.name,
};

user.greet();      // 'Ada' (method call)
const greet = user.greet;
greet();           // TypeError: this is undefined in a plain strict-mode call
user.greetArrow(); // undefined (the arrow takes this from the outer scope)

4. Closures in loops

for (var i = 0; i < 3; i++) setTimeout(() => console.log(i));
// 3 3 3 (one shared var i, read after the loop ends)

for (let j = 0; j < 3; j++) setTimeout(() => console.log(j));
// 0 1 2 (let creates a new binding per iteration)

5. The event loop with async/await

console.log('1');
setTimeout(() => console.log('2'), 0);
(async () => {
  console.log('3');
  await null;
  console.log('4');
})();
Promise.resolve().then(() => console.log('5'));
console.log('6');
// 1 3 6 4 5 2

The async function runs synchronously until await, so 3 prints before 6. The code after await and the .then callback are both microtasks, queued in that order, so 4 then 5. The timer is a task, so 2 comes last.

Coding exercises with solutions

debounce: run only after the calls stop

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

Typical use: a search box that fetches only after the user stops typing. Follow-ups: a leading option, and a cancel method.

throttle: run at most once per interval

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

Typical use: scroll or resize handlers.

Promise.all from scratch

function promiseAll(items) {
  return new Promise((resolve, reject) => {
    const results = [];
    let remaining = items.length;
    if (remaining === 0) return resolve(results);
    items.forEach((item, i) => {
      Promise.resolve(item).then((value) => {
        results[i] = value; // keep input order, not completion order
        if (--remaining === 0) resolve(results);
      }, reject);
    });
  });
}

curry

function curry(fn) {
  return function curried(...args) {
    return args.length >= fn.length
      ? fn.apply(this, args)
      : (...more) => curried.apply(this, [...args, ...more]);
  };
}

const add = (a, b, c) => a + b + c;
curry(add)(1)(2)(3); // 6

memoize (single argument)

function memoize(fn) {
  const cache = new Map();
  return (arg) => {
    if (!cache.has(arg)) cache.set(arg, fn(arg));
    return cache.get(arg);
  };
}

Mention the trade-offs: the cache grows forever (add a size limit), and object arguments are cached by reference.

Flatten an array

arr.flat(Infinity) is the built-in answer; interviewers may ask for a recursive version:

const flatten = (arr) =>
  arr.reduce((out, item) => out.concat(Array.isArray(item) ? flatten(item) : item), []);

More practice: an event emitter (on, off, emit), a concurrency-limited task runner, and deep equality. For timed coding rounds, our HackerRank interview tips cover patterns and timeboxing.

Behavioral questions for JavaScript developers

Use the STAR method with a real project:

  • “Tell me about a tricky asynchronous bug you fixed.”
  • “Describe a time you improved the performance of a JavaScript-heavy page.”
  • “How did you introduce TypeScript, a test suite or a new build tool to a team?”
  • “Tell me about code you’d write differently today.”

Frequently asked questions

What JavaScript questions are asked in interviews?
Data types and coercion, == vs === vs Object.is, truthy and falsy values, hoisting and the temporal dead zone, closures, how this is set (and arrow functions), prototypes and classes, call/apply/bind, the event loop and microtasks, promises and async/await, Map, Set and WeakMap, deep copying with structuredClone, ES modules vs CommonJS, recent language additions, output-prediction puzzles, and coding exercises such as debounce, throttle and Promise.all.
How do I prepare for a JavaScript coding interview?
Write the common utilities from memory (debounce, throttle, Promise.all, curry, memoize, flatten, an event emitter), practice explaining output puzzles step by step, review the event loop and this rules until you can draw them, and time yourself on a few problems in a plain editor without autocomplete.
What’s the difference between == and ===?
=== compares without type conversion and returns false when the types differ; == converts the operands first, with surprising results such as 0 == false being true and null == 0 being false. MDN recommends === in almost all cases; value == null is the common deliberate exception, because it matches both null and undefined.
Why does a promise callback run before setTimeout(..., 0)?
Promise reactions are microtasks and timers are tasks. After each task, JavaScript drains the whole microtask queue before taking the next task, so a resolved promise’s callback runs first even when the timer’s delay is 0.
Which new JavaScript features should I know in 2026?
From TC39’s finished proposals: Change Array by Copy (toSorted, toReversed, with) from ES2023; Object.groupBy and Promise.withResolvers from ES2024; iterator helpers, new Set methods, Promise.try and RegExp.escape from ES2025; and Array.fromAsync and Error.isError in ES2026. Temporal, the replacement for Date, is listed for ES2027.
Can Huru help me practice JavaScript interview questions?
Yes, for the speaking parts. You answer out loud, an AI interviewer follows up, and every answer is scored out of 100 with a stronger version. Huru doesn’t run code, so practice coding exercises in an editor.

Sources

Practice this with Huru AI

Turn what you just read into a rehearsal. Unlimited AI mock interviews with instant, specific feedback.

Amine Boukioud

Amine Boukioud is a co-founder of Beatview, Inc., the company behind Huru, and Head of Growth at Huru. He writes and reviews Huru’s interview guides; each one starts from primary sources such as employers’ own careers pages, official data and research, and follows Huru’s editorial policy.

Published

How this article was made: researched and drafted with AI assistance, then written, fact-checked and signed by Amine Boukioud on October 28, 2026. Sources are linked where they are used. How we work: editorial policy.