Skip to content
JavaScript interview 6 min read

JavaScript Interview Questions

Most JavaScript interviews probe the same handful of fundamentals: how the type system coerces values, how scope and hoisting decide what a name refers to, what this binds to, how prototypes link objects, and how the event loop schedules asynchronous work. Knowing these cold lets you reason about unfamiliar code instead of memorizing trivia. The questions below are grouped by theme, each with a concise, technically precise answer and runnable examples where it helps.

Types & coercion

Q: What are the primitive types in JavaScript? There are seven: string, number, bigint, boolean, undefined, symbol, and null. Everything else — objects, arrays, functions — is an object. Primitives are immutable and compared by value.

Q: Why does typeof null return "object"? It is a historical bug from the first JavaScript engine that can never be fixed without breaking the web. To check for null, use value === null.

Q: What is the difference between == and ===? === (strict equality) compares value and type with no conversion. == (loose equality) performs type coercion first, which leads to surprising results. Always prefer ===.

console.log(0 == "");        // true  (both coerce to 0)
console.log(0 == "0");       // true
console.log("" == "0");      // false
console.log(null == undefined); // true
console.log(NaN === NaN);    // false

Output:

true
true
false
true
false

Q: How do you reliably check if a value is an array? Use Array.isArray(value). typeof returns "object" for arrays, so it cannot distinguish them.

Q: What does NaN mean and how do you test for it? NaN (“Not a Number”) is the result of an invalid numeric operation like 0 / 0. It is the only value not equal to itself, so use Number.isNaN(x) rather than x === NaN.

Q: What is the difference between null and undefined? undefined means a variable has been declared but not assigned, or a property/return value is absent. null is an explicit “no value” you assign yourself.

Scope & hoisting

Q: What is hoisting? Declarations are processed before code runs. var and function declarations are hoisted — var is initialized to undefined, while function declarations are fully available. let and const are hoisted but remain in the temporal dead zone until their declaration line, so accessing them early throws.

console.log(fn());     // works — function declaration
console.log(x);        // undefined — var hoisted, not assigned
// console.log(y);     // ReferenceError — TDZ
var x = 1;
let y = 2;
function fn() { return "hoisted"; }

Output:

hoisted
undefined

Q: What is the difference between let, const, and var?

Featurevarletconst
ScopeFunctionBlockBlock
Hoisted (usable early)Yes (undefined)No (TDZ)No (TDZ)
ReassignableYesYesNo
Redeclarable in scopeYesNoNo

const prevents reassignment of the binding, not mutation of the value. const arr = []; arr.push(1) is perfectly legal.

Q: What is a closure? A closure is a function bundled with references to its surrounding lexical scope. The inner function keeps access to outer variables even after the outer function has returned — the basis for private state and factory functions.

function counter() {
  let n = 0;
  return () => ++n;
}
const next = counter();
console.log(next(), next(), next());

Output:

1 2 3

this & functions

Q: How is this determined? By how a function is called, not where it is defined: as a method (obj.fn()) this is the object; called plainly it is undefined in strict mode (or the global object otherwise); with new it is the new instance; and call/apply/bind set it explicitly.

Q: How do arrow functions differ regarding this? Arrow functions have no own this — they capture it lexically from the enclosing scope. This makes them ideal for callbacks where you want to preserve the outer this.

const obj = {
  name: "Ada",
  greetLater() {
    setTimeout(() => console.log(`Hi, ${this.name}`), 0);
  },
};
obj.greetLater();

Output:

Hi, Ada

Q: What is the difference between call, apply, and bind? call invokes immediately with arguments listed individually; apply invokes immediately with arguments as an array; bind returns a new function with this permanently fixed.

Q: What is a higher-order function? A function that takes a function as an argument or returns one — for example map, filter, and setTimeout.

Objects & prototypes

Q: What is prototypal inheritance? Every object has an internal link to a prototype object. When a property is not found on an object, the engine walks up the prototype chain until it finds it or reaches null.

Q: How do you create an object with no prototype? Object.create(null) — useful for a clean dictionary with no inherited keys like toString.

Q: What is the difference between a shallow and a deep copy? A shallow copy ({ ...obj } or Object.assign) copies top-level properties but shares nested references. A deep copy duplicates everything; structuredClone(obj) does this natively.

const original = { user: { name: "Ada" } };
const shallow = { ...original };
shallow.user.name = "Linus";
console.log(original.user.name); // mutated — shared reference

const deep = structuredClone(original);
deep.user.name = "Grace";
console.log(original.user.name); // unchanged

Output:

Linus
Linus

Q: What does Object.freeze do? It makes an object immutable at the top level — properties cannot be added, removed, or reassigned. It is shallow, so nested objects remain mutable.

Async & event loop

Q: What is the event loop? JavaScript runs on a single thread. The event loop continuously checks the call stack; when it is empty, it runs queued tasks. Microtasks (promise callbacks) drain completely before the next macrotask (timers, I/O) runs.

Q: What is the output order here, and why?

console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");

Output:

1
4
3
2

Synchronous code (1, 4) runs first. Then the microtask queue drains (3), and only afterward the macrotask timer fires (2).

Q: What is the difference between a Promise and async/await? async/await is syntactic sugar over promises. An async function always returns a promise, and await pauses it until the awaited promise settles — making async code read like synchronous code.

Q: What does Promise.all do versus Promise.allSettled? Promise.all resolves when all inputs resolve and rejects immediately if any one rejects. Promise.allSettled always resolves with a status report for each input, so one failure does not discard the others.

Q: How do you debounce a function? Reset a timer on every call so the action only fires after activity stops.

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

Best practices

  • Default to const, use let only when reassigning, and avoid var.
  • Always use === and !==; reserve == for the intentional value == null check.
  • Prefer arrow functions for callbacks to keep this lexical, and methods for object behavior.
  • Use async/await with try/catch over long promise chains for readable async flow.
  • Reach for structuredClone for deep copies instead of the fragile JSON.parse(JSON.stringify(...)) trick.
  • Explain your reasoning out loud in interviews — interviewers grade how you think, not just the final answer.

Interview tip: when asked to predict output, narrate the call stack, microtask queue, and macrotask queue step by step. Demonstrating the mental model earns more credit than a lucky guess.

Last updated June 1, 2026
Was this helpful?