Scope in JavaScript
Introduction
Scope decides where a variable name is visible and who can access it. JavaScript has function scope (var) and block scope (let, const). Understanding scope prevents bugs where inner code accidentally overwrites outer data—or cannot see values you expected.
Prerequisites
Global Scope
Variables declared at the top level of a script (outside functions) live in global scope:
// Global binding
const appTitle = "Learn JS";
function showTitle() {
console.log(appTitle);
}
showTitle();In browsers, global bindings attach to window; in Node.js, similar globals exist (globalThis). Prefer few globals in real apps.
Block Scope (let and const)
Bindings declared inside { } blocks are block-scoped:
// Block-scoped let
const basePrice = 10;
if (basePrice > 0) {
const taxRate = 0.08;
const total = basePrice * (1 + taxRate);
console.log(total);
}
// console.log(taxRate); // ReferenceError — not visible outside blockconst and let do not leak out of if, for, or while blocks.
Function Scope (var and functions)
var is scoped to the nearest function, not block:
// var leaks out of block (legacy behavior)
function demoVar() {
if (true) {
var count = 1;
}
console.log(count);
}
demoVar();// let stays inside block
function demoLet() {
if (true) {
let count = 1;
console.log(count);
}
// console.log(count); // ReferenceError
}
demoLet();Nested Scope and Shadowing
Inner scopes can reuse names that hide outer ones (shadowing):
// Outer and inner name
const label = "outer";
function printLabel() {
const label = "inner";
console.log(label);
}
printLabel();
console.log(label);Shadowing is legal but can confuse readers—use distinct names when possible.
Scope Chain (Intuition)
When code reads a variable, JavaScript searches:
- Current scope
- Outer enclosing scopes
- Global scope
If nowhere is found → ReferenceError.
// Inner function reads outer variable
const theme = "dark";
function render() {
console.log(`Theme: ${theme}`);
}
render();Closures (later) build on this chain.
const in Loops (Preview)
const in for loops works with modern loop forms:
// const with for...of (each iteration new binding in some forms)
const items = ["a", "b", "c"];
for (const item of items) {
console.log(item);
}Classic for (let i = 0; ...) uses block-scoped let per iteration behavior—important when callbacks appear later.
Mini Example: Block vs Function Scope
// Compare let vs var in a block
function compareScope() {
console.log("--- let ---");
if (true) {
let x = 10;
console.log(x);
}
console.log("--- var ---");
if (true) {
var y = 20;
}
console.log(y);
}
compareScope();FAQ
Should I use global variables?
Minimize globals. Pass data via parameters, return values, or modules. Globals make testing and reasoning harder.
What is the temporal dead zone (TDZ)?
let/const exist but cannot be read before their declaration line in the same scope—unlike var hoisting surprises. You will meet this when learning hoisting in depth.
Does each file have its own scope?
ES modules (later) give files module scope. Scripts share global scope unless bundled otherwise.
What comes next?
Strings and template strings.