THE MODN CHRONICLES

Interview Prep

Interview Questions on JS — Closures, Promises, Event Loop, and What Interviewers Actually Test

JavaScript interviews are not about syntax. They are about understanding how the language actually works — closures, the event loop, prototypal inheritance, and async patterns. Here are the questions that separate developers who know JS from developers who use JS.

JavaScript code on a developer screen

JavaScript powers 98% of websites. Every frontend, full-stack, and Node.js interview starts with JS fundamentals.

The JavaScript Interview Landscape

JavaScript is the most interviewed programming language in the world. Whether you are applying for a frontend role at a product company, a full-stack position at a startup, or a service company role at TCS, Infosys, or Wipro — JS questions are guaranteed. The difference is depth: service companies test syntax and basic concepts, while product companies test how the engine works under the hood.

The most common mistake candidates make is preparing React or Angular questions while ignoring core JavaScript. Interviewers at companies like Flipkart, Razorpay, and Swiggy start with vanilla JS before touching any framework. If you cannot explain closures, the event loop, or prototypal inheritance, the framework questions never come.

This guide covers the actual JavaScript interview questions asked in Indian companies — organized by topic, with code examples and the output interviewers expect you to predict.

The number one reason candidates fail JS interviews: they learn React but skip core JavaScript. Interviewers test closures and the event loop before they ever ask about components.

Variables, Scope, and Hoisting

These questions test whether you understand how JavaScript handles variable declarations behind the scenes. The var/let/const distinction is the most asked JS question in Indian interviews.

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

Why they ask: This is the gateway question. If you get this wrong, the interview goes downhill fast.

// var: function-scoped, hoisted as undefined
// let: block-scoped, hoisted but in Temporal Dead Zone
// const: block-scoped, cannot be reassigned

function test() {
  if (true) {
    var a = 1;    // accessible outside if block
    let b = 2;    // only inside if block
    const c = 3;  // only inside if block
  }
  console.log(a); // 1
  console.log(b); // ReferenceError
}

// Hoisting behavior:
console.log(x); // undefined (var is hoisted)
console.log(y); // ReferenceError (let is in TDZ)
var x = 5;
let y = 10;

// const with objects — the reference is constant,
// not the value:
const obj = { name: "JS" };
obj.name = "JavaScript"; // Works fine
obj = {};                // TypeError

Q2: What will this code output?

for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// Output: 3, 3, 3 (not 0, 1, 2)
// Why: var is function-scoped, so all callbacks
// share the same 'i' which is 3 after the loop

// Fix with let:
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 1000);
}
// Output: 0, 1, 2
// Why: let creates a new binding for each iteration

// Fix with IIFE (older approach):
for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(() => console.log(j), 1000);
  })(i);
}
// Output: 0, 1, 2

Interviewer expects: You should explain WHY it outputs 3,3,3 — not just know the fix. The explanation involves closures, scope, and the event loop.

Closures

Closures are the single most tested JavaScript concept in interviews. If you understand closures deeply, you understand half of how JavaScript works.

Q3: What is a closure? Give a practical example.

Definition: A closure is a function that remembers the variables from its outer scope even after the outer function has returned.

