🕶️ Variable Shadowing in JavaScript — A Complete Guide

1 What Is Shadowing?
Term | In one sentence |
Shadowing | Declaring a new variable with the same name in a nested (inner) scope, thereby hiding the outer one only inside that inner scope. |
let title = 'Global';
function setTitle() {
let title = 'Local'; // ← shadows the global title
console.log(title); // Local
}
setTitle();
console.log(title); // Global
2 Where Can a Variable Live?
Scope | Created where? | Visible where? |
Global | Outside every function / block | Everywhere (for var : also on window ) |
Function | Inside a function body | Only inside that function |
Block | Inside { … } , if , for , switch , … | Only inside that block (works only for let / const ) |
3 Scoping Rules by Keyword
Keyword | Respects block scope? | Respects function scope? |
var | ❌ (no – it leaks) | ✅ |
let | ✅ | ✅ |
const | ✅ | ✅ |
4 Six Core Shadowing Scenarios (Like-for-Like)
# | Outer declaration | Inner scope | Result | Why? |
1 | var (global) | var in function | Shadow | New function-scoped variable |
2 | var (global) | var in block | Same variable | var ignores block ⇒ leaks |
3 | let (global) | let in function | Shadow | Function scope respected |
4 | let (global) | let in block | Shadow | Block scope respected |
5 | const (global) | const in function | Shadow | Function scope respected |
6 | const (global) | const in block | Shadow | Block scope respected |
4.1 var
global ➜ var
in function ✔️ (shadowing)
jsCopyEditvar animal = 'Cat'; // global var
function showAnimal() {
var animal = 'Dog'; // NEW variable (function-scoped)
console.log(animal); // Dog
}
showAnimal();
console.log(animal); // Cat
Inner animal
shadows the global one only inside showAnimal
.
4.2 var
global ➜ var
in block ⚠️ (no shadowing — same variable!)
jsCopyEditvar counter = 1; // global var
{
var counter = 2; // SAME variable (block ignored)
console.log(counter); // 2
}
console.log(counter); // 2 ← value leaked out
Because var
ignores block scope, the assignment inside {}
simply reassigns the global counter
.
4.3 let
global ➜ let
in function ✔️ (shadowing)
jsCopyEditlet user = 'Alice'; // global let
function setUser() {
let user = 'Bob'; // NEW variable (function scope)
console.log(user); // Bob
}
setUser();
console.log(user); // Alice
let
is function-scoped, so the inner declaration creates a fresh variable that hides the global one inside the function.
4.4 let
global ➜ let
in block ✔️ (shadowing)
jsCopyEditlet mode = 'light'; // global let
{
let mode = 'dark'; // NEW block-scoped variable
console.log(mode); // dark
}
console.log(mode); // light
Blocks do matter to let
, so the inner mode
is a distinct variable that disappears after the block.
4.5 const
global ➜ const
in function ✔️ (shadowing)
jsCopyEditconst rate = 0.1; // global const
function localRate() {
const rate = 0.2; // NEW const (function scope)
console.log(rate); // 0.2
}
localRate();
console.log(rate); // 0.1
Just like let
, a function-scoped const
hides the global one only inside the function.
4.6 const
global ➜ const
in block ✔️ (shadowing)
jsCopyEditconst apiUrl = 'prod.example.com';
{
const apiUrl = 'dev.example.com'; // NEW const (block scope)
console.log(apiUrl); // dev.example.com
}
console.log(apiUrl); // prod.example.com
Block-scoped const
behaves the same way as block-scoped let
.
5 Illegal Shadowing & Redeclaration
Pattern | Code | Outcome | Reason |
Redeclare in same scope | let x = 1; let x = 2; | ❌ SyntaxError | let /const cannot be redeclared |
var after let /const in same scope | let y = 3; var y = 4; | ❌ SyntaxError | Hoisting makes scopes overlap |
Mix in different scopes | var z = 5; { let z = 6; } | ✅ | Different (block) scope ⇒ legal |
6 Shadowing vs Reassignment vs Redeclaration
Action | Creates a new variable? | Example | Legal? |
Shadowing | ✅ (inner scope) | let a = 1; { let a = 2; } | Yes |
Reassignment | ❌ | let a = 1; a = 2; | Yes |
Redeclaration (same scope) | ❌ | let a = 1; let a = 2; | No (var is the only exception) |
7 Mixed-Keyword Cheatsheet
Outer keyword | Inner keyword | In function | In block |
var | let / const | Shadow ✔️ | Shadow ✔️ |
let / const | var | Shadow ✔️ | ❌ Illegal (same hoisted scope) |
8 Why var
Is Tricky Inside Blocks
if (true) {
var leaked = '👻';
}
console.log(leaked); // 👻 (Block was flattened)
var
is hoisted to the nearest function or global scope, so the name isn’t shadowed—it’s the same variable, now reassigned.
9 Best Practices to Dodge Shadow-Bugs
Prefer
let
/const
– block scope prevents leaks.Unique, descriptive names – accidental shadowing disappears.
Turn on ESLint’s
no-shadow
rule – your linter becomes a bodyguard.Never mix
var
withlet
/const
in the same file unless you really know why.
🔑 One-Line Memory Hook
Shadowing = same name, new (inner) scope.
If the scopes overlap (because ofvar
or redeclaration ), you don’t get a new variable—you get an error or a leak.Suggested Tags
Subscribe to my newsletter
Read articles from pushpesh kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
