ForgeFrontend - Prepare, Practice, Crack
Secure checkout
Lifetime access
Instant PDF download
Free updates forever

Prepare · Practice · Crack

JavaScript Interview Questions and Answers

Short answer

Almost every JavaScript interview is built on six topics: scope and closures, how this is decided, the prototype chain, the event loop, coercion and equality, and promises. Learn those as models rather than answers and you can derive the rest of the questions from them.

TopicWhich round tests itDepth expected
Scope and closuresScreening, then every round afterDeep - it underpins debounce, memoize, modules
this and bindingScreening, code reviewRules, plus why arrows are different
Prototype chainScreening at product companiesConceptual; class is syntax over it
Event loopScreening and output questionsMicrotask vs macrotask ordering
Coercion and equalityScreening, trick questionsUse ===, know why == surprises
Promises and asyncCoding round, machine codingDeep - including error handling
Array and object methodsCoding round, live reviewWhich mutate, and Set/Map
ES6+ syntaxAssumed everywhere, rarely askedFluency, not definitions
Where each topic is actually tested. Preparing for the wrong round is the most common wasted effort.

How the JavaScript round is actually structured

In most Indian product companies and funded startups the JavaScript round comes first, lasts 45 to 60 minutes, and has three parts: rapid-fire fundamentals for the first fifteen minutes, then a few output-prediction snippets, then one small implementation such as debounce or a Promise.all polyfill. Service companies usually stop after the first part. Knowing that shape matters, because it tells you that fundamentals are the gate, you do not reach the implementation if the first fifteen minutes go badly.

The other thing worth knowing is that the question list is remarkably stable. Closures, `this`, the event loop, hoisting and equality account for the large majority of what gets asked, and they have for a decade. That is good news: this is a finite syllabus, and the way to clear it is not to memorise fifty answers but to understand six models well enough to derive an answer to a question you have not seen.

What is the difference between var, let and const?

`var` is function-scoped and hoisted with an initial value of undefined, so reading it before its line gives undefined rather than an error. `let` and `const` are block-scoped and hoisted into a temporal dead zone, so reading them early throws a ReferenceError. `const` prevents reassignment of the binding, it does not freeze the value.

Scope, the TDZ, and what const actually protectsjavascript
1function scopes() {
2  if (true) {
3    var functionScoped = "leaks out";
4    let blockScoped = "stays in";
5  }
6  console.log(functionScoped);   // → leaks out
7  // console.log(blockScoped);   // → ReferenceError: not defined
8}
9scopes();
10
11console.log(hoisted);            // → undefined     declared, not assigned
12var hoisted = 1;
13
14// console.log(tdz);             // → ReferenceError: Cannot access before initialization
15let tdz = 2;
16
17const config = { debug: false };
18config.debug = true;             // fine, the OBJECT is mutable
19console.log(config.debug);       // → true
20// config = {};                  // → TypeError: Assignment to constant variable
21
22const frozen = Object.freeze({ a: 1 });
23frozen.a = 2;
24console.log(frozen.a);           // → 1     silently ignored (throws in strict mode)

The practical rule to state: default to `const`, use `let` only where you genuinely reassign, and never use `var` in new code. The follow-up is usually the loop question, a `var` loop with setTimeout printing 3 3 3 while `let` prints 0 1 2, because `let` creates a fresh binding per iteration while `var` has only one for the whole function.

What is a closure?

A function together with the variables from the scope in which it was defined, kept alive after that scope has returned. The word to be careful with is "copy", a closure holds a live reference, not a snapshot, which is exactly why the loop question behaves as it does and why two closures over the same variable see each other's changes.

Private state, shared state, and the accidental leakjavascript
1function makeCounter() {
2  let count = 0;                 // unreachable from outside
3  return {
4    increment: () => ++count,
5    get value() { return count; },
6  };
7}
8
9const c = makeCounter();
10c.increment(); c.increment();
11console.log(c.value);            // → 2
12console.log(c.count);            // → undefined     genuinely private
13
14// Two closures over the SAME variable, not two copies
15function pair() {
16  let shared = 0;
17  return [() => ++shared, () => shared];
18}
19const [inc, read] = pair();
20inc(); inc();
21console.log(read());             // → 2
22
23// The leak: a closure keeps its whole scope alive
24function attach() {
25  const bigData = new Array(1e6).fill("x");
26  return () => bigData.length;   // → the million-element array can never be freed
27}

