What you'll learn
Scope determines where a name can be used; hoisting determines when its declaration is registered. A precise model prevents mysterious ReferenceErrors and accidental shared state.
By the end of this lesson, you'll be able to:
- Trace a name through lexical scopes
- Predict hoisting and temporal-dead-zone behavior
- Choose declarations that keep state local
Core mental model
Use this decision table as a compact reference. Focus on what each tool means and when it earns its place in production code.
| Concept | What it means | Decision rule |
|---|---|---|
| Lexical scope | Visibility follows source-code nesting | Declare data in the narrowest block that owns it |
| Hoisting | Declarations are registered before execution | Do not rely on use-before-declaration |
| TDZ | let/const exist but cannot be read before initialization | Declare bindings close to first use |
Professional workflow
Build the behavior in small, observable steps. Each step should leave something you can inspect or test.
- Describe the binding and scope boundary: inputs, outputs, state, timing, and expected failures.
- Implement the smallest correct path with names that expose intent.
- Add edge cases and failure handling before introducing abstractions.
- Verify behavior with realistic data and one deliberately adversarial example.
- Refactor only after the observable behavior is protected.
Make behavior observable
Guided code lab
Follow the scope chain
The inner function reads its own parameter and then closes over the nearest matching outer binding.
const label = "global";
function makeReporter(label) {
const prefix = "Course";
return function report() {
console.log(prefix + ": " + label);
};
}
makeReporter("JavaScript")();Block scope prevents leakage
A binding created inside a branch belongs only to that block.
function priceLabel(price) {
if (price < 0) {
const message = "Invalid price";
return message;
}
const message = "$" + price.toFixed(2);
return message;
}
console.log(priceLabel(12));Production practice
Contract
A binding should be visible only where its invariant is understood.
Verification
Mark each declaration and predict every lookup before running the program.
Operations
Treat unexpected globals and shadowed names as defects during review.
Common failure mode
Independent workshop
Refactor a script with global counters and shadowed names into small functions with block-scoped state.
Your finished workshop must include:
- No var declarations or implicit globals
- A written scope trace for one nested lookup
- Tests for a valid and invalid input
Definition of done
Recap & quick check
Key takeaways
- Scope is lexical
- let and const are block scoped
- The TDZ makes premature access an error
- Narrow visibility reduces coupling
Quick check
1. What decides lexical scope?
2. Can const be read before its declaration line?
3. Which declaration is function-scoped?
Keep the workshop. Later modules deliberately build on these decisions, so each exercise can become part of your final portfolio architecture.