Introduction
In our previous lesson, we established a solid understanding of how var, let, and const differ, particularly regarding their scope—whether they are confined to a function or a block. Today, we will build upon that knowledge to explore one of JavaScript's most fundamental mechanisms: the scope chain.
You've seen that code can be nested—functions inside functions, blocks inside other blocks. The scope chain is the set of rules the JavaScript engine follows to find and access variables across these nested environments. Understanding this lookup process is not just an academic exercise; it is the key that unlocks more advanced concepts like closures, which we will tackle in our next lesson. By the end of this session, you'll be able to trace how JavaScript resolves variables in complex, nested code, a critical skill for debugging and writing predictable applications.
Lexical Scope: The Blueprint for the Chain
Before we can trace the scope chain, we must first understand how it's created. In JavaScript, the structure of the scope chain is defined by lexical scope (also known as static scope). This means that a function's access to variables is determined by where that function is physically written in the code, not by where it is called or invoked. The hierarchy of scopes is set in stone during the "authoring" or compile phase.
An inner function's scope is lexically nested within its outer function's scope, which is itself nested within its outer scope, and so on, all the way up to the global scope. This creates a physical chain of scopes.
The following image provides a high-level conceptual map. The function isApple is defined inside the global scope. When isBanana calls isApple, you might intuitively think isApple should have access to the apple variable inside isBanana. However, because of lexical scoping, isApple's parent is the Global Execution Context where it was defined, not the context of isBanana which called it.

To formalize this, each function's execution context has two key components: its own local memory (for its variables and parameters) and a reference to its parent's lexical environment. This chain of references is the scope chain.
Let's watch a video from Akshay Saini's "Namaste JavaScript" series that explains this beautifully.
The Scope Chain, 🔥Scope & Lexical Environment | Namaste JavaScript Ep. 7
This video provides a clear definition of scope, lexical environment, and how the physical placement of your code determines variable access.
Watch the segments from the beginning up to the visual explanation of the lexical environment hierarchy: What is Scope? This part defines scope and introduces its connection to the lexical environment. Lexical Environment Explained. Pay close attention to the definition of "lexical" and the idea that a function is lexically inside another. The visual breakdown of execution contexts and their memory is key here.
As the video explains, function C is lexically inside function A, and A is lexically in the global scope. This physical nesting at author-time is what creates the chain: C -> A -> Global.
Traversing the Scope Chain: The Lookup Process
Now that we know how the chain is structured, how does the engine use it? When the engine needs to resolve a variable identifier (e.g., when you write console.log(myVar)), it follows a simple, hierarchical process:
- Start Local: It first looks for
myVarin the current, most immediate scope (the local scope of the currently executing function). - Move Outward: If it doesn't find the variable locally, it follows the reference to the parent's lexical environment and searches there.
- Repeat: It continues this process, moving "up" the chain from one outer scope to the next.
- Reach Global: This continues until it reaches the global scope. If it finds the variable there, it uses it.
- Not Found: If the engine has searched all the way to the end of the chain (the global scope) and still hasn't found the variable, it will throw a
ReferenceError.
This entire lookup mechanism is what we call resolving a variable via the scope chain.
The following video from Lydia Hallie provides a superb, animated visualization of exactly this process.
JavaScript Visualized - Execution Contexts
This video, "JavaScript Visualized," animates the creation of a function execution context and demonstrates how the engine traverses the scope chain.
Watch the segment from this timestamp. Notice how the engine, while executing the greet function, cannot find lastName in its local environment. It then uses the "outer environment" property to look up into the global environment, where it successfully finds the variable.
This traversal is the core mechanism you need to master. Let's look at another static diagram that ties it all together with the block scope concepts we learned in the last lesson.

Shadowing: When Variables Collide
A common and important behavior related to the scope chain is shadowing. This occurs when a variable declared in an inner scope has the same name as a variable in an outer scope.
Because the lookup process starts from the innermost scope and works its way out, it will find and use the inner variable first, effectively "shadowing" or blocking access to the outer variable of the same name.
The "You Don't Know JS Yet" series by Kyle Simpson offers an excellent explanation.
Chapter 3: The Scope Chain - You Don't Know JS Yet - Mintlify
This chapter from the definitive "You Don't Know JS" series discusses shadowing with clear examples.
In the chapter, please read two key sections: The section on Shadowing. Focus on how the studentName parameter inside printStudent shadows the global studentName. The section on Illegal Shadowing. This serves as a great review of our last lesson, showing how let and var interact within the scope chain, sometimes in ways that are disallowed.
As you read, shadowing is not an error; it's a natural consequence of lexical scoping. However, it can sometimes lead to bugs if you're not aware of which variable you are accessing. Modern tools like ESLint can be configured to warn you about shadowed variables.
Practice: Tracing the Chain
Let's apply what you've learned. Consider the following code snippet. Before running it, trace the execution and predict what will be logged at points A, B, C, and D. Think about which givenName is in scope at each point.
let givenName = 'Amon Williams';
function printGreeting() {
let givenName = "Khalid";
console.log(`A: Hello ${givenName}`); // What is givenName here?
if (givenName.split(" ").length < 2) {
givenName = "Khalid Williams";
console.log(`B: Hello ${givenName}`); // And here?
}
console.log(`C: Hello ${givenName}`); // And here?
}
printGreeting();
console.log(`D: Hello ${givenName}`); // Finally, what is givenName out here?
- At A: The engine looks for
givenNameinsideprintGreeting. It finds thelet givenName = "Khalid"declaration. It stops there and logsA: Hello Khalid. - At B: This is inside the
ifblock. The engine modified thegivenNamefrom theprintGreetingscope (sinceletis re-assignable). It logsB: Hello Khalid Williams. - At C: The
ifblock has finished. We are back in theprintGreetingfunction's scope. ThegivenNamevariable in this scope was modified, so it logsC: Hello Khalid Williams. - At D: We are now in the global scope, outside the function. The lookup for
givenNamefinds the globallet givenName = 'Amon Williams'. The changes insideprintGreetinghad no effect on this variable because the inner variable shadowed it. It logsD: Hello Amon Williams.
This exercise demonstrates the concrete effects of both the scope chain lookup and shadowing.
Conclusion
In this lesson, we demystified the process of variable lookup in JavaScript. You now have a mental model for how the engine navigates nested scopes to find the data it needs.
Key Takeaways:
- Lexical Scoping: A function's scope is determined by where it is written in the code, not where it is called. This static structure forms the blueprint for the scope chain.
- The Scope Chain: This is the chain of references linking each lexical environment to its parent, all the way up to the global scope.
- Lookup Process: The JavaScript engine resolves variables by searching the current scope first, then moving up the chain one level at a time until it finds a match or reaches the end.
- Shadowing: An inner-scope variable with the same name as an outer-scope variable will be found first, effectively hiding the outer variable from that point inward.
This understanding of the scope chain is the absolute foundation for our next topic: closures. A closure is a function that "remembers" the environment in which it was created. As you will see, it's the scope chain mechanism that makes this powerful feature possible.