Two additions turn a textbook answer into a strong one. Name a real use - module privacy, `debounce` holding its timer, `memoize` holding its cache, a React `useState` closure capturing a stale value. And name the cost: a closure keeps its entire enclosing scope reachable, so a long-lived callback that closes over a large object prevents it being collected. Very few candidates mention the second one.

How is the value of this determined?

By how the function is called, not where it was written, with arrow functions as the deliberate exception. There are four rules and they apply in priority order: `new` binding, then explicit binding with call, apply or bind, then implicit binding from the object before the dot, then the default, which is undefined in strict mode and modules.

The four rules, and where arrows break the patternjavascript
1"use strict";
2
3function who() { return this; }
4
5console.log(who());                    // → undefined        default binding
6console.log(who.call({ tag: "explicit" }));   // → { tag: 'explicit' }
7
8const obj = { tag: "implicit", who };
9console.log(obj.who().tag);            // → implicit         the object before the dot
10
11const made = new who();                // → a new empty object
12console.log(typeof made);              // → object
13
14// Losing the receiver - the bug behind most "this is undefined" errors
15const detached = obj.who;
16console.log(detached());               // → undefined        no object before the dot
17console.log(detached.bind(obj)().tag); // → implicit
18
19// Arrows have no this of their own; they use the enclosing scope's
20const timer = {
21  label: "tick",
22  startBroken() { setTimeout(function () { console.log(this.label); }, 0); },
23  startFixed()  { setTimeout(() => console.log(this.label), 0); },
24};
25timer.startFixed();
26// → tick
27// startBroken would log undefined, the callback is called plainly

The complete answer includes the reverse case, because it is what interviewers use to check whether you understand arrows or just prefer them. An arrow function is correct as a callback inside a method, where you want the enclosing `this`. It is wrong as the method itself, where it captures the module scope and never sees the object at all.

Explain the event loop

JavaScript runs on a single thread with one call stack, so nothing else executes while your code is running. Asynchronous work is handed to the host, the browser or Node, and when it finishes, the callback is queued. Once the stack is empty, the event loop takes work from those queues, and the order in which it does so is what the question is really about.

There are two queues. Microtasks, which is promise callbacks and `queueMicrotask`, are drained completely after the current synchronous work finishes. Macrotasks - timers, I/O, event handlers, are taken one at a time, and the entire microtask queue is drained again after each. That is why a `.then` always runs before a `setTimeout(fn, 0)` scheduled earlier, and why an infinite chain of microtasks freezes the tab: rendering is a macrotask and never gets a turn.

The ordering question, in the form it is askedjavascript
1console.log("A");
2
3setTimeout(() => console.log("B"), 0);
4
5Promise.resolve().then(() => console.log("C"));
6
7(async () => {
8  console.log("D");              // an async fn body runs SYNCHRONOUSLY
9  await null;
10  console.log("E");              // everything after await is a microtask
11})();
12
13console.log("F");
14
15// → A
16// → D
17// → F
18// → C
19// → E
20// → B

Line D is the one that separates real understanding from a memorised rule. Calling an async function does not defer anything: the body runs immediately up to the first `await`, and only the continuation is queued. If you can say that unprompted, most output-prediction questions become straightforward.

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299

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

`===` compares type and value with no conversion. `==` converts the operands to a common type first, following a set of rules that produce results almost nobody predicts reliably. The correct professional answer is to use `===` everywhere, with one deliberate exception.

The surprising results, and the one useful ==javascript
1console.log(0 == "");          // → true
2console.log(0 == "0");         // → true
3console.log("" == "0");        // → false     not transitive!
4console.log(null == undefined);// → true
5console.log(null == 0);        // → false     null only equals undefined
6console.log(NaN == NaN);       // → false
7console.log([] == false);      // → true      [] → "" → 0, false → 0
8
9console.log(0 === "");         // → false     no surprises with ===
10
11// The one idiomatic use of ==
12function greet(name) {
13  if (name == null) return "Hello, guest";   // catches null AND undefined
14  return "Hello, " + name;
15}
16console.log(greet(undefined), greet(0));
17// → Hello, guest Hello, 0

The `"" == "0"` line is worth pointing out because it proves `==` is not transitive, 0 equals both, but they do not equal each other, which is a much better argument than "it is confusing". And `x == null` as a deliberate null-or-undefined check is worth naming, because it shows you are choosing `===` rather than following a rule you cannot justify.

