đŸ“Ļ var, let & const

Introduction

Back to the basics with a very short article on the key differences between var , let and const variables.

Scope

Scope is the current context of code, which determines the accessibility of variables. JavaScript has three types of scope: block, function and global.

Block Scope

When let and const variables are declared inside a { } block, they cannot be accessed from outside. They are block scoped. However, var variables are not block scoped, they can be accessed outside.

1if (true) {
2 let greeting = 'yo';
3 const greetingTwo = 'hi';
4 var greetingThree = 'hello';
5}
6
7console.log(greeting); // ReferenceError: greeting is not defined
8console.log(greetingTwo); // ReferenceError: greetingTwo is not defined
9console.log(greetingThree); // 'hello'

This is because variables declared with var are hoisted. In contrast, any variables defined using let and const are hoisted to the top of the block scope, but not initialized. Attempting to use let and const variables before they are declared will result in a ReferenceError.

Hoisting

Definition

Hoisting is JavaScript's default behaviour of moving declarations to the top. A var variable can be used before its declaration. Functions defined using the function keyword are also hoisted. ES6 arrow functions are not hoisted.

Temporal Dead Zone

let and const are hoisted to the top of their block, but they are not initialised. The period between the start of the block and the actual declaration line is called the Temporal Dead Zone (TDZ). Accessing the variable anywhere in that zone throws a ReferenceError, even though the variable technically exists in the scope. This is intentional: it prevents the confusing undefined behaviour you get with var before its declaration.

var gives you undefined before declaration. let/const throw a ReferenceError, which is safer.

Hoisting of var allowing usage before declaration:

1greeting = 'yo'; // assign string to greeting before declaration
2var greeting; // declare greeting

Hoisting of function allowing usage before declaration:

1greetings(); // logs: 'yo'
2
3function greetings() {
4 console.log('yo');
5}
6
7greetingsTwo(); // ReferenceError: Cannot access 'greetingsTwo' before initialization
8
9const greetingsTwo = () => {
10 console.log('hi');
11};

Function Scope

A function creates a scope. Variables declared with var, let and const in a function are locally scoped and cannot be accessed outside.

Similar to block scope, when let and const variables are declared inside a function block, they cannot be accessed from outside. They are function scoped. Variables declared with var are also function scoped and cannot be accessed outside.

1function greetings() {
2 let greeting = 'yo';
3 const greetingTwo = 'hi';
4 var greetingThree = 'hello';
5}
6
7console.log(greeting); // ReferenceError: greeting is not defined
8console.log(greetingTwo); // ReferenceError: greetingTwo is not defined
9console.log(greetingThree); // ReferenceError: greetingThree is not defined

Function Lexical Scope

Lexical scope is where an item got created.

Lexical scope allows function scope to access variables from the parent scope. A child function is said to be lexically bound by that of the parent function. Functions may access variables from their parent scope, all the way up to the global scope, this is called the scope chain.

Definition

JavaScript uses a scope chain to determine the accessibility of variables. When we use a variable, JavaScript searches for the declaration starting at the current scope and continues to any parent scopes until it reaches the global scope. Traversing all scopes is called the scope chain.

Note: functions and objects in JavaScript are also variables.

Here are a few examples of lexical scope in functions below.

1const greeting = 'yo'; // globally scoped
2
3// Define nested functions:
4function greetings() {
5 const greetingTwo = 'hi';
6 function logGreetings() {
7 console.log({ greeting, greetingTwo });
8 }
9 return logGreetings();
10}
11
12greetings(); // logs: {greeting: 'yo', greetingTwo: 'hi'}

The logGreetings nested function has access to greeting via global scope and greetingTwo via lexical scope.

1// Define nested functions:
2function greetings() {
3 console.log(greeting); // ReferenceError: greeting is not defined
4 function logGreetings() {
5 const greeting = 'hello';
6 }
7 return logGreetings();
8}
9
10greetings();

The outer scope of a nested function cannot access variables defined inside the inner scope of another nested child function.

Remember, all variables used inside a function have function scope. Variables declared with var, let and const in a function are locally scoped and cannot be accessed by outer scope.

Global Scope

Variables declared with var, let and const outside block or function scope are globally scoped and accessible across the entire script.

1let greeting = 'yo';
2const greetingTwo = 'hi';
3var greetingThree = 'hello';
4
5function greetings() {
6 console.log(greeting);
7 console.log(greetingTwo);
8 console.log(greetingThree);
9}
10
11greetings();
12// 'yo'
13// 'hi'
14// 'hello;
15
16if (true) {
17 console.log(greeting); // 'yo'
18 console.log(greetingTwo); // 'hi'
19 console.log(greetingThree); // 'hello;
20}
21
22console.log(greeting); // 'yo'
23console.log(greetingTwo); // 'hi'
24console.log(greetingThree); // 'hello;

