Understanding Lexical Scope in JavaScript (With Clear Examples)

Introduction

Lexical scope is a core concept in JavaScript. It might seem complex at first—just like closures—but it's actually quite easy to understand. Having a solid grasp of lexical scope is very useful; understanding topics like this makes you a more effective developer and helps you make better decisions while writing code.

So today, we’ll dive deep into lexical scope. Trust me, by the end of this article, you'll have a clear understanding of it. But only if you read it all the way through! 😄

Let’s get started.

What is Lexical Scope in JavaScript?

We often come across the term lexical scope, and at first, it might sound like both words mean the same thing. But in reality, they refer to two different concepts. Together, they form the idea of lexical scope.

To understand this fully, we need to break down both terms individually.

I’ll start with the word scope, because the word lexical has little meaning in JavaScript without scope. Once you understand the scope, the concept of lexical scope will be much easier to grasp.

What is Scope?

If we look at the word scope from a real-life perspective, it refers to the range or extent of something that it covers, includes, or affects. In JavaScript, scope defines how far a given variable or function is accessible in your code. Simply put, it’s the range within which something can be accessed.

There are three main types of scope in JavaScript:

Global Scope

A variable or function declared outside of any function or block is in the global scope. It is accessible anywhere in the file, including inside functions or blocks.

JavaScript

copy

let myName = 'John Harry';

console.log("globally:", myName) // output: John Harry

// function
function myFunction() {
  console.log("function:", myName) // output: John Harry
}

myFunction();

// block

{

  console.log("block of code:", myName) // output: John Harry
}

Did you notice that I created a variable named myName outside any function or block of code? This means it is a global variable, and that's why it is accessible everywhere in the code. I accessed this variable three times in three different places: globally, inside a function, and within a block of code. The output is John Harry because it is a global variable.

To help you understand better, I also included labels such as globally and function when accessing the variable myName.

Local Scope Explanation:

A local variable is any variable that is declared inside a function or a block of code (a block is usually enclosed by curly braces { }). These variables are only accessible within that specific function or block and their child scopes.

JavaScript

copy

// block of code 
{
  // declared in block of code
  let myName = 'John Harry';
  
  // accessing in the block of code
  console.log("block of code:", myName) // output: John Harry
}

// globally accessing

console.log("globally:", myName) // output: ReferenceError: myName is not defined


// accessing in function
function myFunction() {
  console.log("function:", myName) // output: ReferenceError: myName is not defined
}

myFunction();

Have you seen this example? I created a variable inside a block and accessed it within the same block; it returned the expected output: John Harry. However, when I tried to access the same variable outside the block but still within the function, it returned a ReferenceError, indicating that it is not defined there. This shows that the variable has block scope, so it is only accessible within that block.

The same applies to functions — if we declare something inside a function, it is only accessible within that function and its inner blocks. We can't access it from outside the function

Lexical JavaScript

In JavaScript, lexical refers to the physical location of code in the source file — meaning where variables and functions are written in the code, like whether they are defined inside the function or globally. This placement determines the scope's definition. At first, this might seem a bit confusing, but don’t worry — just like with scope, we’ll explore this deeply and make it clear. Check this example below:

JavaScript

copy

function x() {
  let myName = 'John';
  
  function y() {
    console.log(myName) // output: John
  }
  
  y();
}

x();

The function y() is lexically scoped inside the function x(), which means it has access to x()'s variables. I know it is still not clear. Let me try one more time to help you understand 😀

In simple words, when we called the y() function and tried to access the myName variable — even though it wasn’t created inside y() — it was still successfully accessed. This means that y() has access to all variables created in its parent scope, which is x().

This ability to access variables from the parent scope, even when they are not defined inside the current function, is known as lexical scoping. The function y() is lexically defined inside x(), and x() provides a lexical environment for its inner functions, like y().

So now, if we combine the two words lexical and scope using the example above, it would look like this:

Lexical: The function y() is lexically defined inside the function x().

Scope: The function y() can access the variable myName, which is declared in its parent function x().

So, when we combine them, we can say that function y() has lexical scope inside function x() — meaning it can access variables from the scope in which it was defined.

Before I go further, I want to clarify that I used a nested function approach to help you better understand this topic.