What is the prototype chain?

Every object has a hidden link to another object, its prototype. When you read a property that the object does not have, the engine follows that link, and the next, until it finds the property or reaches null. That chain is how inheritance works in JavaScript, and `class` is syntax over it rather than a different system underneath.

The chain, and what class compiles down tojavascript
1const animal = { speak() { return "generic noise"; } };
2const dog = Object.create(animal);
3dog.name = "Bruno";
4
5console.log(dog.speak());                    // → generic noise   found on the prototype
6console.log(Object.hasOwn(dog, "speak"));    // → false
7console.log(Object.getPrototypeOf(dog) === animal);  // → true
8
9class Kit {
10  constructor(name) { this.name = name; }
11  describe() { return "Kit: " + this.name; }
12}
13const k = new Kit("React");
14console.log(Object.hasOwn(k, "name"));       // → true    instance property
15console.log(Object.hasOwn(k, "describe"));   // → false   lives on Kit.prototype
16console.log(Object.getPrototypeOf(k) === Kit.prototype);  // → true
17
18// One shared method for every instance, that is the point of the chain
19const k2 = new Kit("JS");
20console.log(k.describe === k2.describe);     // → true

The last line is the answer to "why does this matter". Methods live once on the prototype and every instance shares them, so creating ten thousand objects does not create ten thousand copies of each function. Arrow-function class fields, by contrast, are per-instance, which is the trade-off behind the common React advice about defining handlers inside a class.

How do promises and async/await work?

A promise is an object representing a value that is not ready yet, in one of three states, pending, fulfilled or rejected, and it settles exactly once. `async`/`await` is syntax over the same machinery: an async function always returns a promise, and `await` pauses the function until the awaited promise settles, without blocking the thread.

Sequential versus parallel, and the error-handling trapjavascript
1const delay = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
2
3async function sequential() {
4  const a = await delay(100, "a");   // waits 100ms
5  const b = await delay(100, "b");   // then another 100ms
6  return [a, b];
7}
8// → resolves after ~200ms
9
10async function parallel() {
11  const [a, b] = await Promise.all([delay(100, "a"), delay(100, "b")]);
12  return [a, b];
13}
14// → resolves after ~100ms, both started before either was awaited
15
16// The trap: a rejection with no catch
17async function risky() { throw new Error("boom"); }
18risky();
19// → UnhandledPromiseRejection - the throw becomes a rejected promise
20
21risky().catch((e) => console.log("handled:", e.message));
22// → handled: boom
23
24// try/catch works with await, and does NOT work with a bare .then
25async function safe() {
26  try { await risky(); } catch (e) { return "caught " + e.message; }
27}
28safe().then(console.log);          // → caught boom

Awaiting in a loop when the requests are independent is the single most common performance mistake in real code, and interviewers ask about it precisely because it looks correct. Naming `Promise.all` for independent work and sequential awaits only for genuine dependencies is what they are listening for.

Is JavaScript pass by value or pass by reference?

Always pass by value - but for objects, the value being passed is a reference. That is why mutating a parameter's properties is visible to the caller while reassigning the parameter itself is not. Being able to state it in that one sentence is unusual enough to be worth practising.

Mutation is visible; reassignment is notjavascript
1function mutate(o) { o.value = "changed"; }
2function reassign(o) { o = { value: "new object" }; }
3
4const obj = { value: "original" };
5
6mutate(obj);
7console.log(obj.value);       // → changed      the caller sees it
8
9reassign(obj);
10console.log(obj.value);       // → changed      unaffected, only the local name moved
11
12// Primitives are copied outright
13let n = 1;
14function bump(x) { x++; }
15bump(n);
16console.log(n);               // → 1
17
18// The same rule explains the default-parameter surprise
19function addItem(item, list = []) { list.push(item); return list; }
20console.log(addItem("a"), addItem("b"));
21// → [ 'a' ] [ 'b' ]          a FRESH array each call, unlike Python

The default-parameter line at the end is a useful thing to have ready, because interviewers who have worked in Python often ask it as a trap. In JavaScript a default parameter expression is evaluated afresh on every call that omits the argument, so a default empty array is a new array each time and never accumulates across calls. That is the opposite of Python's famous mutable-default behaviour, and knowing the difference reads as genuine cross-language experience rather than revision.

