Scope and Shadowing

JavaScript ES2023 · ✓ verified by execution on 2026-08-11

Scope determines the visibility of values and expressions in your code. Understanding scope is essential for managing variables properly and avoiding hard-to-track bugs.

Block Scope

In JavaScript, any pair of curly braces {} forms a block. Variables declared with let or const inside a block are block-scoped, meaning they only exist within those curly braces.

javascript
{
  let x = 'hello';
  console.log(x);
}
console.log(typeof x);
Output
hello
undefined

If you try to access a block-scoped variable from outside its block, JavaScript throws a ReferenceError because the variable is “not available for use”:

javascript
if (true) {
  let b = 10;
}
try {
  console.log(b);
} catch (e) {
  console.log(e.name);
}
Output
ReferenceError

The Scope Chain

Scopes can be layered in a hierarchy. When you nest blocks, you create a parent-child relationship. Child scopes have access to parent scopes, but not vice versa.

javascript · visualize
let globalVar = 1;
{
  let innerVar = 2;
  console.log(globalVar + innerVar);
}
3

Visualizing Scope Hierarchy

This diagram shows how block scopes nest inside the global scope. Child scopes can read values from their parents, but parents cannot reach into child scopes.

Global Scope let globalVar = 1 Block Scope let innerVar = 2 // Access to both globalVar and innerVar
Nested scopes: code inside a block can read variables from the global scope around it, but a variable declared inside the block is not visible outside it.

When JavaScript looks for a variable, it starts in the current scope. If it doesn’t find it, it moves up to the parent scope, continuing until it reaches the global scope. This process is called traversing the scope chain.

Variable Shadowing

Because of how the scope chain works, an interesting effect called shadowing can happen. If you declare a variable in an inner scope with the exact same name as a variable in an outer scope, the inner variable “shadows” (hides) the outer one.

Predict the output javascript

Read the code. What exactly will it print? Commit to an answer before you look.

let a = 1;
{
  let a = 2;
  console.log(a);
}
console.log(a);
Output
2
1

Re-declaring a variable in an inner scope does not permanently change the outer variable. It only hides it while execution is inside the inner scope. Once the inner scope exits, the outer variable becomes visible again.

Functions also create scopes and follow the exact same rules. A function parameter will shadow an outer variable of the same name:

javascript
function f(a) {
  {
    let a = 2;
    console.log(a);
  }
  console.log(a);
}
f(1);
Output
2
1

Edge Case: Temporal Dead Zone (TDZ)

When you shadow a variable, you must be careful not to access the outer variable inside the inner block before the inner variable’s declaration. Doing so results in a ReferenceError due to the Temporal Dead Zone.

javascript
let a = 1;
{
  try {
    console.log(a);
    let a = 2;
  } catch (e) {
    console.log(e.name);
  }
}
Output
ReferenceError

The inner block “knows” that a is going to be declared, so it shadows the outer a immediately. But since the let a = 2 line hasn’t run yet, accessing it throws an error.

Check yourself

What happens if you declare a variable with let inside an if block and try to log it outside?

Reveal answer

It throws a ReferenceError — Variables declared with let and const are strictly block-scoped and disappear when the block ends. Attempting to access them outside the block throws a ReferenceError.

Does redeclaring a variable in an inner scope permanently change the outer variable?

Reveal answer

No, it only hides the outer variable while inside the inner scope — This is called variable shadowing. It only hides the outer variable while in the inner scope; the outer variable regains visibility once the inner scope exits.

Can a parent scope access a variable declared in a child scope?

Reveal answer

No, child scopes have access to parent scopes, but not vice versa — Scopes can be layered in a hierarchy. Child scopes have access to parent scopes, but parent scopes cannot reach into child scopes.

Challenges

Challenge 1 +15 XP

Fix the code so that the outer variable x is printed as 1, and the inner variable x is printed as 2.

javascript
  • Test 1 — expects "2\n1\n"
Need a hint? (−25% XP)

Use the let keyword to redeclare the variable in the inner block.

Show solution (0 XP)
let x = 1;
{
  let x = 2;
  console.log(x);
}
console.log(x);

Challenge 2 +15 XP

Make the code run without a ReferenceError by moving the variable declaration to the correct scope.

javascript
  • Test 1 — expects "Success\n"
Need a hint? (−25% XP)

Declare the variable outside the block so it is accessible where it is logged.

Show solution (0 XP)
let message;
if (true) {
  message = 'Success';
}
console.log(message);