Skip to content

scope

A scope is the region of a program where a declared name is visible, so code inside that region can refer to the name and code outside it can’t.

Scopes nest. When code reads a name, JavaScript looks for its binding, the link that a declaration creates between a name and a value. The lookup starts in the innermost scope and moves through each enclosing scope until it finds the binding or reaches the global scope. Inner code can see outer names, but outer code can’t see names declared inside. An inner declaration with the same name as an outer one shadows the outer binding.

JavaScript has four kinds of scope. The global scope holds top-level declarations of a script, and each ES module gets its own module scope. A function body creates a function scope, and a block in curly braces creates a block scope for let, const, and class declarations, plus function declarations in strict mode.

JavaScript scope is lexical, meaning it’s fixed by where code appears in the source, so a function sees the names around its definition wherever it’s called. That rule makes a closure work, and hoisting describes when the bindings in a scope become usable.

Example

Say a checkout function adds up an order’s prices and subtracts a coupon discount inside an if block:

Language: JavaScript
> const shippingFee = 4;
undefined
> function orderTotal(prices, hasCoupon) {
…   let total = 0;
…   for (const price of prices) {
…     total += price;
…   }
…   if (hasCoupon) {
…     const discount = 5;
…     total -= discount;
…   }
…   return total + shippingFee;
… }
undefined

> orderTotal([10, 20], true)
29

> total
Uncaught ReferenceError: total is not defined
> discount
Uncaught ReferenceError: discount is not defined

The function reads shippingFee from the global scope, and the if block reads total from the function scope. Neither total nor discount exists outside the scope that declared it, so reading them at the top level throws a ReferenceError. Each lookup walks outward through the nested scopes until it finds the name or runs out of scopes:

A name lookup starts in the if block scope, moves out to the function scope, then the global scope, then throws ReferenceError.
Name Lookup Walks Outward Through Nested Scopes

Blocks don’t scope var declarations, which belong to the enclosing function or to the top level of the script or module. Code that declares a flag inside a block shows the difference:

Language: JavaScript
> if (true) {
…   var legacyFlag = "on";
…   let blockFlag = "on";
… }
undefined

> legacyFlag
'on'
> blockFlag
Uncaught ReferenceError: blockFlag is not defined

The var binding leaks out of the block into the enclosing scope, while the let binding ends with the closing brace.

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