The same rule explains the one about copying. Spreading an object into a new one copies the top level only, so nested objects are still shared between the copy and the original, a shallow copy. That is why updating nested React state needs a spread at every level that changed, and why `structuredClone` exists for the cases where you genuinely need the whole tree duplicated.

How should you prepare in the last week?

Work backwards from the round structure. Spend the first two days on the six models above until you can explain each in three sentences without notes, because that is the gate. Spend two days on output prediction, write the answer down before running it, and when you are wrong, identify which model you misread rather than memorising the case. Spend the last two days implementing the standard utilities from a blank file, timed.

What not to do: read a list of fifty questions and their answers the night before. It feels productive and it fails on the first follow-up, because every one of these questions has a "why" behind it and a list gives you only the "what". If you have one evening left, pick closures, `this` and the event loop, and go deep on those three, they generate more interview questions than everything else combined.

Frequently asked questions

What are the most common JavaScript interview questions?
Closures, how this is determined, the event loop and microtask ordering, hoisting and the temporal dead zone, == versus ===, the prototype chain, and promises with async/await. Those seven account for the large majority of first-round questions, and the list has been stable for years.
What is a closure in JavaScript?
A function together with the variables from the scope where it was defined, still reachable after that scope has returned. It holds a live reference rather than a copy. Closures are how modules get private state and how debounce, throttle and memoize keep their internal data between calls.
How is the value of this decided in JavaScript?
By how the function is called, in priority order: new binding, then explicit binding with call, apply or bind, then implicit binding from the object before the dot, then the default, which is undefined in strict mode. Arrow functions have no this of their own and use the enclosing scope's.
What is the event loop?
The mechanism that lets single-threaded JavaScript handle asynchronous work. Completed callbacks are queued, and once the call stack is empty the loop takes them. Microtasks - promise callbacks - drain completely before any macrotask such as a timer, which is why .then always beats setTimeout(fn, 0).
What is hoisting and the temporal dead zone?
Declarations are processed before code runs. var is hoisted and initialised to undefined, and function declarations are fully available early. let and const are hoisted but left uninitialised, so accessing them before their line throws a ReferenceError, that gap is the temporal dead zone.
Should I ever use == instead of ===?
Only for x == null, which deliberately matches both null and undefined and nothing else. Everywhere else use ===, because == coerces and produces results that are not even transitive: 0 equals both "" and "0", but those two do not equal each other.
What is the prototype chain?
Every object links to a prototype object, and property lookups walk that chain until the property is found or the chain reaches null. It is JavaScript's inheritance mechanism, and class is syntax over it, methods declared in a class live once on the prototype and are shared by every instance.
What is the difference between promises and async/await?
None underneath - async/await is syntax over promises. An async function always returns a promise, and await pauses that function until the awaited promise settles without blocking the thread. The practical difference is that try/catch works with await, whereas .then chains need .catch.
Is JavaScript pass by value or pass by reference?
Always pass by value. For objects the value being copied is a reference, which is why mutating a parameter's properties is visible to the caller but reassigning the parameter itself is not. Stating it in exactly that way is a noticeably stronger answer than picking one of the two labels.
Do I need DSA for a JavaScript frontend role?
Much less than for a backend role. A handful of patterns - hash maps, two pointers, sliding window, basic recursion, covers what frontend rounds ask, and the implementation problems are usually utilities like debounce rather than graph algorithms. FAANG-level loops are the exception.
How long does it take to prepare for a JavaScript interview?
For someone already writing JavaScript daily, two focused weeks is realistic: one on the fundamentals until each can be explained without notes, one on output prediction and implementing the standard utilities from scratch. Reading a list of answers the night before reliably fails on the first follow-up.
Which JavaScript topics should a fresher prioritise?
Scope and closures first, because more questions derive from them than from anything else, then this, then the event loop. Array methods and which of them mutate come next, since they appear in every live-coding round. Prototypes and coercion can be learnt at a conceptual level and deepened later.

This is 1 of 80+ questions in the JavaScript Interview Kit

Get every question with detailed answers, follow-ups and real code, plus coding challenges and a last-minute revision sheet. One-time payment, instant access.

⚡ Get the JavaScript Interview Kit → ₹299

Full kit

JavaScript Interview Kit · ₹299

Get it →