🕶️ Variable Shadowing in JavaScript — A Complete Guide

pushpesh kumarpushpesh kumar
5 min read

1 What Is Shadowing?

TermIn one sentence
ShadowingDeclaring 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?

ScopeCreated where?Visible where?
GlobalOutside every function / blockEverywhere (for var: also on window)
FunctionInside a function bodyOnly inside that function
BlockInside { … }, if, for, switch, …Only inside that block (works only for let / const)

3 Scoping Rules by Keyword

KeywordRespects block scope?Respects function scope?
var❌ (no – it leaks)
let
const

4 Six Core Shadowing Scenarios (Like-for-Like)

#Outer declarationInner scopeResultWhy?
1var (global)var in functionShadowNew function-scoped variable
2var (global)var in blockSame variablevar ignores block ⇒ leaks
3let (global)let in functionShadowFunction scope respected
4let (global)let in blockShadowBlock scope respected
5const (global)const in functionShadowFunction scope respected
6const (global)const in blockShadowBlock 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

PatternCodeOutcomeReason
Redeclare in same scopelet x = 1; let x = 2;❌ SyntaxErrorlet/const cannot be redeclared
var after let/const in same scopelet y = 3; var y = 4;❌ SyntaxErrorHoisting makes scopes overlap
Mix in different scopesvar z = 5; { let z = 6; }Different (block) scope ⇒ legal

6 Shadowing vs Reassignment vs Redeclaration

ActionCreates a new variable?ExampleLegal?
Shadowing✅ (inner scope)let a = 1; { let a = 2; }Yes
Reassignmentlet 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 keywordInner keywordIn functionIn block
varlet / constShadow ✔️Shadow ✔️
let / constvarShadow ✔️❌ 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

  1. Prefer let / const – block scope prevents leaks.

  2. Unique, descriptive names – accidental shadowing disappears.

  3. Turn on ESLint’s no-shadow rule – your linter becomes a bodyguard.

  4. Never mix var with let/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 of var or redeclaration ), you don’t get a new variable—you get an error or a leak.Suggested Tags

0
Subscribe to my newsletter

Read articles from pushpesh kumar directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

pushpesh kumar
pushpesh kumar