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 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 = {}; // TypeErrorQ2: 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, 2Interviewer 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 valueEvent 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 macrotasksQ6: 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
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 runsQ8: 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 winsQ9: 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 functionPrototypes 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 chainQ13: 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; // trueDOM 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 listsQ15: 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