Understand how JavaScript scope works — global, function, and block scope — and master the powerful concept of closures.
Scope determines where variables are accessible in your code. JavaScript has several types of scope, and understanding them is critical for writing bug-free code.
Global scope — variables declared outside any function or block are globally accessible:
const appName = 'Kodojo'; // global scope
function greet() {
console.log(appName); // accessible here
}
console.log(appName); // accessible here tooGlobal variables are available everywhere, but having too many pollutes the global namespace and can cause naming conflicts. Avoid creating unnecessary globals.
Function scope — variables declared with var inside a function are only accessible within that function:
function calculate() {
var result = 42;
console.log(result); // 42
}
calculate();
console.log(result); // ReferenceError: result is not definedvar is function-scoped — it does not care about blocks like if or for, only functions:
function example() {
if (true) {
var x = 10; // function-scoped, not block-scoped!
}
console.log(x); // 10 — x is accessible outside the if block
}This is one of the reasons let and const were introduced — they behave more predictably.
let and const are block-scoped — they are only accessible within the nearest enclosing curly braces {}:
if (true) {
let x = 10;
const y = 20;
console.log(x, y); // 10 20
}
console.log(x); // ReferenceError
console.log(y); // ReferenceErrorThis applies to any block — if, for, while, or even a standalone {}:
{
let secret = 'hidden';
}
console.log(secret); // ReferenceErrorBlock scoping is especially important in for loops. With let, each iteration gets its own copy of the variable:
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 0, 1, 2 (correct!)With var, all iterations share the same variable — a classic JavaScript gotcha:
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Logs: 3, 3, 3 (wrong!)This is why modern JavaScript strongly prefers let and const over var.
When JavaScript encounters a variable, it looks it up through the scope chain — starting from the innermost scope and working outward:
const global = 'I am global';
function outer() {
const outerVar = 'I am outer';
function inner() {
const innerVar = 'I am inner';
console.log(innerVar); // found in inner scope
console.log(outerVar); // found in outer scope
console.log(global); // found in global scope
}
inner();
}
outer();The lookup order is: inner scope → outer scope → global scope. If the variable is not found anywhere, JavaScript throws a ReferenceError.
This means inner functions always have access to variables from their parent functions. This is the foundation of closures.
Variable shadowing occurs when an inner scope declares a variable with the same name as an outer one:
const x = 'outer';
function example() {
const x = 'inner'; // shadows the outer x
console.log(x); // 'inner'
}
example();
console.log(x); // 'outer' — unchangedThe inner x hides (shadows) the outer x within that scope. The outer x is still intact outside the function.
A closure is a function that remembers and can access variables from its outer (enclosing) scope, even after the outer function has finished executing. Closures are created every time a function is created.
function createGreeter(greeting) {
// greeting is captured by the closure
return function(name) {
return `${greeting}, ${name}!`;
};
}
const sayHello = createGreeter('Hello');
const sayHi = createGreeter('Hi');
console.log(sayHello('Alice')); // 'Hello, Alice!'
console.log(sayHi('Bob')); // 'Hi, Bob!'Even though createGreeter has returned, sayHello and sayHi still remember their respective greeting values. This is the closure in action.
Common use cases for closures:
Data privacy — hiding variables from outside access:
function makeCounter() {
let count = 0; // private!
return {
increment() { count++; },
getCount() { return count; },
};
}Factory functions — creating specialized functions:
function multiplier(factor) {
return (n) => n * factor;
}
const double = multiplier(2);
const triple = multiplier(3);Callbacks that remember context — event handlers, timers, etc.
Partial application — pre-filling some arguments of a function.
Closures are one of the most powerful patterns in JavaScript. They enable modules, private state, and functional programming techniques.
<p id="output"></p>
<script>
// A counter factory using closures
function makeCounter() {
let count = 0; // private variable
return {
increment() {
count++;
},
decrement() {
count--;
},
getCount() {
return count;
},
};
}
const counter = makeCounter();
counter.increment();
counter.increment();
counter.increment();
counter.decrement();
// count is not accessible directly:
// console.log(counter.count); // undefined
// Only through the closure:
document.getElementById('output').textContent =
'Count: ' + counter.getCount(); // Count: 2
</script>What is a closure?