Scope and Closures
How JavaScript decides which variable a name refers to, and how a function keeps hold of the variables it was born next to. Declarations and hoisting, the execution model, closures, and the this binding are the classic JavaScript screen — most rounds open here.
part ofJavaScriptoverview, primer and where to startread it →on this pageshowhide
explore
- Declarations and Hoisting12 questions
- var vs let and const4 questions
- Hoisting and the Temporal Dead Zone4 questions
- Function Declarations vs Expressions4 questions
- Execution Model8 questions
- Execution Contexts and the Scope Chain4 questions
- Strict Mode4 questions
- Closures18 questions
- Closure Capture Semantics4 questions
- Loop Variable Capture5 questions
- IIFE and the Module Pattern5 questions
- Closure Memory Retention4 questions
- The this Binding18 questions
- Binding Rules and Precedence4 questions
- call, apply and bind6 questions
- Arrow Functions and Lexical this4 questions
- Losing this in Callbacks4 questions
questions
page 2 of 2In JavaScript, which redeclarations of the same name does var allow that let and const reject, and when does declaring a name in a nested block become a SyntaxError rather than legal shadowing?
basics
~20 svar silently allows the same name twice in one scope; let and const make it a SyntaxError. Shadowing the same name in a nested block is always legal for let and const, but a var inside the block collides with an outer let because var is hoisted past the braces.
In a non-strict JavaScript script, what happens when a function assigns to a name that is not declared anywhere in the scope chain — for example `function init() { cache = {}; }` — and why is it a bug worth catching?
basics
~20 sThe assignment walks the scope chain, finds no binding, and in non-strict script code creates a property on the global object instead of throwing. That accidental global outlives the call and is shared by every other script on the page or process.
Why does assigning to a property of an object that was passed to Object.freeze() do nothing at all in sloppy mode, yet throw a TypeError under "use strict"?
basics
~10 sThe assignment fails in both dialects. Sloppy mode discards the failure and continues; strict mode reports it as a TypeError. Strict mode changes the reporting of a failed write, never whether the write succeeds.
Why do `call`, `apply` and `bind` have no effect on an arrow function's `this`, and what happens to the optional `thisArg` argument of `Array.prototype.map` when the callback is an arrow?
basics
~20 sAn arrow has no this binding to overwrite — it reads this from the enclosing scope. So call, apply, bind and the thisArg of methods like map still pass a value, but the arrow never consults it. Arguments are still forwarded normally.
Besides having no `this` of their own, what else do JavaScript arrow functions lack compared with ordinary functions, and what happens if you call `new` on one?
basics
~20 sArrow functions have no own arguments, no super, no new.target, and no prototype property, and they are not constructors: new on an arrow throws a TypeError. They also cannot be generators. All four missing bindings resolve lexically instead.
Why does Array.prototype.slice.call(arguments) turn the arguments object into a real array, and what replaces that idiom in modern JavaScript?
basics
~20 sArray methods are specified as generic: they read only a length property and integer-keyed properties from whatever this they are given. call supplies the array-like arguments object as that this. Modern code uses a rest parameter, Array.from, or spread instead.
How does Function.prototype.bind let you preset arguments as well as this, and what is the trap in its first parameter?
basics
~20 sAny arguments after bind's first are stored and prepended to every later call, giving partial application. The trap is that the first parameter is always the this value, never an argument — so presetting only arguments requires passing a placeholder receiver such as null.
When a JavaScript method is detached and then called as a plain function, this is undefined in some code and globalThis in other code. What decides which, and why is the globalThis outcome the more dangerous bug?
basics
~20 sStrict mode decides it. In strict code a plain call leaves this as undefined, so the first property access throws immediately; in sloppy code the engine substitutes globalThis, so the method silently reads and writes global properties instead of failing.
In a JavaScript call written as `a.b.c.method()`, which object becomes `this` inside `method`, and what rule decides that?
basics
~20 sOnly the immediate base of the property access becomes this, so it is a.b.c — not a. Implicit binding uses the object the method was read from, and everything further left in the chain is irrelevant.
A createLogger(config) factory captures the config object it was given. Weeks later, a caller mutates config.level after several loggers were created, and every existing logger changes behaviour. Explain why this happens and how you would design the factory so captured state cannot be changed underneath it.
basics
~20 sThe closure captured the parameter binding, which holds a reference to the caller's object, so property mutations are visible immediately to every logger. To stop it, read the values you need into local bindings at creation time, or copy or freeze the object before capturing it.
A reviewer finds `for (var i = 0; i < ids.length; i++) { load(ids[i]).then(r => { results[i] = r; }); }` and every response ends up at one index instead of spread across the array, leaving holes. Diagnose it, and say what you would change so this class of bug cannot recur.
basics
~20 sAll the promise callbacks share the single var binding for i, and they run only after the loop has finished, when i equals ids.length. So every write lands on that one out-of-range index. Declare the counter with let, or build the array from returned values instead of index writes.
A JavaScript module is built like this: `const mod = (function () { function a() { return 'original a'; } function b() { return a(); } return { a, b }; })();`. A caller then does `mod.a = () => 'patched a';`. Why does `mod.b()` still return 'original a', and what does that tell you about what the returned object holds?
basics
~20 sInside the wrapper, b calls the local binding a, not the property mod.a. The returned object holds a copy of the function value at return time, so replacing the property rewires only outside callers; every internal call keeps going to the original.
In JavaScript, what happens when you put a `function` declaration inside an `if` block, and how does the result differ between strict-mode code such as an ES module and a sloppy-mode script in a browser?
basics
~20 sIn strict-mode code, including ES modules, a function declared inside a block is scoped to that block and is not visible after it. In sloppy-mode scripts, legacy web semantics also create a function-scoped variable that is only filled in when the block actually runs.
A JavaScript app throws `ReferenceError: Cannot access 'config' before initialization` at startup. What does that specific message tell you, and how do you track down the cause?
basics
~20 sThat message means the binding exists in scope but is still uninitialized: something declared with let, const, or class was accessed before its declaration ran. Find what executes during the scope's setup and reaches the name early, rather than looking for a missing declaration.
At the top level of a classic (non-module) script, var declarations become properties of globalThis but let and const do not. What problems does that cause, and how does the picture change in an ES module?
basics
~20 sTop-level var in a classic script creates a non-configurable property on the global object, so separate scripts can collide with each other and with built-in globals. let and const instead create bindings in a separate global scope that is not reachable as a property. In an ES module neither touches globalThis.
You add "use strict" to the top of a large legacy browser script and parts of the page stop working. What classes of failure should you expect, and how would you roll the change out safely?
basics
~20 sExpect two classes: parse-time syntax errors that kill the entire script before a line runs, and runtime errors on code paths that previously failed quietly. Roll out per function rather than per file, and lint for undeclared globals first.
In a JavaScript class, what actually differs between defining `handleClick = () => { ... }` as an instance field and defining `handleClick() { ... }` as a prototype method?
basics
~20 sThe arrow field is an own, enumerable property created fresh on every instance, with this locked to that instance. The method lives once on the prototype, is non-enumerable, and takes this from the receiver at call time.
You are writing a generic wrapper that logs every call to a method it is given. How do you make sure the wrapped method still receives the correct this, and where does that approach break down?
basics
~20 sReturn a regular function that invokes the target with fn.apply(this, args). The wrapper picks up the receiver from its own call site and forwards it. An arrow wrapper cannot do this, and calling fn(...args) drops the receiver entirely.
A JavaScript service class passes its own tests but throws "Cannot read properties of undefined" once other modules register its methods as callbacks. How do you confirm the receiver is being lost, and what would you change so this cannot recur across the codebase?
basics
~20 sConfirm it by checking the registration sites for a bare method reference and by reproducing the detachment directly, or by asserting the receiver inside the method. Then remove the hazard at the boundary: export closures or pre-bound handlers instead of raw methods, rather than asking every caller to remember.
A sloppy-mode function `function User(name){ this.name = name; }` is invoked as `User('ada')` with no `new`. What is `this` inside the call, what happens to the assignment, and why is this bug hard to spot in production?
basics
~20 sWith no new and no receiver, default binding applies: this is globalThis in sloppy mode, so the assignment creates a global property and the call returns undefined. Nothing throws, so the failure surfaces far from its cause.
In a classic non-module script, what does `console.log(typeof foo); var foo = 1; function foo() {} console.log(typeof foo);` print, and why?
basics
~20 sIt prints "function" and then "number". When the scope is entered, the function declaration wins and puts the function in the shared binding; the var declaration does not overwrite it. Later the assignment foo = 1 runs and replaces the value.
What happens to the this value stored by Function.prototype.bind when the resulting function is invoked with the new operator?
basics
~20 sThe bound this is ignored. Construction creates a fresh object and uses that as the receiver, so new is the one call form a hard binding cannot override. Bound arguments are still prepended, and instanceof against the target function still works.
JavaScript identifier resolution is normally static — you can tell from the source which binding a name refers to. Which two legacy constructs break that guarantee, how does each one do it, and what do engines and minifiers do in response?
basics
~20 sThe with statement and non-strict direct eval break it. with pushes an object onto the scope chain so lookups depend on that object's properties at runtime, and direct eval can add bindings to the calling scope. Both defeat static analysis, so engines deoptimize and minifiers stop renaming.
A memoize() helper in JavaScript keeps its cache in a closure. What memory questions would you settle before shipping it into a long-running process?
basics
~20 sSettle four things: whether the cache is bounded in size or age, what each entry transitively retains, whether object arguments should be held weakly so callers can be collected, and how long the memoized function itself lives.
Now that most JavaScript ships as ES modules, where does an immediately invoked function expression still earn its place in a codebase, and where has it become noise you should delete?
basics
~20 sDelete it wherever a module file or a block already provides the scope: wrapping a whole module body buys nothing. Keep it for code that cannot be a module — injected snippets, embeds, build-free pages — and for an async wrapper where await is otherwise illegal.
showing 31–56 of 56