Advanced JavaScript Interview Questions
This guide covers in-depth, advanced JavaScript concepts frequently asked in senior engineering interviews at top tech companies.
1. The Event Loop and Concurrency Model
Q: Explain how the JavaScript Event Loop works. What is the difference between the Macrotask and Microtask queues?
JavaScript is single-threaded, meaning it executes one piece of code at a time on the Call Stack. However, it handles concurrency using the Event Loop.
- Call Stack: Where synchronous code is executed.
- Web APIs: Browser environments provide Web APIs (like
setTimeout,fetch, DOM events) that run in the background. - Queues:
- Microtask Queue: Holds callbacks from Promises (
.then,.catch),MutationObserver, andqueueMicrotask. - Macrotask Queue (Task Queue): Holds callbacks from
setTimeout,setInterval, I/O, UI rendering.
- Microtask Queue: Holds callbacks from Promises (
Execution Order: The Event Loop continuously checks the Call Stack. If it's empty, it looks at the Microtask queue. It will execute all microtasks sequentially until the Microtask queue is entirely empty. Only then will it execute one macrotask from the Macrotask queue, and then immediately check the Microtask queue again.
Follow-up Code Snippet:
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Output: 1, 4, 3, 2
2. Closures and Memory Leaks
Q: What is a closure? How can closures lead to memory leaks in JavaScript?
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives a function access to its outer scope even after the outer function has returned.
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
}
}
const increment = outer();
increment(); // 1
Memory Leaks: JavaScript uses a Garbage Collector (Mark and Sweep algorithm) to free up memory. A variable is garbage collected when it is no longer reachable. Closures can cause memory leaks if they inadvertently hold references to large objects (like DOM nodes or massive arrays) long after they are needed, preventing the GC from cleaning them up.
3. The this Keyword Context
Q: How is the value of this determined in JavaScript?
Unlike statically typed languages where this refers to the class instance, in JavaScript, this is determined by how a function is called (its execution context), not where it is defined.
- Global Context / Free Function Invocation:
thisrefers to the global object (windowin browsers). In strict mode ('use strict'), it isundefined. - Method Invocation: If called as a method of an object (
obj.foo()),thisrefers toobj. - Constructor Functions (
newkeyword):thisrefers to the newly created instance. - Explicit Binding (
call,apply,bind): You can explicitly setthis. - Arrow Functions: Arrow functions do not have their own
thisbinding. They lexically inheritthisfrom the outer function scope where they were defined.
4. Prototypal Inheritance
Q: Explain Prototypal Inheritance. How does the Prototype Chain work?
JavaScript objects have a special hidden property [[Prototype]] (historically accessed via __proto__), which is either null or references another object.
When you try to access a property on an object:
- The engine checks if the property exists on the object itself.
- If not, it looks at the object's prototype.
- It continues up the "prototype chain" until it finds the property or reaches a prototype of
null(usuallyObject.prototype.__proto__), at which point it returnsundefined.
const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;
console.log(rabbit.jumps); // true (from rabbit)
console.log(rabbit.eats); // true (from prototype)
5. Hoisting and Temporal Dead Zone (TDZ)
Q: What is hoisting? How do var, let, and const differ in this regard?
Hoisting is JavaScript's default behavior of moving declarations to the top of the current scope during the compilation phase, before execution.
var: Both the declaration and initialization (withundefined) are hoisted. You can access it before its lexical definition without an error (it yieldsundefined).letandconst: The declarations are hoisted, but they are not initialized. Accessing them before initialization throws aReferenceError. The space between the top of the scope and the actual declaration is called the Temporal Dead Zone (TDZ).- Function Declarations: The entire function (declaration and body) is hoisted. You can call it before it is defined.
- Function Expressions: Only the variable is hoisted, not the function assignment.