JavaScript: Stop Using var. A Visual Guide to const and let
Why modern JavaScript uses const and let instead of var: reassignment, block vs. function scope, the loop-closure bug, hoisting and the temporal dead zone, redeclaration, real Node.js runs, a speed test, and exercises with answers.

On this page
For its first twenty years, JavaScript had exactly one way to declare a variable: var. Then ES6 (also called ES2015) added two more, const and let, and today most style guides boil the advice down to three words: stop using var.
That isn't just fashion. var has quirks that cause real bugs: variables that leak out of blocks, loops whose callbacks all see the same number, and variables you can use before they're declared. In this guide you'll see each quirk run for real, learn the simple rule for choosing between const and let, and practice spotting and fixing var bugs. Exercises with answers wrap it up.
All the outputs below come from real runs with Node.js 26. They're the same in every modern browser.
The rule in one picture

1// The old way
2var name = "Angela";
3
4// The new way
5const name = "Angela";- Start with
const. Most variables are given a value once and never reassigned. - Switch to
letonly when you need to reassign the variable with=later. - Don't use
varin new code.
Let's see why, one behavior at a time.
const: assigned once, for good
A const variable gets its value when it's declared and can never be reassigned:
1const greeting = "Hello";
2console.log(greeting);
3
4try {
5 greeting = "Goodbye";
6} catch (error) {
7 console.log(`${error.name}: ${error.message}`);
8}Hello
TypeError: Assignment to constant variable.(We catch the error so the program can print it and keep going.) Because it can never be assigned later, a const must get a value right away. This program doesn't even start:
const answer;SyntaxError: Missing initializer in const declarationThat's the point of const: when you read code and see const total = …, you know total holds that value everywhere below. No hunting for a line that changes it.
const locks the label, not the box
Here's the part that surprises people. You can still change what's inside a const array or object:
1const pets = [];
2pets.push("cat");
3pets.push("dog");
4console.log(pets);
5
6const settings = { theme: "light" };
7settings.theme = "dark";
8settings.fontSize = 16;
9console.log(settings);
10
11try {
12 pets = ["hamster"];
13} catch (error) {
14 console.log(`${error.name}: ${error.message}`);
15}[ 'cat', 'dog' ]
{ theme: 'dark', fontSize: 16 }
TypeError: Assignment to constant variable.![const locks the label, not the box. The variable pets is a label permanently attached to one array. pets.push("cat") and pets.push("dog") are allowed: they change what is inside the same array, which now holds "cat" and "dog". pets = ["hamster"] is not allowed: it tries to move the label to a different array, giving TypeError: Assignment to constant variable. const means "this name always refers to this value", not "this value never changes". To freeze the contents too, use Object.freeze, which is shallow](https://nbktechworld.us-southeast-1.linodeobjects.com/static/images/c674439b-1a54-49dc-89db-509ec6a9523c.png?v=1792875896867)
A variable is a label stuck on a value. const glues the label to one array forever, but the array itself can still change. Only pets = …, which tries to move the label, is forbidden.
If you want the contents locked too, Object.freeze does that, but only one level deep:
1const place = Object.freeze({ city: "Lisbon", tags: ["sea"] });
2place.city = "Porto";
3place.tags.push("sun");
4console.log(place);{ city: 'Lisbon', tags: [ 'sea', 'sun' ] }The city didn't change (outside strict mode the assignment is silently ignored; inside it, it throws), but the array inside could still grow.
let: when the value has to change
Use let when the variable genuinely gets new values over time: counters, running totals, loop indexes, or a value decided by an if:
1let total = 0;
2for (const price of [3.5, 4.25, 2]) {
3 total += price;
4}
5console.log(total);
6
7let message = "Good morning";
8const hour = 15;
9if (hour >= 12) {
10 message = "Good afternoon";
11}
12console.log(message);9.75
Good afternoonNotice the loop uses const price: each trip around a for…of loop gets a brand-new price, so it's never reassigned. A classic counting loop is different, because index++ reassigns the variable:
1const letters = ["a", "b", "c"];
2for (let index = 0; index < letters.length; index++) {
3 console.log(index, letters[index]);
4}0 a
1 b
2 cScope: where a variable exists
Scope is the region of code where a variable can be used. This is where var and let/const differ the most:
letandconstare block-scoped: they exist only inside the nearest pair of curly braces{ }, like anifbody or a loop.varis function-scoped: it ignores blocks and belongs to the whole function around it.

1function checkScopes() {
2 if (true) {
3 var leaky = "var";
4 let contained = "let";
5 }
6 console.log(leaky);
7 try {
8 console.log(contained);
9 } catch (error) {
10 console.log(`${error.name}: ${error.message}`);
11 }
12}
13
14checkScopes();var
ReferenceError: contained is not definedThe same thing happens with loop variables. A var index outlives its loop:
1for (var oldIndex = 0; oldIndex < 3; oldIndex++) {
2 console.log(oldIndex);
3}
4console.log("after the var loop:", oldIndex);
5
6for (let newIndex = 0; newIndex < 3; newIndex++) {
7 console.log(newIndex);
8}
9try {
10 console.log("after the let loop:", newIndex);
11} catch (error) {
12 console.log(`${error.name}: ${error.message}`);
13}10
21
32
4after the var loop: 3
50
61
72
8ReferenceError: newIndex is not definedA leaked variable might seem harmless, but it means a loop counter or a temporary value can collide with another variable of the same name elsewhere in the function. Block scope keeps each variable where it belongs.
The classic loop bug
This is the bug that made many people switch for good. Each loop iteration schedules a function to run a moment later:
1for (var index = 0; index < 3; index++) {
2 setTimeout(() => console.log("var:", index), 0);
3}
4
5for (let index = 0; index < 3; index++) {
6 setTimeout(() => console.log("let:", index), 0);
7}1var: 3
2var: 3
3var: 3
4let: 0
5let: 1
6let: 2
The functions run after the loop has finished. With var there's a single index for the whole loop, and by then it's 3. With let, JavaScript creates a fresh index for every iteration, so each function remembers its own value. The same bug shows up with click handlers, timers and promises created in a loop, and let fixes all of them without any extra code.
Hoisting and the temporal dead zone
JavaScript sets up a function's variables before running its first line. That's called hoisting. What a variable holds before its declaration line is where the keywords differ:
1function beforeDeclaring() {
2 console.log("var before its line:", early);
3 var early = "ready";
4 console.log("var after its line:", early);
5
6 try {
7 console.log(late);
8 } catch (error) {
9 console.log(`${error.name}: ${error.message}`);
10 }
11 let late = "ready";
12}
13
14beforeDeclaring();var before its line: undefined
var after its line: ready
ReferenceError: Cannot access 'late' before initialization
A var exists from the top of its function and quietly holds undefined until its line runs, so a mistake gives you a wrong value instead of an error. A let or const also exists from the top of its block, but touching it before its line throws. That stretch is called the temporal dead zone. Loud errors are good: they point at the mistake instead of letting undefined travel through your program.
Declaring the same name twice
var lets you declare a variable again without a peep, which can silently overwrite one that's already in use:
var score = 10;
var score = 0;
console.log(score);0With let or const, the program refuses to run:
let score = 10;
let score = 0;SyntaxError: Identifier 'score' has already been declaredAnd one more var quirk: in a regular browser script, a var declared at the top level becomes a property of the global window object, where any other script on the page can read or overwrite it. Top-level let and const don't.
Is any of them faster?
No. We timed the same summing loop written three ways, with 50 million iterations:

All three came in at about 1.3 nanoseconds per iteration, and the small differences moved around from run to run. JavaScript engines compile all three to the same fast code, so choose by clarity and correctness alone.
Side by side
var | let | const | |
|---|---|---|---|
| Can be reassigned | Yes | Yes | No |
| Must be given a value right away | No | No | Yes |
| Scope | Whole function | Nearest { } block | Nearest { } block |
| Fresh copy per loop iteration | No (one shared variable) | Yes | Yes (in for…of / for…in) |
| Used before its line | undefined | ReferenceError | ReferenceError |
| Declared twice | Allowed | SyntaxError | SyntaxError |
Top-level variable becomes window property | Yes | No | No |
| Use it in new code | No | When reassigning | By default |

Modernizing old code
Converting var code is mostly mechanical. Take this function:
1function summarize(prices) {
2 var total = 0;
3 var count = prices.length;
4 for (var index = 0; index < count; index++) {
5 total += prices[index];
6 }
7 var average = total / count;
8 return "Total " + total + ", average " + average.toFixed(2);
9}
10
11console.log(summarize([3.5, 4.25, 2]));Total 9.75, average 3.25Go through each var: is it ever reassigned after its declaration? total is (+=) and so is index (++): they become let. count and average never are: they become const.
1function summarize(prices) {
2 let total = 0;
3 const count = prices.length;
4 for (let index = 0; index < count; index++) {
5 total += prices[index];
6 }
7 const average = total / count;
8 return `Total ${total}, average ${average.toFixed(2)}`;
9}
10
11console.log(summarize([3.5, 4.25, 2]));Total 9.75, average 3.25Same output, and now each declaration tells the reader whether the value changes. (The template literal with backticks is another ES6 improvement.)
Your turn: practice
Try these before peeking at the answers.
Exercise 1: Predict the output (warm-up). What do the three console.log lines print?
1const colors = ["red"];
2colors.push("green");
3console.log(colors.length);
4
5let count = 1;
6count = count + 1;
7console.log(count);
8
9var shade = "dark";
10if (true) {
11 var shade = "light";
12}
13console.log(shade);Exercise 2: const or let? Pick a keyword for each variable: a user's birth year, a score that goes up during a game, a list of to-dos you keep adding to, and the result of Math.max(...numbers).
Exercise 3: Fix the buttons. This code is supposed to report "Clicked button 0", "Clicked button 1" and "Clicked button 2", but every handler reports button 3. Fix it.
1const handlers = [];
2for (var buttonNumber = 0; buttonNumber < 3; buttonNumber++) {
3 handlers.push(() => console.log("Clicked button " + buttonNumber));
4}
5handlers.forEach((handler) => handler());Exercise 4: Spot the error. What happens when this runs, and why?
1function greet() {
2 console.log(message);
3 const message = "Hi!";
4}
5greet();Exercise 5: Modernize (challenge). Convert every var to const or let, choosing const whenever you can, and make sure the output doesn't change.
1var words = ["tree", "sky", "banana", "sun"];
2var longest = "";
3for (var index = 0; index < words.length; index++) {
4 var word = words[index];
5 if (word.length > longest.length) {
6 longest = word;
7 }
8}
9console.log("Longest word: " + longest);Quick quiz
- Can you
pushinto an array declared withconst? - Where does a variable declared with
letinside anifblock exist? - Why does a
varloop withsetTimeoutprint the same number three times? - What do you get when you use a
varbefore its declaration line? And alet? - Which keyword should you start with?
Answers
Exercise 1: push changes the array's contents, which const allows; count is reassigned, which let allows; and the second var shade isn't a new variable at all, because var ignores the if block and simply reassigns the same shade:
1const colors = ["red"];
2colors.push("green");
3console.log(colors.length);
4
5let count = 1;
6count = count + 1;
7console.log(count);
8
9var shade = "dark";
10if (true) {
11 var shade = "light";
12}
13console.log(shade);2
2
lightWith let shade both times, the inner one would be a separate variable living only in the block, and the last line would print dark.
Exercise 2: Birth year: const (it never changes). Game score: let (score += 10 reassigns it). To-do list: const (push changes the contents, not the variable). Math.max(...numbers): const (it's computed once).
Exercise 3: Change var to let, so every iteration gets its own buttonNumber:
1const handlers = [];
2for (let buttonNumber = 0; buttonNumber < 3; buttonNumber++) {
3 handlers.push(() => console.log("Clicked button " + buttonNumber));
4}
5handlers.forEach((handler) => handler());Clicked button 0
Clicked button 1
Clicked button 2Exercise 4: It throws, because message is used inside its temporal dead zone, before its const line:
1function greet() {
2 try {
3 console.log(message);
4 } catch (error) {
5 console.log(`${error.name}: ${error.message}`);
6 }
7 const message = "Hi!";
8}
9greet();ReferenceError: Cannot access 'message' before initializationWith var message, it would print undefined instead and hide the mistake. The fix is to declare the variable before using it.
Exercise 5: words is never reassigned, so it's const (we only read it). longest changes inside the loop, so it's let. index is reassigned by index++: let. word gets a fresh variable on every iteration and is never reassigned within one: const.
1const words = ["tree", "sky", "banana", "sun"];
2let longest = "";
3for (let index = 0; index < words.length; index++) {
4 const word = words[index];
5 if (word.length > longest.length) {
6 longest = word;
7 }
8}
9console.log("Longest word: " + longest);Longest word: bananaEven tidier: for (const word of words) removes the index altogether.
Quiz: (1) Yes: const stops you from reassigning the variable, not from changing the array's contents. (2) Only inside that block, between its curly braces, from its declaration line on. (3) There's only one var variable for the whole loop, and the callbacks run after the loop ends, when it's already 3. (4) A var gives undefined; a let throws ReferenceError: Cannot access … before initialization. (5) const.
Cheat sheet
| Situation | Use |
|---|---|
| A value you set once | const |
| An array or object you add to or change inside | const |
| A counter, total or flag that you reassign | let |
A classic for (… ; … ; index++) loop index | let |
A for…of loop variable | const |
A value chosen by an if / else | let (or const with the ? : operator) |
| Contents that must never change | const plus Object.freeze |
| New code | Never var |
Wrapping up
const and let aren't just new spellings of var; they fix its biggest traps:
- Start with
const, and switch toletonly when you reassign with=. constlocks the variable, not the value: arrays and objects can still change inside.letandconstare block-scoped;varleaks out of blocks into the whole function.- Loops with
letgive each iteration its own variable, fixing the "all my callbacks see 3" bug. - Using
letorconsttoo early throws an error (the temporal dead zone) instead of handing youundefined. - Redeclaring is an error with
letandconst. - Speed is identical: about 1.3 nanoseconds per loop iteration for all three in our test.
New to the language? Read What Is the JavaScript Programming Language?. For the details, see MDN's pages on const, let and var.
Learn more about JavaScript with Eloquent JavaScript.
Read next

C++ std::vector: Size, Capacity, and Safe Growth
Learn how std::vector grows in C++: the difference between size and capacity, when reserve helps, why resize is different, and how growth can invalidate references.

How to Handle Errors in Go: A Hands-On Guide
Learn Go's everyday error pattern with runnable examples: return an error, check it, add context with %w, and recognize a missing file with errors.Is.

What Is the Swift Programming Language? A Simple Guide to the Language Behind iPhone Apps
A friendly, jargon-free guide to Swift, the programming language Apple created for iPhone, iPad and Mac apps. Learn why it replaced Objective-C, how optionals prevent crashes, how an app is made, and how fast Swift really is.
Comments
Be kind and specific. Comments with links from new accounts appear after a moderator reviews them.