JavaScript Variable Scope
Understanding variable scope is essential for writing bug-free JavaScript code. Scope determines where variables are accessible in your code and how long they exist.
Try It Yourself
Experiment with different scope levels to see how variable accessibility changes:
// Experiment with different scopes
let globalVar = "I'm global";
function outerFunction() {
let outerVar = "I'm in outer function";
function innerFunction() {
let innerVar = "I'm in inner function";
// Try accessing different variables
console.log("From inner:");
console.log("- innerVar:", innerVar);
console.log("- outerVar:", outerVar);
console.log("- globalVar:", globalVar);
}
innerFunction();
console.log("\nFrom outer:");
console.log("- outerVar:", outerVar);
console.log("- globalVar:", globalVar);
// console.log("- innerVar:", innerVar); // Would cause error
}
outerFunction();
console.log("\nFrom global:");
console.log("- globalVar:", globalVar);
// console.log("- outerVar:", outerVar); // Would cause errorWhat is Variable Scope?
Scope determines the accessibility and visibility of variables in different parts of your code. JavaScript has three main types of scope:
- Global Scope - Variables accessible from anywhere
- Function Scope - Variables accessible only within a function
- Block Scope - Variables accessible only within a block (ES6+)
Global Scope
Variables declared outside any function or block have global scope. They can be accessed from anywhere in your code.
// Global variable - accessible everywhere
let globalMessage = "Hello, World!";
function greet() {
console.log(globalMessage); // Can access global variable
}
greet(); // "Hello, World!"
console.log(globalMessage); // "Hello, World!"Function (Local) Scope
Variables declared inside a function are local to that function. They cannot be accessed from outside the function.
function calculateTotal() {
// Local variable - only accessible inside this function
let price = 100;
let tax = 0.1;
let total = price + (price * tax);
console.log(total); // 110
}
calculateTotal();
// console.log(price); // Error: price is not definedLocal variables are created when a function starts executing and are destroyed when the function completes. Each function call creates a new set of local variables.
Block Scope (ES6)
With let and const, variables can be scoped to any block of code (code between curly braces {}). This includes if statements, loops, and standalone blocks.
// Block scope with let and const
if (true) {
let blockVar = "I'm block-scoped";
const BLOCK_CONST = "Me too!";
console.log(blockVar); // Works
}
// console.log(blockVar); // Error: blockVar is not defined
// var ignores block scope
if (true) {
var functionVar = "I escape the block!";
}
console.log(functionVar); // Works! "I escape the block!"Block Scope in Loops
Block scope is particularly important in loops, especially with asynchronous callbacks:
// Using var in a loop (common bug)
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Outputs: 3, 3, 3 (not 0, 1, 2!)
// Using let in a loop (correct behavior)
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// Outputs: 0, 1, 2var in loops with async callbacks is a common source of bugs. The variable is shared across all iterations, so by the time the callbacks run, the loop has finished and the variable holds its final value. var vs let vs const Scope
| Feature | var | let | const |
|---|---|---|---|
| Global Scope | |||
| Function Scope | |||
| Block Scope | |||
| Can be redeclared | |||
| Hoisted with value | |||
| Temporal Dead Zone |
The Scope Chain
When JavaScript looks for a variable, it starts in the current scope and moves outward through enclosing scopes until it finds the variable or reaches the global scope. This is called the scope chain.
let global = "I'm global";
function outer() {
let outerVar = "I'm in outer";
function inner() {
let innerVar = "I'm in inner";
// Can access all variables in the scope chain
console.log(innerVar); // "I'm in inner"
console.log(outerVar); // "I'm in outer"
console.log(global); // "I'm global"
}
inner();
// console.log(innerVar); // Error: innerVar is not defined
}
outer();Lexical Scope and Closures
JavaScript uses lexical scoping (also called static scoping), which means the scope of a variable is determined by where it is written in the code, not where it is called from.
A closure is created when a function "remembers" variables from its outer scope even after the outer function has finished executing:
function createCounter() {
let count = 0; // This variable is "enclosed"
return function() {
count++; // Inner function can access outer's variables
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count is not accessible from here
// console.log(count); // ErrorVariable Shadowing
When a variable in an inner scope has the same name as a variable in an outer scope, the inner variable "shadows" the outer one within that scope:
let name = "Global Name";
function showName() {
let name = "Local Name"; // Shadows the global variable
console.log(name); // "Local Name"
}
showName();
console.log(name); // "Global Name" (unchanged)Nested Block Scope
Block scope follows the same chain principle as function scope. Inner blocks can access outer block variables, but not vice versa:
function demo() {
let a = 1;
if (true) {
let b = 2;
if (true) {
let c = 3;
console.log(a, b, c); // 1 2 3 - all accessible
}
// console.log(c); // Error: c is not defined
console.log(a, b); // 1 2 - a and b accessible
}
// console.log(b); // Error: b is not defined
console.log(a); // 1 - only a accessible
}The Module Pattern
Closures can be used to create private variables and methods, implementing encapsulation in JavaScript:
// Using closures to create private scope
const bankAccount = (function() {
// Private variable - not accessible outside
let balance = 0;
return {
deposit: function(amount) {
balance += amount;
return balance;
},
withdraw: function(amount) {
if (amount <= balance) {
balance -= amount;
return balance;
}
return "Insufficient funds";
},
getBalance: function() {
return balance;
}
};
})();
console.log(bankAccount.deposit(100)); // 100
console.log(bankAccount.withdraw(30)); // 70
console.log(bankAccount.getBalance()); // 70
// console.log(balance); // Error: balance is not definedimport/export) provide a native way to create private scope at the module level. Scope Best Practices
1. Minimize Global Variables
// Bad: Global pollution
var userName = "John";
var userAge = 30;
var userEmail = "john@example.com";
// Good: Using an object namespace
const user = {
name: "John",
age: 30,
email: "john@example.com"
};
// Better: Using a module or closure
const UserModule = (function() {
const name = "John";
const age = 30;
return {
getName: () => name,
getAge: () => age
};
})();2. Use Block Scope for Temporary Variables
// IIFE creates its own scope
(function() {
var privateVar = "I'm private";
let alsoPrivate = "Me too";
// Do something with these variables
console.log(privateVar);
})();
// Variables are not accessible outside
// console.log(privateVar); // Error3. Declare Variables at the Top of Their Scope
For better readability, declare variables at the beginning of their scope. This makes it clear what variables are available and prevents confusion with hoisting.
4. Use const by Default, let When Needed
Start with const and only use let when you need to reassign. Avoid var in modern JavaScript.
Common Scope Mistakes
1. Accidentally Creating Global Variables
function badExample() {
// Missing declaration keyword creates global variable!
accidentalGlobal = 'Oops';
}
badExample();
console.log(accidentalGlobal); // 'Oops' - it's global!
// Use 'use strict' to prevent this
'use strict';
function goodExample() {
// strictModeVar = 'Error'; // ReferenceError in strict mode
let properVar = 'Safe';
}2. Assuming var Has Block Scope
function varMistake() {
if (true) {
var x = 10;
}
console.log(x); // 10 - var escapes the block!
}
function letCorrect() {
if (true) {
let y = 10;
}
// console.log(y); // ReferenceError - let respects block scope
}3. Closure in Loops with var
// Problem: All buttons log 5
for (var i = 0; i < 5; i++) {
// This won't work as expected
// buttons[i].onclick = function() { console.log(i); };
}
// Solution 1: Use let
for (let i = 0; i < 5; i++) {
// Each iteration gets its own i
// buttons[i].onclick = function() { console.log(i); };
}
// Solution 2: Create a new scope with IIFE
for (var i = 0; i < 5; i++) {
(function(index) {
// buttons[index].onclick = function() { console.log(index); };
})(i);
}Test Your Knowledge
Variable Scope Quiz
5 questionsWhat will this code output? if (true) { var x = 5; } console.log(x);
What will this code output? if (true) { let y = 10; } console.log(y);
What is the scope chain order for variable lookup?
What is variable shadowing?
Which statement about closures is true?
Coding Challenge
Use closures to create a counter with private state. The internal count variable should not be directly accessible from outside the returned object.
// Challenge: Create a counter with private state
// The counter should have:
// - A private count variable (not accessible from outside)
// - An increment() method that adds 1
// - A decrement() method that subtracts 1
// - A getCount() method that returns current count
// - A reset() method that sets count back to 0
function createCounter() {
// Your code here
}
// Test your counter
const myCounter = createCounter();
console.log(myCounter.getCount()); // Should print: 0
myCounter.increment();
myCounter.increment();
console.log(myCounter.getCount()); // Should print: 2
myCounter.decrement();
console.log(myCounter.getCount()); // Should print: 1
myCounter.reset();
console.log(myCounter.getCount()); // Should print: 0Summary
- Global scope variables are accessible everywhere but should be minimized
- Function scope keeps variables local to a function, created with any declaration keyword
- Block scope (with
letandconst) limits variables to the nearest{}block - The scope chain allows inner scopes to access outer scope variables
- Closures allow functions to remember their outer scope even after the outer function has returned
- Use
constby default,letwhen reassignment is needed, and avoidvar - Variable shadowing occurs when inner scope variables have the same name as outer scope variables