These questions ask where a binding can be used, whether an assignment or declaration is valid, and which same-named binding a reference finds. Solve them by locating the declaration before reasoning about the stored value.
Draw the scope, then classify the change
Mark the nearest enclosing block and function for each declaration. A let or const declared in an if, while, or ordinary block is visible there and in nested scopes, but not after that block. A let declared by a for initializer belongs to the loop. A var declared inside an if that is inside a function belongs to the containing function, so the if block alone does not hide it.
| Question to ask | Rule to apply |
|---|---|
| Is the name visible here? | Start in the current lexical environment and move through enclosing environments until the name is found. The local variables of whichever function made the call are not inserted into this search. |
| Is this assignment allowed? | A let binding may receive another value; for example, let total = 1 followed by total = 3 changes total. Assigning a different value to an initialized const binding throws a TypeError. |
| Is the object itself changing? | const fixes the binding, not the internals of a mutable value. After const settings = Object.create(null), assigning settings.mode changes the object without replacing settings. |
| Is this a declaration conflict? | Two let or const declarations with the same name in one lexical scope cause a SyntaxError. The same spelling in a genuinely nested scope creates a separate binding that shadows the outer one there. |
| Can outside code read a function local? | Parameters and bindings declared inside a function are not directly resolved outside it. A returned closure may retain internal access, but that does not make those names global. |
Remember one declaration-specific detail: const must receive an initializer as part of its declaration, while let may begin without one. Scope, permission to reassign, and mutability of an object are separate questions, so answer them in that order.
Where the mistakes come from
| Tempting route | How to reject it |
|---|---|
| Giving let or const function-wide reach, or letting a loop declaration escape | Both declarations are lexical. Draw the exact block or loop boundary and include only scopes nested inside it. |
| Treating let as immutable or treating const as only a scope marker | let permits reassignment. const requires initialization and rejects later rebinding, so scope and reassignment are distinct axes. |
| Calling every repeated spelling a duplicate declaration | Same-name lexical declarations conflict only in the same scope. A declaration in a nested scope shadows the outer binding without replacing it. |
| Assuming function parameters and locals become directly available after the call | Those bindings remain within the function's lexical environment. Returning a value or a closure does not expose the local names for outside lookup. |
Try it
A var declaration appears inside an if block that is itself inside a function. Which statements are true? Select all that apply.
- The binding belongs to the containing function scope
- The binding is accessible directly outside the containing function
- Code later in the same function can use it after the assignment executes
- The binding ceases to exist as soon as the if block ends
Given const list = [1], what happens when list.push(2) is called?
- A TypeError occurs because every const value is deeply immutable
- The call is ignored and list remains [1]
- The call succeeds and list becomes [1, 2]
- A new binding named list is created
Which statements describe lexical name lookup in JavaScript? Select all that apply.
- A function searches the local variables of whichever function called it
- The current lexical environment is checked before an enclosing one
- Property prototypes determine which lexical binding wins
- Lookup can continue through successively enclosing lexical environments