Hide and Seek
episode 04#Lexical Scope
To execute JavaScript code in our minds, we need one fundamental skill: know which identifiers are accessible at each point of our program.
This idea is known as scope, and understanding it is the final building block before learning closures.
I like to think of scope as a system that builds invisible walls around our code. From the outside you can’t peek in — anything declared inside stays private. But from the inside, you can always see the world around you.
There are a few things we need to see to acquire this knowledge.
Block Scope, Function Scope, Global Scope.
Block Scope
Walls but not for everyone.
Every time we use brackets
{}we generate an enclosing space for variables declared withconstandletthat prevents them from being visible and accessed from the outside.What about variables declared with
var? Don't worry, we leave them for later.
Variables with let and const
Any time we use curly braces {}, JavaScript creates a new block scope.
This means that variables defined inside a block are not visible from the outside.
{
const treasure = 'ring';
}
console.log(treasure); // Throw ReferenceErrorThe reason this code throws a ReferenceError is because treasure lives inside an inner block scope, and the outer scope can't access it.
And there is more. We have seen that we can't access a variable declared inside a block {} from the outside.
But if the variable is declared outside, can we access it from an inner block?
const treasure = 'ring';
{
console.log(treasure); // ?
}Well that would work perfectly.
A block can read variables that exist in an outer scope, but not the other way around. Inner scopes can "see" outer scopes, but outer scopes cannot "see" inner ones.
const treasure = 'ring';
{
console.log(treasure); // 'ring'
}Constructs that create a block scope:
-
{}simple blocks -
if/else -
for,while,do...while -
switch -
try/catch/finally
Any of these forms a new block scope for let and const.
Variables with var
Variables declared with var do not follow block scope because of how JavaScript was originally designed and how the language specification treats it.
In early JavaScript (1995), the only kind of scope that existed was function scope.
Blocks {} did not create new scopes at all.
So
varwas designed to attach itself to the nearest function scope, not the closest block likeletandconst.
When JavaScript sees:
{
var x = 1;
}It internally asks, what is the nearest function environment?
-
Save x there.
-
If there is not a function around, it saves it in the global scope.
The block is completely ignored.
A little test now. What happens when we run this code?
{
var location = 'chamber of secrets';
}
console.log(location); // ?JavaScript saves the variable location in the global scope ignoring the block {} and making possible for the console.log() to reach the variable location.
{
var location = 'chamber of secrets';
}
console.log(location); // 'chamber of secrets'And all these leads us to functions.
Are we missing something? Functions also use curly braces, are they creating a new block scope?
Well, yes and no.
Functions do create a new scope, but not a block scope. They create a Function Scope, which is a different kind of scope with its own rules.
Let’s break it down.
Function Scope
Everyone is welcome inside this walls.
A function scope is much stronger and more isolated than a block scope. Everything declared inside a function—whether it’s var, let, const, parameters, or even inner function declarations—lives exclusively inside that function’s private environment. Nothing leaks out unless you deliberately return it.
Let’s look at a simple example:
function hunt() {
const treasure = 'gold';
let map = 'old map';
var coordinates = '1234';
}
console.log(treasure); // Throw ReferenceError
console.log(map); // Throw ReferenceError
console.log(coordinates); // Throw ReferenceErrorEven var, which happily ignores block scope, is completely contained inside a function. That’s because functions don’t create a block scope — they create a function scope, and var is designed to belong to whichever function scope is nearest.
In other words
A function forms a boundary. Anything declared inside stays inside.
And just like with block scopes, the relationship is one-directional:
-
From inside the function you can access things declared outside.
-
But the outside world can’t see anything declared inside the function.
Global Scope
You can’t fence in the wind. No walls at all.
We only need to remember two things about the Global Scope:
- If you declare something outside of any function or block, it belongs to the global scope.
- And everything inside your program can “see” it.
const globalTreasure = 'ring';
function cave() {
console.log(globalTreasure); // 'ring'
}
{
console.log(globalTreasure); // 'ring'
}
cave();No matter where you are—in a function, in a block, or at the top level—the global scope is always available as a fallback. There are no walls here; everything is accessible from anywhere.
Recap
- Scope defines which identifiers are accessible at each point in a program.
- JavaScript uses scope boundaries to control visibility and prevent unwanted access to variables.
- Block scope is created by
{}and applies to variables declared withletandconst. - Variables declared with
vardo not respect block scope and attach to the nearest function scope instead. - Function scope creates a private environment where all declarations (
var,let,const, parameters, and inner functions) are isolated. - The global scope contains identifiers declared outside any function or block and is accessible from anywhere in the program.
- Scope visibility is one-directional: inner scopes can access outer scopes, but not the other way around.