In JavaScript, a function `show` is declared at the top level of a file and logs a variable `value`; a separate function `run` declares its own local `const value = 'inner'` and then calls `show()`. Which binding does `show` resolve, and what rule decides it?
answer
- decided by the source, not the stack
- captured when the function was created
- the caller's locals are not on the chain
- lexical, not dynamic, scoping
- callbacks carry their definition scope
basics
~20 sIt resolves the top-level value, not the caller's. JavaScript scoping is lexical: a function's outer environment link is fixed where the function is written in the source, so the caller's local bindings are never on its scope chain.
solid answer
~50 s`show` logs the top-level `value` and never sees `run`'s local one. When the `show` function object is created, it captures a reference to the environment it was *written* in — the top-level environment — and stores it as its outer link. Calling `show()` pushes a fresh execution context whose environment record holds only `show`'s own parameters and locals, and whose outer link is that captured top-level environment. `run`'s environment is nowhere on that chain, no matter that `run` is the caller and is still on the call stack. This is lexical (static) scoping, and the opposite discipline — dynamic scoping, where a name would resolve against the caller — is not how JavaScript works for variables. The one thing that *does* follow the call site is the `this` value, which is a separate mechanism from identifier resolution.
code
javascript · 12 linesconst value = 'outer';
function show() {
console.log(value);
}
function run() {
const value = 'inner'; // real, live, and irrelevant to show
show();
}
run(); // "outer"go deeper
Know the answer is the top-level value and be able to say the phrase "JavaScript uses lexical scoping — where the function is written decides what it can see" without hesitating.
Explain the mechanism: the function object captures its defining environment when it is created, each call adds a fresh record whose outer link is that captured environment, and the caller is nowhere on the chain. Show you can predict the output of the rewritten, nested version too.
Use it to reason about design: callbacks carry their own scope, so data must arrive as arguments; faking dynamic scope through shared module state creates order-dependent bugs. Be ready to explain why lexical scoping is what lets tools analyse code without running it.
Own the tradeoff. Lexical scoping makes dependencies explicit and analysable but pushes cross-cutting context (request ids, tenancy, tracing) into explicit parameter passing or a deliberate context-carrying mechanism; be able to argue when that plumbing cost is worth paying versus a shared ambient store.
## Two candidate rules There are two coherent ways a language can answer "which `value` does this function mean?" - **Lexical (static) scoping**: the answer is decided by where the function's source text sits — which blocks and functions physically enclose it. You can determine it by reading the file. - **Dynamic scoping**: the answer is decided by who is on the call stack at the moment the name is evaluated. The same function can mean different bindings on different calls. JavaScript chose lexical scoping for variables, and interviewers ask this question because a reasonable person coming from a scripting background — or reasoning from "the caller is still on the stack, so its variables must be around" — guesses dynamic. ## The mechanism: where the outer link comes from When the engine evaluates a function declaration or expression, it creates a function object and stores in it a reference to the **environment that is currently running** — the environment the function literal appears in. That stored reference becomes the function's outer environment for every future call. Calling the function then does two things: 1. Creates a new execution context with a fresh environment record for that call's parameters and local declarations. 2. Sets that record's outer link to the environment captured at *creation* time. Notice what step 2 does **not** consult: the caller. The call stack and the scope chain are different structures. The stack records who is waiting for whom to return; the scope chain records which environments enclose this code in the source. They happen to line up when a function is called from where it was written, which is why the difference is invisible until you write the case that separates them: ```js const value = 'outer'; function show() { console.log(value); } function run() { const value = 'inner'; show(); } run(); // "outer" ``` Inside `run`, the `value` binding is real and live — `run`'s own body would log `"inner"`. But `show`'s chain is `show's call environment -> top-level environment -> global`, and `run`'s environment simply is not on it. ## Why this matters beyond the puzzle **Callbacks keep their own scope.** Passing a function into a library does not move it into the library's scope; it still sees only what surrounded its definition. That is why you pass data as arguments rather than hoping the receiving function's locals are visible. ```js const label = 'module'; function print() { console.log(label); } function withLocal(fn) { const label = 'local'; fn(); } withLocal(print); // "module" ``` **Readability and tooling.** Because the binding for every name is decidable from the source, a reader (and a linter, and a minifier) can determine what a function depends on without running it. A dynamically scoped language cannot promise that. **Nesting adds links, not calls.** A function declared *inside* another does have the enclosing function's environment on its chain — again because of where it is written, not because of who calls it: ```js function outer() { const secret = 42; function inner() { return secret; } // secret is on inner's chain by position return inner(); } ``` ## Shadowing falls out of the same rule Resolution walks outward and stops at the first record that has the name. So a parameter or local with the same name as an outer binding hides it for the whole of that function — and only for that function. Shadowing is not a special feature; it is the natural consequence of "first match wins" on a chain that is ordered from innermost outward. ## The exception people conflate: `this` One thing in a traditional function *is* decided at the call site rather than lexically: the `this` value. Candidates often generalise from that and conclude that variable lookup must work the same way. It does not — `this` is bound by the call, identifiers are resolved by the chain. Keeping those two mechanisms separate in your head is most of what this question is testing. ## How to answer it out loud Say the result first (`"outer"`), then name the rule (lexical scoping), then give the mechanism in one sentence (the function captured its defining environment as its outer link when it was created, and the caller's environment is not on that chain). If you want to demonstrate depth, add that the call stack and the scope chain are separate structures that only coincidentally align.
- How would the output change if `show` were declared inside `run` instead of at the top level?It would log `"inner"`. Declaring `show` inside `run` means the function object is created while `run`'s environment is running, so that environment becomes its outer link and `run`'s local `value` is now on its chain — resolved before the top-level one. Nothing about the call changed; only the position of the source text did.
- If the caller's variables are not reachable, what should a function do when it genuinely needs the caller's data?Take it as a parameter, or close over it deliberately by defining the function where that data lives. Those are the only two supported channels. Reaching for a shared module-level or global variable to fake dynamic scoping works but creates hidden coupling: the function's behaviour then depends on execution order rather than on its inputs.
- Does anything in JavaScript resolve a name against the caller rather than the definition site?Not for ordinary identifiers. The nearest thing is the legacy `with` statement and non-strict direct `eval`, which can bend what is visible at runtime, and they are exactly why those constructs are discouraged. The `this` value is decided by the call, but `this` is a binding provided by the call, not an identifier looked up along the chain.
saying these in an interview costs you the question
- Says the called function sees the caller's local variables
- Claims scope is determined by the call stack
- Thinks passing a callback moves it into the receiving scope
- Assumes variable lookup follows the same rule as this
- Says the closest declaration in the file wins regardless of nesting