đĻ 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}67console.log(greeting); // ReferenceError: greeting is not defined8console.log(greetingTwo); // ReferenceError: greetingTwo is not defined9console.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
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 declaration2var greeting; // declare greeting
Hoisting of function allowing usage before declaration:
1greetings(); // logs: 'yo'23function greetings() {4 console.log('yo');5}67greetingsTwo(); // ReferenceError: Cannot access 'greetingsTwo' before initialization89const 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}67console.log(greeting); // ReferenceError: greeting is not defined8console.log(greetingTwo); // ReferenceError: greetingTwo is not defined9console.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.
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 scoped23// Define nested functions:4function greetings() {5 const greetingTwo = 'hi';6 function logGreetings() {7 console.log({ greeting, greetingTwo });8 }9 return logGreetings();10}1112greetings(); // 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 defined4 function logGreetings() {5 const greeting = 'hello';6 }7 return logGreetings();8}910greetings();
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';45function greetings() {6 console.log(greeting);7 console.log(greetingTwo);8 console.log(greetingThree);9}1011greetings();12// 'yo'13// 'hi'14// 'hello;1516if (true) {17 console.log(greeting); // 'yo'18 console.log(greetingTwo); // 'hi'19 console.log(greetingThree); // 'hello;20}2122console.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 1923var greeting = 'yo!'; // global scope45function greetings() {6 console.log(greeting); // 'yo!'7 var greetingTwo = 'hello'; // local function scope8}910if (true) {11 var greetingThree = 'hi'; // block scope12}1314if (true) {15 greetingThree = 'sup'; // the weakness with var!! value mutated16}1718console.log(greeting); // 'yo!'19console.log(greetingTwo); // ReferenceError: greetingTwo is not defined20console.log(greetingThree); // 'sup';21console.log(bye); // cya2223var 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}45function somefn() {6 x = 2; // the weakness with var!! value mutated7}89somefn();1011console.log(x); // 2
- Can be re-declared and updated.
1var greeting = 'yo!';2var greeting = 'hi';34// this is fine also56var 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 scope23function greetings() {4 console.log(greeting) // 'yo!'5 let greetingTwo = 'hello'; // local function scope6}78if(true) {9 const greetingThree = 'hi'; // block scope10}1112console.log(greeting) // 'yo!'13console.log(greetingTwo); // ReferenceError: greetingTwo is not defined14console.log(greetingThree) // ReferenceError: greetingThree is not defined
- Can be updated but NOT re-declared.
1let greeting = 'yo!';2greeting = 'hello';34let 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 scope23function greetings() {4 console.log(greeting) // 'yo!'5 const greetingTwo = 'hello'; // local function scope6}78if (true) {9 const greetingThree = 'hi'; // block scope10}1112console.log(greeting) // 'yo!'13console.log(greetingTwo); // ReferenceError: greetingTwo is not defined14console.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 declared34const 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'];34greeting.message = 'hi'; // object properties can be updated just fine5greetings.push('sup'); // array properties can be updated just fine67console.log(greeting); // {message: 'hi'}8console.log(greetings); // (3)Â ['yo', 'hi', 'sup']
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
| Feature | var | let | const |
|---|---|---|---|
| Block scoped | No | Yes | Yes |
| Function scoped | Yes | Yes | Yes |
| Hoisted | Yes (as undefined) | Yes (TDZ) | Yes (TDZ) |
| Re-declarable | Yes | No | No |
| Updatable | Yes | Yes | No (binding) |
| Object props mutable | Yes | Yes | Yes |
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 classlogName = () => console.log(this.name)// function: 'this' depends on callerlogName() { console.log(this.name) }
đš Regular Function vs Arrow Function
17 Aug 2023 âĸ đ 9 min read âĸ Updated 15 Mar 2026Learn the differences between normal functions and ES6 arrow functions.
const Btn = ({ onClick, children }) => (<button onClick={onClick}>{children}</button>)
đ§ą React Component Composition
20 Nov 2022 âĸ đ 10 min read âĸ Updated 15 Mar 2026Understanding component composition in React with a simple food orders app.