Skip to content
beginner

JavaScript Scope: Global, Function, and Block Scope

You write a variable inside a function, log it, and it prints perfectly. You move that same console.log two lines down, outside the function, and suddenly…

Published 2026-10-03Updated 2026-10-048 min read
Medical professional in teal scrubs using a laptop and headset, sitting at a desk in an office setting.
Medical professional in teal scrubs using a laptop and headset, sitting at a desk in an office setting. Photo by https://kaboompics.com/ on Pexels.

You write a variable inside a function, log it, and it prints perfectly. You move that same console.log two lines down, outside the function, and suddenly JavaScript throws a ReferenceError. Nothing about the value changed. What changed is where you asked for it.

That is scope in JavaScript: the set of names you can actually see at a specific line of code. Not a property of the value. A property of the position.

Once you can answer one question reliably — at this line, which names can I see? — most of the confusing errors beginners hit stop being mysterious. So let's build that rule instead of memorizing a list.

Why a Variable Works Here but Not There

Here is the smallest version of the problem:

function greet() {
  const message = "Hello from inside";
  console.log(message);
}

greet();
console.log(message);
Hello from inside
ReferenceError: message is not defined

The first log works because it runs inside greet, where message was declared. The second log runs outside greet, in the global code, where message simply does not exist. JavaScript is not being difficult — it is answering the only question it knows how to answer: is this name visible from here?

You already know how to declare variables and call functions. Scope is the missing piece that explains where those declarations live and how far their visibility reaches.

Knowledge check

Check your understanding

Answer this question before you continue.

What happens when this code runs?
Output Prediction

Focus: Predict whether a function-local declaration is accessible from code outside that function.

function greet() {
  const message = "Hello from inside";
  console.log(message);
}

greet();
console.log(message);

The Three Places a Declaration Can Live

Before we drill into each one, here is the map. Every declaration you write lands in one of three scopes:

  • Global scope — declared outside any function or block. Visible everywhere in the script.
  • Function scope — declared inside a function. Visible only within that function.
  • Block scope — declared inside curly braces with let or const. Visible only inside those braces.

Think of nested rooms. The global scope is the whole house. A function is a room inside it. A block is a closet inside that room. You can always look outward from where you stand, but you cannot see into a closet you are not standing in.

There is also module scope, which matters once you start using import and export. We are leaving it aside here so the three core scopes stay clear.

Global Scope: Visible Everywhere, and That Is the Risk

A declaration outside any function or block is global. Every other part of the script can read it.

const appName = "Notes";

function showAppName() {
  console.log(appName);
}

showAppName();
Notes

That convenience is also the danger. In a browser script, global var and function declarations become properties of the global object, which means two separate scripts on the same page can collide on the same name. The second one silently overwrites the first. No error. Just a value that quietly changed.

My rule is simple: keep globals few and intentional. If a function needs a value, pass it in as an argument rather than reaching out to a global. That keeps the dependency visible in the function signature instead of hidden somewhere above it.

Knowledge check

Check your understanding

Answer this question before you continue.

A script declares `const appName = "Notes";` outside every function and block. What happens when a function in that script logs `appName`?
Single Choice

Focus: Recognize that code inside a function can read a declaration from global scope.

Function Scope: What Happens Inside Stays Inside

Every function creates its own scope. Variables declared inside it — with var, let, or const — are local to that function.

Parameters count too. A parameter behaves like a local variable, which surprises a lot of beginners:

function double(value) {
  const result = value * 2;
  console.log(result);
}

double(5);
console.log(result);
10
ReferenceError: result is not defined

value and result both exist only while double is running. Once the function returns, they are not reachable from the code outside it. And because each function has its own scope, two functions can both use a variable named result without ever stepping on each other.

One nuance worth filing away for later: a function's local names are not visible from outside, but an inner function can still hold onto them after the outer function returns. That is a closure, and it is the next topic — not something you need to untangle yet.

Knowledge check

Check your understanding

Answer this question before you continue.

What is the result of running this code?
Output Prediction

Focus: Trace that a variable declared inside a function is unavailable to code outside that function.

function double(value) {
  const result = value * 2;
  console.log(result);
}

double(5);
console.log(result);

Block Scope: Curly Braces as a Boundary

A block is any pair of curly braces: an if, an else, a for, a while, or even a bare { }. Variables declared with let and const inside a block are visible only inside that block.

Here is where var breaks the pattern:

if (true) {
  var escaped = "I leak out";
  let contained = "I stay in";
}

console.log(escaped);
console.log(contained);
I leak out
ReferenceError: contained is not defined

Read that output twice. var ignored the block and attached itself to the surrounding function or global scope. let respected the braces and stayed inside. This single difference is the most surprising behavior beginners run into, and it is the reason modern code defaults to let and const.

Knowledge check

Check your understanding

Answer this question before you continue.

What happens when this code runs?
Output Prediction