// Basic closure:
function outer() {
  let count = 0;
  return function inner() {
    count++;
    return count;
  };
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// 'count' persists because inner() closes over it

// Practical use: private variables
function createBankAccount(initialBalance) {
  let balance = initialBalance; // private
  return {
    deposit(amount) {
      balance += amount;
      return balance;
    },
    withdraw(amount) {
      if (amount > balance) return "Insufficient funds";
      balance -= amount;
      return balance;
    },
    getBalance() {
      return balance;
    }
  };
}
const account = createBankAccount(1000);
account.deposit(500);    // 1500
account.withdraw(200);   // 1300
// account.balance       // undefined (private!)

Q4: What will this output? (Closure trap question)

function createFunctions() {
  const result = [];
  for (var i = 0; i < 3; i++) {
    result.push(function() { return i; });
  }
  return result;
}
const fns = createFunctions();
console.log(fns[0]()); // 3 (not 0!)
console.log(fns[1]()); // 3 (not 1!)
console.log(fns[2]()); // 3 (not 2!)

// All functions close over the SAME 'i' variable
// By the time they execute, i is already 3

// Fix: use let instead of var
// Or use an IIFE to capture each value

Event Loop and Async JavaScript

The event loop is what makes JavaScript non-blocking despite being single-threaded. Product companies test this heavily — they want you to predict the execution order of mixed sync/async code.

Q5: Explain the JavaScript event loop.

Key concepts: Call stack, Web APIs, callback queue (macrotask queue), microtask queue, and the event loop that coordinates them.

// Execution order:
// 1. Synchronous code runs first (call stack)
// 2. Microtasks run next (Promises, queueMicrotask)
// 3. Macrotasks run last (setTimeout, setInterval)

console.log("1");                    // sync

setTimeout(() => console.log("2"), 0); // macrotask

Promise.resolve().then(() => 
  console.log("3")                   // microtask
);

console.log("4");                    // sync

// Output: 1, 4, 3, 2
// Even though setTimeout has 0ms delay,
// the Promise callback runs first because
// microtasks have priority over macrotasks

Q6: Predict the output (advanced event loop)

console.log("start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve()
  .then(() => console.log("promise 1"))
  .then(() => console.log("promise 2"));

queueMicrotask(() => console.log("microtask"));

console.log("end");

// Output:
// start
// end
// promise 1
// microtask
// promise 2
// timeout

// Explanation:
// 1. "start" and "end" — synchronous, run first
// 2. "promise 1" — microtask, runs after sync
// 3. "microtask" — also microtask, queued after promise 1
// 4. "promise 2" — chained promise, new microtask
// 5. "timeout" — macrotask, runs after all microtasks
Developer debugging JavaScript code

Understanding the event loop is what separates a JavaScript user from a JavaScript developer. Every senior interview tests this.

Promises and Async/Await

Every modern JS codebase uses Promises. Interviewers test whether you understand the Promise lifecycle, error handling, and the difference between Promise.all, Promise.race, and Promise.allSettled.

Q7: What are the three states of a Promise?

// Pending  → initial state
// Fulfilled → resolved successfully
// Rejected  → failed with an error

// Once settled (fulfilled or rejected), 
// a Promise cannot change state

const promise = new Promise((resolve, reject) => {
  // async operation
  const success = true;
  if (success) {
    resolve("Data loaded");  // → fulfilled
  } else {
    reject("Failed to load"); // → rejected
  }
});

promise
  .then(data => console.log(data))   // "Data loaded"
  .catch(err => console.error(err))
  .finally(() => console.log("Done")); // always runs

Q8: Promise.all vs Promise.allSettled vs Promise.race

const p1 = Promise.resolve(1);
const p2 = Promise.reject("error");
const p3 = Promise.resolve(3);

// Promise.all — fails fast on first rejection
Promise.all([p1, p2, p3])
  .catch(err => console.log(err)); // "error"

// Promise.allSettled — waits for ALL to settle
Promise.allSettled([p1, p2, p3])
  .then(results => console.log(results));
// [{status:"fulfilled", value:1},
//  {status:"rejected", reason:"error"},
//  {status:"fulfilled", value:3}]

// Promise.race — first to settle wins
Promise.race([p1, p2, p3])
  .then(val => console.log(val)); // 1

// Promise.any — first to FULFILL wins
Promise.any([p2, p1, p3])
  .then(val => console.log(val)); // 1 (skips p2)

// When to use which:
// .all     → parallel API calls, all must succeed
// .allSettled → parallel calls, need all results
// .race    → timeout patterns
// .any     → first successful response wins

Q9: Convert callback to async/await

// Callback hell:
getUser(id, (user) => {
  getOrders(user.id, (orders) => {
    getOrderDetails(orders[0].id, (details) => {
      console.log(details);
    });
  });
});

// With async/await:
async function getOrderInfo(id) {
  try {
    const user = await getUser(id);
    const orders = await getOrders(user.id);
    const details = await getOrderDetails(orders[0].id);
    return details;
  } catch (error) {
    console.error("Failed:", error.message);
    throw error; // re-throw for caller to handle
  }
}

// Key rule: async/await is syntactic sugar over
// Promises. Every async function returns a Promise.

The ‘this’ Keyword

The ‘this’ keyword in JavaScript behaves differently from every other language. It is determined by HOW a function is called, not where it is defined. This trips up even experienced developers.

Q10: What does ‘this’ refer to in different contexts?

// 1. Global context
console.log(this); // window (browser) / global (Node)

// 2. Object method
const obj = {
  name: "JS",
  greet() { console.log(this.name); }
};
obj.greet(); // "JS" — this = obj

// 3. Regular function
function show() { console.log(this); }
show(); // window (non-strict) / undefined (strict)

// 4. Arrow function — inherits from parent scope
const obj2 = {
  name: "JS",
  greet: () => { console.log(this.name); }
};
obj2.greet(); // undefined! Arrow doesn't have own 'this'

// 5. Event handler
button.addEventListener("click", function() {
  console.log(this); // the button element
});
button.addEventListener("click", () => {
  console.log(this); // window! Arrow inherits outer this
});

// 6. call, apply, bind — explicitly set 'this'
function greet() { console.log(this.name); }
const user = { name: "Rahul" };
greet.call(user);    // "Rahul"
greet.apply(user);   // "Rahul"
const bound = greet.bind(user);
bound();             // "Rahul"

Q11: What is the difference between call, apply, and bind?

function introduce(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}
const person = { name: "Priya" };

// call — invokes immediately, args as list
introduce.call(person, "Hello", "!");
// "Hello, Priya!"

// apply — invokes immediately, args as array
introduce.apply(person, ["Hi", "."]);
// "Hi, Priya."

// bind — returns NEW function, does not invoke
const boundFn = introduce.bind(person, "Hey");
boundFn("?"); // "Hey, Priya?"

// Memory trick:
// Call = Comma separated args
// Apply = Array of args
// Bind = returns Bound function

Prototypes and ES6 Classes

JavaScript uses prototypal inheritance, not classical inheritance. ES6 classes are syntactic sugar over prototypes. Senior-level interviews always test whether you understand what happens under the hood.

Q12: Explain prototypal inheritance.

// Every JS object has a hidden [[Prototype]] link
// When you access a property, JS looks up the chain

const animal = {
  eat() { return "eating"; }
};

const dog = Object.create(animal);
dog.bark = function() { return "woof"; };

dog.bark(); // "woof" — found on dog
dog.eat();  // "eating" — found on animal (prototype)

// Prototype chain: dog → animal → Object.prototype → null

// ES6 class (syntactic sugar):
class Animal {
  eat() { return "eating"; }
}
class Dog extends Animal {
  bark() { return "woof"; }
}
// Under the hood, this creates the same prototype chain

Q13: What is the difference between __proto__ and prototype?

// .prototype — property on constructor FUNCTIONS
// .__proto__ — property on INSTANCES (the actual link)

function Person(name) {
  this.name = name;
}
Person.prototype.greet = function() {
  return "Hi, " + this.name;
};

const p = new Person("Amit");
p.__proto__ === Person.prototype; // true
p.greet(); // "Hi, Amit"

// Modern way to access prototype:
Object.getPrototypeOf(p) === Person.prototype; // true

DOM and Practical Questions

Frontend interviews always include DOM manipulation and event handling questions. These test whether you can work with the browser without relying on a framework.

Q14: What is event delegation?

// Instead of adding listeners to each child,
// add ONE listener to the parent

// Bad: listener on every button
document.querySelectorAll(".btn").forEach(btn => {
  btn.addEventListener("click", handleClick);
});

// Good: event delegation
document.querySelector(".btn-container")
  .addEventListener("click", (e) => {
    if (e.target.matches(".btn")) {
      handleClick(e);
    }
  });

// Why it matters:
// 1. Works for dynamically added elements
// 2. Uses less memory (1 listener vs N listeners)
// 3. Better performance for large lists

Q15: What is the difference between event bubbling and capturing?

// Capturing: event goes DOWN from window to target
// Bubbling: event goes UP from target to window

// Default is bubbling
parent.addEventListener("click", () => {
  console.log("parent");
});
child.addEventListener("click", () => {
  console.log("child");
});
// Click child → "child", "parent" (bubbles up)

// Enable capturing with third argument:
parent.addEventListener("click", () => {
  console.log("parent");
}, true); // capturing phase
// Click child → "parent", "child" (captures down)

// Stop propagation:
child.addEventListener("click", (e) => {
  e.stopPropagation(); // prevents bubbling
  console.log("child only");
});

Q16: Implement debounce and throttle.

// Debounce: wait until user STOPS doing something
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
// Use: search input — wait until user stops typing
const search = debounce(fetchResults, 300);

// Throttle: execute at most once per interval
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}
// Use: scroll handler — fire at most every 200ms
window.addEventListener("scroll", throttle(onScroll, 200));

This is a coding question in 80% of frontend interviews. Interviewers expect you to write debounce and throttle from scratch without looking it up.

How to Prepare for JS Interviews

Step 1: Master the Core Concepts (Week 1)

Focus on closures, scope, hoisting, the event loop, and ‘this’ keyword. These five topics cover 60% of JS interview questions. Write code examples for each — do not just read about them.

Step 2: Practice Output Prediction (Week 2)

Interviewers love “what will this output?” questions. Practice predicting output for code involving setTimeout + Promises, closure traps, and ‘this’ binding. Run the code to verify your predictions.

Step 3: Implement Utility Functions (Week 3)

Write debounce, throttle, deep clone, flatten array, and Promise.all from scratch. These are the most common JS coding questions. Practice until you can write them without reference.

Step 4: Mock Interviews (Week 4)

Practice explaining your code out loud. Interviewers evaluate your thought process, not just the final answer. Use AI mock interview tools to simulate real interview pressure.

JS Interview Cheat Sheet by Company Type

Service Companies

TCS, Infosys, Wipro

  • • var/let/const
  • • Data types
  • • Array methods
  • • Basic DOM
  • • ES6 features

Product Companies

Flipkart, Razorpay, Swiggy

  • • Closures deep dive
  • • Event loop
  • • Prototypes
  • • Debounce/throttle
  • • Promise patterns

Startups

Series A-C companies

  • • Practical coding
  • • DOM manipulation
  • • API integration
  • • Error handling
  • • Performance

Practice JS Interview Questions with AI

Get asked real JavaScript interview questions, write code, and receive instant feedback on your answers. Practice closures, promises, and event loop questions in a simulated interview environment.

Free · AI-powered feedback · Real interview questions