Skip to content

lexical scope

Lexical scope is the rule that a variable name in JavaScript code refers to whichever declaration of that name is visible where the code is written in the source, not where or how it’s later called.

JavaScript resolves names this way for every function. Each scope sits inside the scopes that surround it in the source text, and a function remembers the scope it was defined in. When the function runs, a name lookup walks outward from the function’s own scope through those enclosing scopes. Remembering that defining scope is what makes a closure work.

Because the nesting is fixed by the source, reading the code shows which binding each name refers to. Two sloppy-mode features loosen this rule. The with statement puts an object’s properties into the lookup at run time, and a direct eval() call can declare new var bindings in the calling scope. Strict mode forbids with and keeps those eval() declarations inside the evaluated code.

Example

Say an invoice helper formats prices in a store’s default currency, and another function that calls it declares its own currency variable:

Language: JavaScript
> const currency = "USD";
undefined
> function formatPrice(amount) {
…   return `${amount.toFixed(2)} ${currency}`;
… }
undefined
> function printEuroInvoice(amount) {
…   const currency = "EUR";
…   return formatPrice(amount);
… }
undefined

> printEuroInvoice(12)
'12.00 USD'

The call happens inside printEuroInvoice(), but formatPrice() was defined at the top level, so its currency lookup finds the top-level "USD" binding. The "EUR" binding is local to the caller’s scope, so the lookup skips it:

formatPrice() is defined in the global scope, so its currency lookup finds 'USD' and skips the calling scope's 'EUR'.
Name Lookup Follows Where Code Is Written

Arrow functions also resolve this lexically, because they don’t have a this binding of their own. A callback inside a method shows the difference:

Language: JavaScript
> const upload = {
…   label: "upload",
…   arrowReport() {
…     const report = () => `${this.label} started`;
…     return report();
…   },
…   functionReport() {
…     const report = function () {
…       return `${this.label} started`;
…     };
…     return report();
…   },
… };
undefined

> upload.arrowReport()
'upload started'
> upload.functionReport()
'undefined started'

The arrow function reads this from the method around it. The regular function expression gets its own this from how it’s called. A plain call like report() doesn’t pass the object as this. In the sloppy-mode REPL, this falls back to the global object, which has no label property.

Have a question about this? Mentor AI can show you examples, compare related terms, and point you to tutorials.


By Leodanis Pozo Ramos • Updated Nov. 5, 2026