Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Scope and Hoisting: Global, Function and Block Scope, TDZ

Understand scope in JavaScript: global, module, function and block scope, the scope chain and shadowing, hoisting of var, let, const and function declarations, the temporal dead zone, and why to avoid global variables.

Beginner8 min readUpdated
On this page

Scope decides where a variable can be used. A variable declared inside a function or block is not visible outside it. Understanding scope prevents a whole class of bugs and is the foundation for closures.

Block scope#

let and const are block-scoped: they exist only inside the nearest { }:

JavaScript
const discount = 10;

if (discount > 5) {
  const message = "Big discount!";
  console.log(message);
}

console.log(typeof message);
Output
Big discount!
undefined

message does not exist outside the if block. Loop variables declared with let are scoped to the loop.

Function scope#

Variables declared inside a function are local to that function:

JavaScript
function calculate() {
  const result = 6 * 7;
  return result;
}

console.log(calculate());
try {
  console.log(result);
} catch (error) {
  console.log(error.message);
}
Output
42
result is not defined

The scope chain#

Inner code can read variables from outer scopes, but not the other way around. When JavaScript looks up a name, it searches the current scope, then the enclosing scope, and so on outwards to the global scope:

JavaScript
const siteName = "Laviyx";            // outer (module) scope

function outer() {
  const section = "JavaScript";       // outer function scope
  function inner() {
    const lesson = "Scope";           // inner function scope
    console.log(`${siteName} > ${section} > ${lesson}`);
  }
  inner();
}

outer();
Output
Laviyx > JavaScript > Scope

This is called lexical scope: what a function can see depends on where it is written in the code, not where it is called from.

Shadowing#

An inner variable with the same name shadows (hides) the outer one inside its scope:

JavaScript
const level = "global";

function show() {
  const level = "function";
  console.log(level);
}

show();
console.log(level);
Output
function
global

Shadowing is legal but can be confusing; prefer distinct names.

Global scope#

Variables declared at the top level of a classic script are global: visible everywhere, and easy to overwrite by accident from another script. Modules (files using import/export, and the scripts on this site) have their own module scope instead, so top-level variables stay private to the file. Keep globals to a minimum.

Hoisting#

Before running code, JavaScript sets up all declarations in each scope. This is called hoisting, and it behaves differently for each kind of declaration.

Function declarations#

Function declarations are hoisted completely, so you can call them before they appear:

JavaScript
console.log(double(21));

function double(n) {
  return n * 2;
}
Output
42

var#

var declarations are hoisted and initialised to undefined, which hides mistakes:

JavaScript
console.log(count);
var count = 5;
console.log(count);
Output
undefined
5

let, const and the temporal dead zone#

let and const are also hoisted, but not initialised. Using them before their declaration line throws an error. The time between the start of the scope and the declaration is called the temporal dead zone (TDZ):

JavaScript
try {
  console.log(total);
  let total = 10;
} catch (error) {
  console.log(error.name + ": " + error.message);
}
Output
ReferenceError: Cannot access 'total' before initialization

An error is much better than a silent undefined, which is another reason to use let and const rather than var.

Loops and scope#

Because let creates a new binding for each loop iteration, callbacks created in a loop each see their own value:

JavaScript
const callbacks = [];
for (let i = 1; i <= 3; i++) {
  callbacks.push(() => i);
}
console.log(callbacks.map((fn) => fn()));
Output
[ 1, 2, 3 ]

With var i, all three callbacks would share one variable and return 4. This classic interview question is explained in Closures.

Practice#

Practice exercise

Predict the output, then check:

JavaScript
let x = 1;
function test() {
  let x = 2;
  if (true) {
    let x = 3;
    console.log("block", x);
  }
  console.log("function", x);
}
test();
console.log("outer", x);
Show solution
JavaScript
let x = 1;
function test() {
  let x = 2;
  if (true) {
    let x = 3;
    console.log("block", x);
  }
  console.log("function", x);
}
test();
console.log("outer", x);
Output
block 3
function 2
outer 1

Each let x creates a new variable in its own scope that shadows the outer ones.

Key takeaway#

Finished this lesson?