Scopes and Variable Shadowing in JS

Pavel Saman
2 min readJan 21, 2023

If I give you the following example code:

const age = 30;

function func() {
const age = 31;
console.log(age);

for(let i = 0; i < 2; i++) {
const age = 32;
console.log(age);
}
}

func();
console.log(age);

What will be printed to the console?

Try to answer before you read on :) Self- testing is a great way of acquiring knowledge.

JavaScript is not terribly difficult but there’re nuances that are not always visible at the first glance.

What I want to demonstrate in the example above are scopes and variable shadowing.

There are 3 scopes in the example above — global, function, and block. JS has nowadays these 3 scopes. When I say scope, it basically means visibility — scope means where your declared variables will be visible.

For example, I can define a variable in a function:

function foo() {
const variable = 5;
}

I can use the variable name inside this function foo() — the variable is visible inside this function. If I try to use the name outside, I’ll get ReferenceError.

On the other hand, if I try to declare the same variable again in the same scope, I’ll get SyntaxError telling me that the identifier is already declared.

Notice how I said “in the same scope” in the previous sentence. Because I can do the following:

const variable = 5;

function foo() {
const variable = 6;
}

And there’ll be no error at all.

This is where shadowing comes into play. variable inside the foo() function shadows variable in the global scope in this example.

It also means that referring to variable inside the function foo() will yield 6. Referring to variable in the global scope will yield 5.

Going back to the initial example, but with answers in comments:

const age = 30;

function func() {
const age = 31;
console.log(age); // 31

for(let i = 0; i < 2; i++) {
const age = 32;
console.log(age); // 32
}
}

func();
console.log(age); // 30

When run, it’ll produce this output:

31
32
32
30

One caveat with scopes might be when it comes to using the keyword var when declaring variables. const and let are block scoped, which means that if I define variables with there keywords in a block, like in a for loop, they will not leak outside the scope — I’d get ReferenceError if I try to access them outside the scope where they were declared.

But when it comes to var, such variables are not block scoped; they can be function scoped.

An example might be better:

function foo() {
var a = 1;

for(let i = 0; i < 2; i++) {
var b = i;
}

console.log(b); // 1
}

foo();
console.log(a); // ReferenceError

Notice how b leaked outside the block scope but did not leak outside the function scope.

This is typically unwanted and a bit surprising, that’s why const and let are used more in modern JavaScript. But it’s good to know about var as well to be aware of this risk.

If you like my posts, please follow me. Thank you.

--

--