Look Up Names Where Code Was Defined

Look Up Names Where Code Was Defined

Lexical scope resolves an outer name using the place where a function was defined. Calling the function from a different scope does not replace its definition's surrounding bindings.

Before this topic, review Find Where a Name Is Visible if its terms are unfamiliar.

What you’ll learn

  • Explain look up names where code was defined through the school example.
  • Trace the shown operations and distinguish their result from displayed output.
  • Make the practice change and explain its consequence: The function uses the outer threshold 75; the call-site threshold 80 is still irrelevant.

Work through the school example

This is JavaScript using language features and the host console. Read the initial values, then follow each operation to its displayed result. A declaration names a value, a function call performs an operation, and console.log displays its arguments.

JavaScript · school.js · complete example

const passMark = 50;
function passed(mark) { return mark >= passMark; }
{ const passMark = 80; console.log(passed(72)); }

Expected output

true
Run in JavaScript compiler →

What is Lexical Scope in JavaScript?

In JavaScript, lexical scope (also called static scope) means that the accessibility of variables is determined by the position of functions and blocks in the source code — not at runtime, but when the code is written. In simpler terms, inner functions have access to variables defined in their outer (parent) scopes.

Understanding Lexical Scope with a Basic Example

function outerFunction() {
    let outerVariable = "I'm from the outer function";

    function innerFunction() {
        console.log(outerVariable);
    }

    innerFunction();
}

outerFunction();
I'm from the outer function

Explanation:

Here, innerFunction is defined inside outerFunction, so it has access to outerVariable due to lexical scoping. JavaScript knows this during the time of writing the code, not at runtime.

Scope Chain: How JavaScript Finds Variables

When a variable is accessed, JavaScript looks for it in the current scope. If it's not found, it walks up the scope chain — all the way to the global scope if necessary.

let globalVar = "Global";

function levelOne() {
    let levelOneVar = "Level 1";

    function levelTwo() {
        let levelTwoVar = "Level 2";

        console.log(globalVar);     // Accessible
        console.log(levelOneVar);   // Accessible
        console.log(levelTwoVar);   // Accessible
    }

    levelTwo();
}

levelOne();
Global
Level 1
Level 2

But It Doesn’t Work in Reverse

function outer() {
    let outerVar = "outer";

    function inner() {
        let innerVar = "inner";
        console.log(outerVar); // Works
    }

    inner();
    console.log(innerVar); // ❌ Error: innerVar is not defined
}

outer();
outer
Uncaught ReferenceError: innerVar is not defined

Variables Are Not Shared Across Siblings

Lexical scope only applies to nested relationships, not sibling functions.

function one() {
    let msg = "from one";
}

function two() {
    console.log(msg); // ❌ Error
}

one();
two();
Uncaught ReferenceError: msg is not defined

Nested Functions and Lexical Environment

Each function creates its own lexical environment. Inner functions carry a reference to their parent's scope. Let's visualize that:

function grandParent() {
    let gp = "grandparent";

    function parent() {
        let p = "parent";

        function child() {
            console.log(gp); // ✅
            console.log(p);  // ✅
        }

        child();
    }

    parent();
}

grandParent();
grandparent
parent

Real-Life Scenario: User Authentication Flow

function authenticate(username) {
    let secret = "token_" + username;

    function getToken() {
        return secret;
    }

    return getToken;
}

const tokenFetcher = authenticate("JohnDoe");
console.log(tokenFetcher()); // Lexical scope retains 'secret'
token_JohnDoe

Closures Are a Direct Result of Lexical Scope

A closure happens when a function "remembers" its lexical scope even after the outer function has finished executing.

function counter() {
    let count = 0;

    return function () {
        count++;
        console.log(count);
    };
}

const increment = counter();
increment(); // 1
increment(); // 2
increment(); // 3
1
2
3

Why Lexical Scope Matters

  • Helps with code modularity and predictability.
  • Provides the foundation for closures and callback functions.
  • Improves memory efficiency when managing state in functions.

Common Mistakes to Avoid

  • Thinking variables are dynamically scoped (they’re not).
  • Expecting sibling functions to access each other’s scope.
  • Misunderstanding how closures keep references, not copies, of variables.

Conclusion

Lexical scope is a core concept that gives JavaScript its power and flexibility. Mastering it lays the foundation for understanding closures, module patterns, and advanced concepts like currying and memoization. Always remember — where you write your functions matters just as much as how you write them.

Recognize the important boundary

The call-site block is not the function's lexical parent. A closure can still observe later changes to a captured mutable binding.

Try a small variation

JavaScript · original source for practice

const passMark = 50;
function passed(mark) { return mark >= passMark; }
{ const passMark = 80; console.log(passed(72)); }

Your task: Replace passMark = 50 with passMark = 75. Predict the result before running the edited program.

The source is repeated here. If the edit targets the HTML rather than the script, use the markup repeated below; the worked answer also repeats the changed markup.

Worked answer and reasoning

The function uses the outer threshold 75; the call-site threshold 80 is still irrelevant.

JavaScript · complete worked variation

const passMark = 75;
function passed(mark) { return mark >= passMark; }
{ const passMark = 80; console.log(passed(72)); }

Expected output

false
Run in JavaScript compiler →

Predict the original program

JavaScript · original program for this question

const passMark = 50;
function passed(mark) { return mark >= passMark; }
{ const passMark = 80; console.log(passed(72)); }
What does the original JavaScript program display, in order?
Choose one answer. You can try again.

Answer and explanation

true

Lexical scope resolves an outer name using the place where a function was defined. Calling the function from a different scope does not replace its definition's surrounding bindings. The call-site block is not the function's lexical parent. A closure can still observe later changes to a captured mutable binding.

Keep the useful idea

Lexical scope resolves an outer name using the place where a function was defined. Calling the function from a different scope does not replace its definition's surrounding bindings.

Check your variation against this explanation: The function uses the outer threshold 75; the call-site threshold 80 is still irrelevant.

Use the JavaScript array reference library for individual array operations and boundary tests to check a rule independently.

Reaching the recap and completing the quizzes are separate milestones.

    Next lesson: Understand Declarations Before Execution →