I could have used a single function instead — for example, by declaring the variable outside of function x() and accessing it inside. That would still demonstrate lexical scope, since function x() would be lexically defined in the global scope. Here's what that would look like:

JavaScript

copy

let myName = 'John';
function x() {
 console.log(myName) // output: John
}
x();

However, I didn’t use that approach because it feels more about scope and less about lexical structure. Using nested functions, like in the previous example, makes the concept of lexical scoping much clearer and more meaningful.

I hope you’re following along so far. Now, let's move on to understanding how lexical scope works.

How Lexical Scope Works in JavaScript

Understanding the philosophy behind this topic is very useful. Even if you feel you’ve understood everything or still have questions, it’s natural—many questions may be in your mind or in the mind of someone trying to truly understand how JavaScript works.

The example we looked at above was simple. Now, let’s move on to a few more complex examples. Take a look at the example below:

JavaScript

copy

function x() {
  let myName = 'Harry';
  function y() {
    console.log(myName); // output: Harry;
  }
  
  return y;
}
const z = x(); // returned function y();
z();

This example might seem slightly complex if you're not familiar with closures, but don't worry, I'll make it easier for you.

The only difference here is that I didn't call function y() inside function x(). Instead, I returned function y from within x.

In our previous examples, we called function y() directly inside function x(). So, when x() was called, it immediately executed y() as well. That made both functions run together at once. Simple enough!

However, the example above works differently. Here, I returned function y from x, which means that when we call x(), it doesn't execute y() right away. Instead, it returns the definition of the function y. To execute it, we need to assign the returned function to a variable, like we did with z, and then call that variable as a function.

JavaScript

copy

const z = x(); // returned function y();
z();

However, you might be wondering: why is the variable myName still accessible inside function y(), even though it was created in function x(), which has already finished executing?

That’s a great question! Even though function x() has completed its execution and is no longer on the call stack, JavaScript doesn’t remove variables that are still being used elsewhere. In this case, since function y() was returned and is still in use, it maintains a reference to the variables declared in its outer scope (i.e., function x()). This behavior is known as closures, which allow inner functions to remember and access variables from their outer lexical environment even after the outer function has finished executing.

Now you have an idea that lexical refers to where the code is physically written, not where it is accessed or called from.

In the example above, you can see that function y was defined inside function x, but it was called outside of x. Still, it was able to access the variable myName from the function x.

Now let’s look at the last example, which should give you an idea of what a lexical chain is and how it works.

JavaScript

copy

function x() {
  let firstName = 'Harry';
  function y() {
    let lastName = 'John';
    function z() {
      console.log(firstName, lastName); // output: Harry John;
    }
    z();
  }
  y();
}
x();

Take a look at how the output matches exactly what we expected. The variable firstName is declared inside function x, and lastName is declared inside function y. Yet we access both variables inside function z, which is a nested function (or a "grandchild" of x and child of y).

This behavior is a result of lexical scoping, and the structure formed by such access is called a lexical environment or lexical scope chain.

Let’s explore this concept further to understand why it's known as a lexical chain.

As JavaScript runs, the function x (the first function) is executed. Then, since function y is defined inside x, it is also executed. Similarly, function z, which is defined inside y, gets executed as well.

This execution order might seem obvious, but here's the twist: before executing the body of function z, JavaScript ensures that this line of code is executed:

JavaScript

copy

console.log(firstName, lastName);

So, what happens here?

JavaScript starts searching for the variables firstName and lastName inside function z. Since these variables are not declared there, it looks one level up—in function y. It finds lastName there because it was declared in y. But since firstName is still not found, JavaScript continues searching in function x, where firstName is declared.

This search process follows a specific sequence: JavaScript looks inside the current function, then moves to the parent function, and continues upward through the nested scope chain. It searches from child to parent, or from innermost to outermost scope.

This behavior is known as lexical scoping, and the structure it follows is called the scope chain, a key concept in how JavaScript resolves variables.

But you might have this question in your mind: Why does JavaScript go from the bottom to the top when searching for a variable? Why doesn’t it go from top to bottom?

The reason is that a child function can access variables declared in its parent scope, but a parent function cannot access variables declared inside its child.