Closure Memory Leaks — Top JS Interview Questions
Heap usage grows monotonically — closures in Express route handlers retaining req objects cause OOM.
- Closures bundle functions with their lexical scope, enabling data privacy.
- The Event Loop runs microtasks before macrotasks every cycle.
- Prototypal inheritance links objects;
thisdepends on call site. - Hoisting moves declarations; let/const live in the Temporal Dead Zone.
- Promises are microtasks; async/await is syntactic sugar over promises.
- Performance: heavy closure usage can retain memory longer than expected.
Think of a JavaScript interview like a driving test. The examiner doesn't just want to see you turn a steering wheel — they want to know you understand WHY you check mirrors before changing lanes, WHEN to brake, and WHAT happens if you don't. These 50 questions work exactly the same way: they're not trivia, they're probes to see if you truly understand the road, not just the pedals.
JavaScript powers over 98% of websites on the internet, runs on servers via Node.js, and has quietly become one of the most in-demand skills in tech. Whether you're applying at a scrappy startup or a FAANG company, the JavaScript interview is a rite of passage — and it's notoriously tricky because the language itself has sharp edges that even experienced devs fall on.
The real problem with most interview prep is that it focuses on 'what' instead of 'how.' You might know that var is function-scoped, but do you understand how the Execution Context and the Scope Chain actually handle it during the creation phase? Understanding these internals is what separates a junior developer from a senior engineer who can debug complex race conditions in an event-driven architecture.
By the end of this article, you will master the 50 most critical questions, from the nuances of Prototypal Inheritance and Closures to the modern complexities of the Event Loop and Asynchronous patterns. We provide production-grade code for every answer, ensuring you aren't just memorizing definitions, but building functional intuition.
Understanding the Foundation: Closures and Scope
One of the most frequent 'Senior' level questions is: 'Explain closures and how they impact memory.' A closure is the combination of a function bundled together with references to its surrounding state (the lexical environment). In simpler terms, a closure gives a function access to its outer scope even after the outer function has finished executing. This is foundational for data privacy and factory patterns in JavaScript.
But here's the production trap: each time you create a closure, you keep the entire lexical scope alive. If that scope contains a large array or DOM reference, memory won't be released until the closure itself is garbage collected. That's why you'll see memory leaks in Node.js servers that create closures inside routes without careful cleanup.
req object.The Event Loop: Asynchronous JavaScript Internals
Interviewers love to test your understanding of the Event Loop. They often ask: 'What is the difference between a Task (Macrotask) and a Microtask?' In the JavaScript runtime, Microtasks (like Promise.then and process.nextTick) always execute before the next Macrotask (like setTimeout or setInterval) in the loop. Understanding this priority is essential for predicting execution order in complex applications.
This order directly affects your UI rendering. A long-running microtask queue can freeze the page because rendering is a macrotask that waits for all microtasks to clear. That's why heavy synchronous work inside Promise.then blocks can degrade perceived performance.
.then() can block the UI for seconds.Prototypal Inheritance and the `this` Keyword
Every object in JavaScript has an internal [[Prototype]] link that points to another object. When you access a property that doesn't exist on the object, JavaScript walks the prototype chain until it finds the property or reaches null. This is prototypal inheritance — a dynamic, delegation-based model quite different from classical inheritance.
The this keyword is determined entirely by the call site, not where the function is defined. That's why methods lose their context when passed as callbacks. The fix: arrow functions (lexical this), .bind(), or storing a reference to this outside.
this defaults to the global object (window) in non‑strict mode, or undefined in strict mode. Always check the call site.this in React class components was the #1 cause of cannot-read-property errors in 2015 codebases.this is undefined, your function is being called without context.this is bound by call site, not definition.this.this.Hoisting and the Temporal Dead Zone
JavaScript hoists variable and function declarations to the top of their scope. But var is initialized with undefined, while let and const are hoisted to a 'temporal dead zone' — they exist but cannot be accessed until the declaration line is reached. Accessing them before that throws a ReferenceError.
This is a frequent source of bugs when code relies on order of declarations. Senior devs treat let and const as block‑scoped and never assume they're initialized before the declaration.
- All declarations are hoisted to the top of their scope.
vargets initialized withundefinedimmediately.letandconstare hoisted but not initialized—they live in the TDZ.- TDZ ends when the actual declaration line executes.
- Accessing a variable in its TDZ throws a ReferenceError.
let inside a switch/case block without braces.case clauses share the same block scope, causing redeclaration errors.case in curly braces to create a new block scope.let and const are hoisted but not initialized.const by default, let when reassignment needed.Promises, Async/Await and Error Handling
Promises represent the eventual completion (or failure) of an asynchronous operation. They replaced callbacks and enable chaining with .then(). async/await is syntactic sugar that makes promise chains read like synchronous code. But under the hood, await waits for a promise to settle — it doesn't block the event loop.
A critical detail: unhandled promise rejections still crash Node.js (since v15). Always append a .catch() or use a try/catch around await. Missing this causes silent data loss in production when an API call fails.
.then() handler to maintain the chain. A missing return turns the next handler into a dangling promise.process.on('unhandledRejection', handler) globally, but better: catch every promise.async/await is a wrapper around promises.await deserves a try/catch.Closure Memory Leak in a Node.js Microservice
req object, which after the response was sent, still held a reference to a large parsed payload via an inner function used for logging. The closure never released it.- Closures retain their lexical scope as long as any reference exists.
- Always profile memory after adding closures in long-lived processes.
- Limit closure scope to only the variables you need — avoid capturing large objects.
setTimeout(fn, 0) runs after a Promise.then() even though both appear in sequence.console.log markers. Microtasks (promises) drain before the next macrotask (setTimeout).await the promise inside the async function. Without await, the function returns a pending promise.for loop with setTimeout inside logs the same final value multiple times.let (block scope) or an IIFE to capture each iteration's value.let i = 0; i < n; i++ or wrap setTimeout in an IIFE: (function(j){ setTimeout(() => console.log(j), 0); })(i).Key takeaways
this is bound by call site; use arrow functions or .bind() to preserve context.let/const have a Temporal Dead Zoneawait needs a try/catch or the promise must have a .catch() to avoid unhandled rejections.Common mistakes to avoid
5 patternsMemorising syntax before understanding 'Hoisting' and 'Temporal Dead Zone'
let throws ReferenceError before initialization; misuses var thinking it's block-scoped.console.log before and after declarations.Skipping practice with `this` keyword binding (call, apply, bind)
this is undefined in event handlers.console.log(this) inside the function to confirm.Ignoring the difference between `==` and `===`
0 == false returns true.=== (strict equality) by default. Enable ESLint rule eqeqeq to enforce it.Not returning a promise from an `async` function
undefined instead of the expected resolved value.async functions always have a return statement when they need to provide a value. The returned value is automatically wrapped in a resolved promise.Assuming `setTimeout(fn, 0)` runs immediately
setTimeout to defer execution still runs after all synchronous code and microtasks, causing incorrect order.setTimeout adds a macrotask. Use Promise.resolve().then() for 'as soon as possible' after current task.Interview Questions on This Topic
Explain closures and give a real-world use case.
useCallback which relies on closure to memoize functions. Example: a counter that increments privately without exposing the variable to global scope.Frequently Asked Questions
That's JavaScript Interview. Mark it forged?
3 min read · try the examples if you haven't