Scope in JavaScript determines where variables, functions, and other identifiers can be accessed in a program. Understanding scope helps you avoid naming conflicts, prevent unexpected behavior, and write clean, maintainable code.
JavaScript has several important scope concepts, including global scope, function scope, block scope, and lexical scope. The scope chain explains how JavaScript searches for variables when they are used inside nested functions or blocks.
In this tutorial, you will learn how each type of scope works, how var, let, and const behave differently, and how JavaScript finds variables through the scope chain.
What Is Scope in JavaScript?
Scope is the area of a program where a variable or function is accessible.
For example:
const message = "Hello, JavaScript!";
function greet() {
console.log(message);
}
greet();
Output:
Hello, JavaScript!
The variable message is declared outside the function. Because it belongs to the outer scope, the greet() function can access it.
However, a variable declared inside a function is generally not accessible outside that function.
function greet() {
const message = "Hello, JavaScript!";
console.log(message);
}
greet();
console.log(message);
The final statement throws a ReferenceError because message is local to the function.
JavaScript scope rules determine which parts of a program can access each variable.
What Is Global Scope in JavaScript?
Global scope is the outermost scope available to ordinary JavaScript code within a particular execution environment or script context.
A variable declared at the top level of a classic browser script using var, let, or const is accessible to code in that script unless another scope shadows it.
Example of Global Scope
const siteName = "Web4Learners";
function displaySiteName() {
console.log(siteName);
}
displaySiteName();
console.log(siteName);
Output:
Web4Learners
Web4Learners
The variable siteName is declared outside the function, so it can be accessed both inside displaySiteName() and from the outer script.
Global Variables in the Browser
In a classic browser script, a top-level var declaration creates a property on the global window object.
var userName = "Karim";
console.log(window.userName);
Output:
Karim
However, top-level let and const declarations in classic scripts do not create corresponding properties on window.
let age = 22;
const country = "India";
console.log(window.age);
console.log(window.country);
Output:
undefined
undefined
The variables still exist in the script’s global lexical environment. They simply are not properties of window.
Important: JavaScript modules have their own module scope. Top-level variables declared in a module do not automatically become global variables or properties of window.
Advantages and Disadvantages of Global Scope
Global variables can be useful when information needs to be shared across a broad part of a program. However, excessive use of global variables can cause problems.
Advantages:
- Values can be accessed from many parts of the same script context.
- Shared configuration can be easy to reference.
- Simple examples can be easier to understand.
Disadvantages:
- Different parts of a program may accidentally reuse the same names.
- Functions can change shared values unexpectedly.
- Large applications become harder to maintain.
- Unnecessary global state can make testing more difficult.
Use local variables and modules when possible, and reserve global scope for values that genuinely need to be globally accessible.
What Is Function Scope in JavaScript?
Function scope means that a variable declared inside a function is accessible within that function and its nested scopes, but not from outside the function.
Variables declared with var, let, and const inside a function are all restricted to that function or its nested scopes.
Example of Function Scope
function calculateTotal() {
const price = 500;
const tax = 50;
console.log(price + tax);
}
calculateTotal();
Output:
550
The variables price and tax are available inside calculateTotal().
Trying to access them outside the function causes a ReferenceError.
function calculateTotal() {
const price = 500;
}
calculateTotal();
console.log(price);
The variable price is not defined in the outer scope.
Function Scope With var
The var keyword is function-scoped rather than block-scoped.
function showMessage() {
var message = "Hello from a function";
console.log(message);
}
showMessage();
Output:
Hello from a function
The message variable cannot be accessed outside showMessage().
Nested Functions and Function Scope
A nested function can access variables declared in its containing function.
function outerFunction() {
const outerMessage = "Outer function";
function innerFunction() {
console.log(outerMessage);
}
innerFunction();
}
outerFunction();
Output:
Outer function
The inner function can access outerMessage because the variable is defined in its outer lexical environment.
This is an important part of lexical scope, which we will explore later in this tutorial.
What Is Block Scope in JavaScript?
Block scope means that a variable is accessible only within the block in which it is declared and any nested scopes.
A block is usually defined by a pair of curly braces {}. Blocks can appear in if statements, loops, and standalone code blocks.
The let and const keywords are block-scoped. The var keyword is not.
Example of Block Scope With let
if (true) {
let message = "Inside the block";
console.log(message);
}
Output:
Inside the block
The variable message is accessible inside the if block.
Trying to access it outside the block causes a ReferenceError.
if (true) {
let message = "Inside the block";
}
console.log(message);
The variable exists only within the block where it was declared.
Block Scope With const
The const keyword also creates block-scoped variables.
if (true) {
const siteName = "Web4Learners";
console.log(siteName);
}
Output:
Web4Learners
The siteName variable cannot be accessed outside the block.
Remember that const prevents reassignment of the variable binding, but it does not make an object or array immutable.
Difference Between var, let, and const in Blocks
Consider this example:
if (true) {
var firstName = "Karim";
let lastName = "Shaik";
const age = 22;
}
console.log(firstName);
Output:
Karim
The var variable is accessible outside the block because var is not block-scoped.
However, accessing lastName or age outside the block would cause a ReferenceError.
| Keyword | Scope inside a function | Block-scoped | Can be reassigned |
|---|---|---|---|
var | Yes | No | Yes |
let | Yes | Yes | Yes |
const | Yes | Yes | No, the binding cannot be reassigned |
Block Scope in Loops
Block scope is especially useful in loops.
for (let i = 1; i <= 3; i++) {
console.log(i);
}
Output:
1
2
3
The variable i is scoped to the loop.
Accessing i after the loop finishes causes a ReferenceError.
By contrast, using var would make the loop variable accessible in the surrounding function or script scope.
What Is Lexical Scope in JavaScript?
Lexical scope means that the accessibility of variables is determined by where functions and blocks are written in the source code.
A function can access variables from its own scope and from the surrounding scopes in which it was defined.
JavaScript does not determine a function’s outer variable scope based on where the function is called. It is based on where the function was created.
Example of Lexical Scope
const language = "JavaScript";
function outerFunction() {
const message = "Learning";
function innerFunction() {
console.log(message, language);
}
innerFunction();
}
outerFunction();
Output:
Learning JavaScript
The innerFunction() function can access both message and language.
messageis defined in the containing function.languageis defined in the global scope.
This is lexical scope in action.
Lexical Scope With Nested Functions
Consider a function that contains another function.
function outer() {
const name = "Karim";
function inner() {
const greeting = "Hello";
console.log(greeting, name);
}
inner();
}
outer();
Output:
Hello Karim
The inner function can access greeting from its own scope and name from the outer function’s scope.
The outer function cannot access the inner function’s local variable greeting.
Lexical Scope Does Not Depend on Where a Function Is Called
This example demonstrates a key rule.
const name = "Global";
function outer() {
const name = "Outer";
return function inner() {
console.log(name);
};
}
const result = outer();
function runFunction(callback) {
const name = "Caller";
callback();
}
runFunction(result);
Output:
Outer
Why does the output say "Outer" instead of "Caller"?
The inner() function was defined inside outer(). Therefore, its lexical environment includes the name variable declared in outer().
Calling it from runFunction() does not change the scope in which it was created.
This principle is fundamental to understanding closures and callback functions.
What Is the Scope Chain in JavaScript?
The scope chain is the sequence of lexical environments JavaScript searches when resolving a variable name.
When JavaScript encounters a variable reference, it first looks in the current scope. If the name is not found, it searches the enclosing scope, then the next outer scope, continuing until it finds the name or reaches the outermost environment.
If the name cannot be resolved, JavaScript throws a ReferenceError when the unresolved identifier is evaluated.
Example of the Scope Chain
const country = "India";
function outerFunction() {
const state = "Telangana";
function innerFunction() {
const city = "Hyderabad";
console.log(city);
console.log(state);
console.log(country);
}
innerFunction();
}
outerFunction();
Output:
Hyderabad
Telangana
India
The innerFunction() function accesses variables from three levels:
- Its own scope contains
city. - Its outer function scope contains
state. - The global scope contains
country.
JavaScript follows the scope chain to find each variable.
How JavaScript Searches for Variables
<box border radius=”lg” padding={3} gap={3}> <box background=”surface-secondary” radius=”md” padding={3} align=”center” gap={1}> **Inner Function Scope**
<text size="sm">`city = "Hyderabad"`</text>
<badge color="info">Search starts here</badge>
</box> <box align=”center” gap={1}> <icon name=”arrow-down” size=”lg” /> <text color=”secondary” size=”sm”>If the variable is not found</text> </box> <box background=”surface” border radius=”md” padding={3} align=”center” gap={1}> **Outer Function Scope**
<text size="sm">`state = "Telangana"`</text>
</box> <box align=”center” gap={1}> <icon name=”arrow-down” size=”lg” /> <text color=”secondary” size=”sm”>If the variable is still not found</text> </box> <box background=”surface” border radius=”md” padding={3} align=”center” gap={1}> **Global Scope**
<text size="sm">`country = "India"`</text>
</box> </box>
This is a simplified illustration of lexical lookup. If a variable is not found in any accessible enclosing environment, the lookup fails.
Variable Shadowing
Variable shadowing occurs when a variable declared in an inner scope has the same name as a variable in an outer scope.
const message = "Global message";
function showMessage() {
const message = "Local message";
console.log(message);
}
showMessage();
console.log(message);
Output:
Local message
Global message
Inside showMessage(), JavaScript finds the local message first. It does not continue to the outer variable because a matching name has already been found.
Outside the function, the global message remains unchanged.
Shadowing is valid in many situations, but confusing or excessive reuse of names can make code harder to understand.
How Scope Works With Closures
A closure is created when a function retains access to variables from its lexical environment, even after the outer function has finished executing.
Closures rely on lexical scope and the scope chain.
Example of a Closure
function createCounter() {
let count = 0;
return function () {
count++;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
Output:
1
2
3
The createCounter() function returns an inner function. Although createCounter() has finished executing, the returned function retains access to count.
Each call to counter() updates the same variable.
Why Closures Are Useful
Closures can be used to:
- Preserve state between function calls.
- Create functions with private internal variables.
- Build reusable callbacks.
- Implement function factories.
- Manage event handlers and asynchronous operations.
For example, a counter closure allows the count variable to remain accessible to the returned function without exposing it as a global variable.
Common Scope Mistakes in JavaScript
Accessing a Function Variable From Outside
Incorrect:
function calculate() {
const total = 100;
}
calculate();
console.log(total);
The code throws a ReferenceError because total is defined inside the function.
Correct:
function calculate() {
const total = 100;
return total;
}
console.log(calculate());
Output:
100
Returning a value allows the caller to receive the result without directly accessing the function’s local variable.
Assuming var Is Block-Scoped
Incorrect assumption:
if (true) {
var score = 90;
}
console.log(score);
Output:
90
The variable is accessible because var is not block-scoped.
Use let or const when you want a variable limited to the block.
Expecting the Calling Function to Define Lexical Scope
A function uses the lexical environment in which it was created, not the local variables of whichever function calls it.
This is why the earlier inner() example prints "Outer" rather than "Caller".
Confusing Scope With Object Properties
A variable declared inside a function is not automatically a property of an object. Scope determines where a name can be resolved; object properties are accessed through an object.
These are different concepts, even though both involve accessing values.
Global Scope vs Function Scope vs Block Scope vs Lexical Scope
| Scope concept | What it means | Example |
|---|---|---|
| Global scope | Variables accessible across the relevant script or environment | const siteName = "Web4Learners"; |
| Function scope | Variables declared inside a function are local to that function | function test() { const x = 10; } |
| Block scope | let and const are limited to their enclosing block | if (true) { let x = 10; } |
| Lexical scope | Variable accessibility follows where code is written | An inner function accesses variables from its outer function |
| Scope chain | The sequence of outer environments searched for a variable | Inner scope → outer scope → global environment |
Lexical scope describes how the environment is determined by the source code. The scope chain describes how JavaScript searches those environments to resolve identifiers.
Frequently Asked Questions
What is scope in JavaScript?
Scope determines where variables, functions, and other identifiers can be accessed within a program.
What is the difference between global scope and function scope?
Global scope makes a declaration available throughout its relevant script or environment, subject to its declaration rules. Function scope restricts a variable to the function in which it is declared and its nested scopes.
What is the difference between function scope and block scope?
Function scope applies to declarations such as var inside a function. Block scope restricts let and const to the block where they are declared.
What is lexical scope in JavaScript?
Lexical scope means a function’s access to outer variables is determined by where the function is defined in the source code, not by where it is called.
What is the scope chain?
The scope chain is the sequence of lexical environments JavaScript searches to resolve a variable reference, starting with the current environment and moving outward.
What is variable shadowing?
Variable shadowing occurs when a declaration in an inner scope uses the same name as a declaration in an outer scope. The inner declaration takes precedence within its scope.
How are closures related to scope?
Closures retain access to variables from the lexical environment in which a function was created. This allows functions to preserve state even after an outer function has returned.
Conclusion
JavaScript scope determines where variables can be accessed and how identifiers are resolved.
Global scope makes declarations available across a broad execution context, function scope keeps local variables inside functions, and block scope limits let and const to specific blocks. Lexical scope determines which outer variables a function can access, while the scope chain explains how JavaScript searches for those variables.
Once you understand these concepts, closures, callbacks, event handlers, and modern JavaScript applications become much easier to understand and debug.