Var Overview

  • Pre ES6
  • Global scope when declared outside a function.
  • NOT block scoped when declared inside curley blocks { } because of hoisting.
  • Function scope when declared inside a function.
1bye = 'cya'; // fine because of hoisting from line 19
2
3var greeting = 'yo!'; // global scope
4
5function greetings() {
6 console.log(greeting); // 'yo!'
7 var greetingTwo = 'hello'; // local function scope
8}
9
10if (true) {
11 var greetingThree = 'hi'; // block scope
12}
13
14if (true) {
15 greetingThree = 'sup'; // the weakness with var!! value mutated
16}
17
18console.log(greeting); // 'yo!'
19console.log(greetingTwo); // ReferenceError: greetingTwo is not defined
20console.log(greetingThree); // 'sup';
21console.log(bye); // cya
22
23var bye;
  • Can cause bugs! If declared in a block scope, it is accessible everywhere else in the script. The value may be accidentally mutated.
1if (true) {
2 var x = 1;
3}
4
5function somefn() {
6 x = 2; // the weakness with var!! value mutated
7}
8
9somefn();
10
11console.log(x); // 2
  • Can be re-declared and updated.
1var greeting = 'yo!';
2var greeting = 'hi';
3
4// this is fine also
5
6var greetingTwo = 'hello';
7greetingTwo = 'hello there';

Let Overview

  • Post ES6
  • Global scope when declared outside a function.
  • Block scope when declared inside curley blocks { }.
  • Function scope when declared inside a function.
1let greeting = 'yo!'; // global scope
2
3function greetings() {
4 console.log(greeting) // 'yo!'
5 let greetingTwo = 'hello'; // local function scope
6}
7
8if(true) {
9 const greetingThree = 'hi'; // block scope
10}
11
12console.log(greeting) // 'yo!'
13console.log(greetingTwo); // ReferenceError: greetingTwo is not defined
14console.log(greetingThree) // ReferenceError: greetingThree is not defined
  • Can be updated but NOT re-declared.
1let greeting = 'yo!';
2greeting = 'hello';
3
4let greeting = 'hi'; // Identifier 'greeting' has already been declared

Const

  • Post ES6
  • Global scope when declared outside a function.
  • Block scope when declared inside curley blocks { }.
  • Function scope when declared inside a function.
1const greeting = 'yo!'; // global scope
2
3function greetings() {
4 console.log(greeting) // 'yo!'
5 const greetingTwo = 'hello'; // local function scope
6}
7
8if (true) {
9 const greetingThree = 'hi'; // block scope
10}
11
12console.log(greeting) // 'yo!'
13console.log(greetingTwo); // ReferenceError: greetingTwo is not defined
14console.log(greetingThree) // ReferenceError: greetingThree is not defined
  • Can NOT be updated or re-declared.
1const greeting = 'yo!';
2const greeting = 'hi'; // Identifier 'greeting' has already been declared
3
4const greetingTwo = 'hi';
5greetingTwo = 'hello'; // TypeError: Assignment to constant variable
  • If the value of a const variable is any object, properties and values can be updated fine.
1const greeting = { message: 'yo!' };
2const greetings = ['yo', 'hi'];
3
4greeting.message = 'hi'; // object properties can be updated just fine
5greetings.push('sup'); // array properties can be updated just fine
6
7console.log(greeting); // {message: 'hi'}
8console.log(greetings); // (3) ['yo', 'hi', 'sup']
Tip

If you need a truly immutable copy of an object, Object.freeze() prevents property changes on the top level. For deep cloning, structuredClone() is now available natively in all modern browsers and Node.js, no libraries needed.

Final Thoughts

Featurevarletconst
Block scopedNoYesYes
Function scopedYesYesYes
HoistedYes (as undefined)Yes (TDZ)Yes (TDZ)
Re-declarableYesNoNo
UpdatableYesYesNo (binding)
Object props mutableYesYesYes

Hopefully this article helped clear up confusion for beginner JavaScript developers, or acted as a refresher for others. Don't forget to bookmark this article for future reference, you may need it for a job interview in the future.

Was this useful?

Thanks for reading! Time for one more?

// arrow: 'this' is always the class
logName = () => console.log(this.name)
// function: 'this' depends on caller
logName() { console.log(this.name) }

🏹 Regular Function vs Arrow Function

17 Aug 2023 â€ĸ 📖 9 min read â€ĸ Updated 15 Mar 2026
☕ Coffee needed

Learn the differences between normal functions and ES6 arrow functions.

0
0
0
JAVASCRIPT
const Btn = ({ onClick, children }) => (
<button onClick={onClick}>
{children}
</button>
)

🧱 React Component Composition

20 Nov 2022 â€ĸ 📖 10 min read â€ĸ Updated 15 Mar 2026
🔰 Beginner friendly

Understanding component composition in React with a simple food orders app.

0
0
0
JAVASCRIPT REACT