Skip to content
JavaScript js scope-closures 5 min read

Scope

Scope is the set of rules that decides where a variable is visible and how a name resolves to a value. Every time you declare a variable, JavaScript places it in a particular region of your program; code outside that region simply cannot see it. Understanding scope is what lets you reason about name collisions, predict which value a reference will return, and avoid leaking state into places it does not belong. It is also the foundation that closures are built on.

The kinds of scope

JavaScript has three scopes you create variables in: global, function, and block. They nest inside one another, with the global scope at the outermost level.

Global scope is the outermost region. A variable declared at the top level of a script (or attached to the global object) is reachable from everywhere. In the browser the global object is window; in Node.js it is globalThis.

Function scope is created by every function. Variables declared inside a function — including its parameters and any var declarations — exist only for the life of a call and are invisible outside it.

Block scope is created by any pair of { } — an if, a for, a while, or a bare block. Crucially, only let and const are block-scoped. var ignores blocks and leaks up to the nearest function (or global) scope.

const appName = "DevCraftly"; // global scope

function render() {
  const heading = "Welcome"; // function scope

  if (true) {
    let badge = "new"; // block scope
    const VERSION = "2.0"; // block scope
    console.log(appName, heading, badge, VERSION);
  }

  // console.log(badge); // ReferenceError — badge is not visible here
}

render();

Output:

DevCraftly Welcome new 2.0

The var keyword behaves differently — it is not block-scoped, which is a common source of bugs:

function demo() {
  if (true) {
    var leaked = "I escape the block";
    let trapped = "I stay inside";
  }
  console.log(leaked); // works — var is function-scoped
  // console.log(trapped); // ReferenceError — let is block-scoped
}

demo();

Output:

I escape the block

Prefer const by default and let when you must reassign. Avoid var: its function-scoping and hoisting behavior produce surprises, especially inside loops and conditionals.

The scope chain

Scopes nest, and that nesting forms a chain. When you reference a name, the engine looks in the current scope first; if it is not found there, it walks outward to the enclosing scope, and continues until it reaches the global scope. If the name is never found, you get a ReferenceError. The lookup only ever goes outward — an outer scope can never see into an inner one.

┌─────────────────────────────────────────────┐
│ Global scope            appName              │
│  ┌────────────────────────────────────────┐ │
│  │ outer() scope        userId             │ │
│  │  ┌───────────────────────────────────┐ │ │
│  │  │ inner() scope     greeting        │ │ │
│  │  │  ── can read: greeting, userId,   │ │ │
│  │  │     appName (walks outward)       │ │ │
│  │  └───────────────────────────────────┘ │ │
│  └────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
const appName = "DevCraftly";

function outer() {
  const userId = 42;

  function inner() {
    const greeting = "Hi";
    // inner can reach all three layers
    console.log(greeting, userId, appName);
  }

  inner();
}

outer();

Output:

Hi 42 DevCraftly

Lexical scoping

JavaScript uses lexical (also called static) scoping: a function’s scope chain is determined by where it is written in the source, not by where or how it is called. You can decide what a function can see just by reading the code — you never have to trace the call stack at runtime.

const message = "module level";

function makePrinter() {
  const message = "inside makePrinter";
  return function print() {
    console.log(message); // resolves where print was defined
  };
}

const printer = makePrinter();

function elsewhere() {
  const message = "inside elsewhere";
  printer(); // call site does NOT change what print sees
}

elsewhere();

Output:

inside makePrinter

Even though printer() is invoked from inside elsewhere, it prints "inside makePrinter" because print was defined there. The call site is irrelevant — only the lexical position matters.

Shadowing

When an inner scope declares a variable with the same name as one in an outer scope, the inner declaration shadows the outer one. Within the inner scope the name refers to the inner variable; the outer variable is untouched and reappears once you leave.

const role = "guest";

function authorize() {
  const role = "admin"; // shadows the global `role`
  console.log(role);
}

authorize();
console.log(role); // outer value is unchanged

Output:

admin
guest

Shadowing is legal and often useful, but accidental shadowing can hide the value you meant to use. Be deliberate, and avoid reusing names across nested scopes unless the shadowing is intentional.

Watch for the temporal dead zone: a let/const name is shadowed for the entire block, so referencing it before its declaration throws a ReferenceError even if an outer variable of the same name exists.

Scope comparison

Aspectvarletconst
ScopeFunctionBlockBlock
HoistedYes (initialized undefined)Yes (temporal dead zone)Yes (temporal dead zone)
ReassignableYesYesNo
Redeclarable in same scopeYesNoNo
Leaks out of { }YesNoNo

Best Practices

  • Default to const, use let when reassignment is required, and avoid var in new code.
  • Declare variables in the narrowest scope that works — block over function, function over global.
  • Keep the global scope clean; treat it as shared, polluting it leads to name collisions across files and libraries.
  • Rely on lexical scoping to reason about visibility: read where a function is written, not where it is called.
  • Avoid accidental shadowing by giving nested variables distinct, descriptive names.
  • Remember the scope chain only searches outward, so design data flow to pass values inward via parameters rather than reaching for outer state.
Last updated June 1, 2026
Was this helpful?