JavaScript Scoping in Real Life: var vs let vs const (and Why It Matters)
This article breaks down JavaScript scoping with var, let, and const, using real-world analogies and practical code examples. Mastering this topic helps you write safer, more predictable code.
📋 Agenda
What is Scope in JavaScript?
var
: Function Scoped & Hoistedlet
: Block Scopedconst
: Block Scoped & Immutable BindingReal-Life Gotchas & Examples
Summary & Takeaways
🧠 1. What is Scope in JavaScript?
In JavaScript, scope refers to the accessibility or visibility of variables. It's the context in which variables are declared and where they can be used.
Think of it like rooms in a house:
If you declare a variable in the living room (global scope), you can access it anywhere.
But if you declare it inside a bedroom (function or block), it's only available there.
Types of Scope in JavaScript:
Global Scope — Variables declared outside any function or block.
Function Scope — Variables declared within a function.
Block Scope — Variables declared within a block
{}
(like in loops,if
statements).Module Scope — Variables in ES6 modules are scoped to the module.
Why Scope Matters:
Avoids variable name collisions
Prevents accidental overwrites
Helps with memory management and garbage collection
Makes debugging easier by limiting variable lifetimes
Encourages more modular, predictable code
Understanding scope helps explain more advanced JS concepts like closures, hoisting, and context
Here’s a quick overview:
// Global scope
var a = 1;
let b = 2;
const c = 3;
function test() {
// Function scope
var x = 10;
let y = 20;
const z = 30;
console.log(x, y, z);
}
if (true) {
// Block scope
let blockScoped = 'I live here only';
}
Understanding scope is the foundation for understanding how variables behave, how hoisting works, and why let
/const
are safer than var
in most situations.
🚧 2. var
: Function Scoped & Hoisted
function demo() {
if (true) {
var x = 5;
}
console.log(x); // 5 — `var` is function-scoped, not block-scoped
}
var
is:
Function-scoped: Only scoped to the function, not block
Hoisted: Moved to the top of its function
Initialized as undefined during hoisting
This means that even if declared later in a function, it can still be accessed — though its value will be undefined
until the actual line is run.
console.log(foo); // undefined
var foo = 'hello';
Bad for:
Loops or
if
blocks where you expect local behaviorAccidentally overwriting values in nested logic
✅ Use let
or const
unless you have a specific reason to use var
(rare).
Fun fact: var
was the only way to declare variables until ES6 introduced let
and const
in 2015.
🧱 3. let
: Block Scoped
if (true) {
let a = 10;
}
console.log(a); // ReferenceError
let
is:
Block-scoped (the
{}
block matters)Hoisted but not initialized, so accessing it before declaration throws an error (temporal dead zone)
console.log(b); // ReferenceError
let b = 5;
Great for:
Loops
Conditional blocks
Safer variable declarations
Interesting insight: Because of temporal dead zone (TDZ), using let
or const
prevents you from accidentally referencing a variable too early — making your code more robust.
🔒 4. const
: Block Scoped & Immutable Binding
const PI = 3.14;
PI = 3.1415; // ❌ TypeError
const
is just like let
in scope but with one key difference:
You can’t reassign the variable itself
But you can mutate objects or arrays it holds:
const arr = [1, 2, 3];
arr.push(4); // ✅
arr = [1, 2]; // ❌ TypeError
Use const
when you don’t intend to reassign — it signals stability to your future self and teammates.
Interesting tip: Many codebases use const
by default and only switch to let
if reassignment is needed. It helps prevent accidental mutations.
🧪 5. Real-Life Gotchas & Examples
Example 1: Loop with var
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 3, 3, 3
Why? Because var
is function scoped. All callbacks share the same i
.
Fix with let
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 0, 1, 2
Bonus gotcha: When using closures inside loops with var
, you often run into unexpected behaviors.
Another common mistake:
if (!foo) {
var foo = 'I am declared later';
}
console.log(foo); // 'I am declared later'
Because of hoisting, foo
is actually declared at the top.
📌 Summary
Here’s a quick reminder of the key takeaways:
var
is function-scoped and hoisted withundefined
as its initial value. It’s flexible but dangerous in large apps.let
is block-scoped and hoisted without initialization. It’s the modern go-to for mutable variables.const
is block-scoped, hoisted without initialization, and cannot be reassigned — perfect for constants and signals of immutability.
✅ Prefer let
and const
in modern JavaScript.
Use let
when the value might change, and const
when it shouldn’t.
Fun design principle: Use const
by default and upgrade to let
only when necessary — this results in safer, more maintainable code.
Want more advanced JS deep dives like closures, async traps, or module patterns?
Subscribe for the next one!