Focus: Distinguish block visibility for `var` from block visibility for `let`.

if (true) {
  var escaped = "I leak out";
  let contained = "I stay in";
}

console.log(escaped);
console.log(contained);

How let, const, and var Differ in Visibility

The cleanest way to think about this is the nearest enclosing boundary rule. let and const stop at the nearest block. var skips blocks and stops at the nearest function (or the global scope if there is no function). A function body is itself a block, which is why let and const declared at the top of a function behave like function-scoped variables there.

KeywordNearest boundary it respectsReassignable?Read before declaration?
constNearest blockNoReferenceError
letNearest blockYesReferenceError
varNearest function (or global)Yesundefined

That last column is the temporal dead zone: the window between entering a block and reaching the let or const declaration line. Reading inside that window throws a ReferenceError. A var read before its declaration returns undefined instead — quieter, but usually not what you wanted.

My default: reach for const, switch to let when the binding genuinely needs to change, and treat var as legacy code you will read but rarely write.

The Scope Chain: How JavaScript Finds a Name

Nested regions show a current function inside an outer function inside global scope. Arrows lead outward from the current scope through each enclosing scope, showing the order JavaScript searches for a name.
When a name is missing locally, JavaScript searches outward through the scopes where the code was written.

When you use a name, JavaScript searches the current scope first. If it does not find it, it walks outward through the enclosing scopes until it reaches the global scope. That search path is the scope chain, and it is decided by where the code is written — not where it is called. That is what people mean by lexical scope.

const greeting = "Hello";

function outer() {
  const name = "Sam";

  function inner() {
    console.log(`${greeting}, ${name}`);
  }

  inner();
}

outer();
Hello, Sam

inner has no greeting or name of its own, so it walks outward and finds both. If inner declared its own name, that inner declaration would shadow the outer one — the closer name wins.

Common Beginner Mistakes to Expect

  • Reading let or const before its declaration. You get a ReferenceError, not undefined. Fix: move the declaration above the first use.
  • Assuming var inside an if block is contained by it. It is not. Fix: use let or const.
  • Forgetting that a parameter shadows an outer variable. A parameter named name hides any outer name inside that function. Fix: rename one of them.
  • Assigning to an undeclared name. total = 5 with no declaration creates a global. Fix: always declare with let, const, or var.

Where Scope Shows Up in Real Code

Loop counters are the everyday case. Using let in a for loop gives each iteration its own binding:

for (let i = 0; i < 3; i++) {
  console.log(`Iteration ${i}`);
}
Iteration 0
Iteration 1
Iteration 2

The same pattern shows up in event handlers, where a variable declared in an outer function stays reachable inside a callback, and in keeping helper variables out of the global namespace so two scripts on one page do not collide.

Practice: Trace the Scope Before You Run It

Write down your prediction before running each snippet. That is the whole drill.

const a = 1;
function one() {
  const b = 2;
  console.log(a, b);
}
one();
console.log(b);
function two(x) {
  let y = x + 1;
  return y;
}
console.log(two(4));
console.log(y);
if (true) {
  var leaked = "var";
  let kept = "let";
}
console.log(leaked);
console.log(kept);

Run each one and compare. A wrong prediction is the useful signal — it points at the exact rule you have not internalized yet.

When you can trace these without guessing, you are ready for closures, which are simply the scope chain doing its job after the outer function has already returned.

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

An inner function uses a name that it does not declare itself. How does JavaScript search for that name?
Question 1 of 2Misconception Check

Focus: Explain that name lookup follows enclosing scopes according to where a function is written.

Which statement correctly summarizes how these declarations behave?
Question 2 of 2Single Choice

Focus: Compare the visibility boundaries and pre-declaration reads of `let`, `const`, and `var`.

References

  1. Grammar and types - JavaScript | MDNdeveloper.mozilla.org
  2. Global and local variable scope  |  Articles  |  web.devweb.dev
  3. Variable scope, closurejavascript.info
Practical resource

Want a more structured JavaScript path?

Use the JavaScript for AI Applications Starter Pack to turn individual tutorials into a focused path from language fundamentals to interactive AI applications.

View the bundle
Coming soon

JavaScript for AI Applications Starter Pack

Build the JavaScript foundation behind modern interactive applications. The JavaScript for AI Applications Starter Pack takes you from core language concepts through functions, browser events, forms, DOM updates, application state, debugging, and practical projects—so you can learn how interfaces take input, work with structured data, respond to users, and turn results into experiences people can actually use.

$9
PDF BundleJavaScriptWeb DevelopmentAI Applications
  • 253-page illustrated PDF
  • 12 guided JavaScript chapters
  • Visual concept diagrams
  • Self-assessment quizzes
  • Bonus deep-dive sections
  • DOM, events, forms & application state
  • Browser projects, debugging & practical workflows

Coming soon

Keep learning

Related tutorials

Continue with nearby JavaScript topics and beginner-friendly explanations.