JavaScript • Syntax and Core Types

let & const — Declare variables using let and const appropriately

Master the modern way to declare variables in JavaScript with block scoping and immutability.

JavaScript emblem with code brackets, braces, and a code file.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

Historical Context & Motivation

When JavaScript was first created in 1995, the only way to declare a variable was with the var keyword. For years, var was the sole option, but it came with quirks that frequently tripped up developers. Variables declared with var are scoped to the entire function rather than to the block of code where they appear, which means they can leak out of if statements and for loops in unexpected ways. They can also be re-declared without warning, leading to silent bugs that are hard to track down.

As JavaScript grew from a simple scripting language into the backbone of modern web applications, the community demanded better tools for managing data. The ECMAScript 2015 standard (often called ES6) introduced two new keywords — let and const — that solved these long-standing problems.

1995 • JavaScript is Born

Brendan Eich creates JavaScript in just 10 days at Netscape. The var keyword is the only way to declare variables, using function-level scoping.

1999 • ECMAScript 3 Published

The third edition of the ECMAScript standard is widely adopted. Developers begin building larger applications and encounter var's scoping bugs more frequently.

2009 • Node.js Arrives

JavaScript moves to the server side. Larger codebases magnify the problems of function scoping and accidental variable re-declaration.

2015 • ES6 Introduces let & const

ECMAScript 2015 adds let and const with block scoping and immutable bindings, revolutionizing how developers manage variables.

2020+ • Modern Best Practice

Today, professional JavaScript developers use const by default and let only when reassignment is necessary. The use of var is strongly discouraged.

The central question this lesson addresses is: how do let and const fix the problems of var, and when should you choose one over the other? Understanding this distinction is foundational to writing clean, bug-free JavaScript.

SECTION 2

Core Principles & Definitions

Before diving into code, you need to understand three key ideas that differentiate let and const from the older var. These principles — block scoping, no hoisting to usable state, and immutable bindings — form the foundation for writing predictable, modern JavaScript.

1

Block Scoping

A block is any section of code wrapped in curly braces { }. Variables declared with let or const exist only inside the block where they are declared. Once execution leaves that block, the variable is gone.
2

Temporal Dead Zone (TDZ)

Unlike var, which is silently initialized to undefined before your code runs, let and const cannot be accessed before their declaration line. This zone is called the Temporal Dead Zone.
3

Immutable Binding with const

A variable declared with const cannot be reassigned to a new value after initialization. However, if the value is an object or array, the contents inside can still be changed — only the binding (the link between the name and the value) is locked.
4

No Re-declaration

With var, you can accidentally declare the same variable name twice in the same scope without any error. Both let and const prevent this by throwing a SyntaxError if you try to re-declare a variable in the same scope.
✦

KEY TAKEAWAY

Think of const like writing someone's name on a locker with permanent marker — the name tag is fixed, but you can rearrange whatever is inside the locker. let is like a dry-erase marker — you can wipe the name off and write a new one. And var is like yelling a name across the entire gym — everyone hears it, whether you wanted them to or not.

SECTION 3

Visual Explanation — Scope Comparison

The following diagram compares how var, let, and const behave when declared inside a block such as an if statement. Notice how var escapes the block and is accessible in the surrounding function, while let and const stay safely contained.

Variable Scope: var vs let vs constfunction example() {if (true) {var a = 1;← function-scoped (leaks out!)let b = 2;← block-scoped (stays here ✓)const c = 3;← block-scoped + no reassignment ✓All three accessible here: a ✓ b ✓ c ✓}Outside the if-block:console.log(a); // 1Works — var leaked!console.log(b); // ReferenceErrorError ✗console.log(c); // ReferenceErrorError ✗}
The dashed cyan rectangle represents the block scope (the if statement). Notice that var a is accessible outside the block — it leaks into the surrounding function scope. Both let b and const c throw a ReferenceError when accessed outside the block.

This visual makes clear why block scoping matters. When you use let or const, you know exactly where a variable exists. There are no surprises from variables "leaking" into places where they shouldn't be. This predictability makes your code easier to read, debug, and maintain — especially as your programs grow larger.

SECTION 4

How It Works — Declaration, Initialization & Assignment

Every variable in JavaScript goes through up to three phases: declaration (the engine registers the name), initialization (the name is bound to a memory location and given a starting value), and assignment (the value can be updated later). The key difference between var, let, and const lies in when and how these phases happen.

var: Hoisted and Initialized

With var, the JavaScript engine moves the declaration to the top of the function (this is called hoisting) and immediately initializes it to undefined. This means you can reference the variable before the line where you wrote var x = 5; — you won't get an error, just the confusing value undefined.

let: Hoisted but Not Initialized

With let, the engine still knows the variable exists at the top of the block, but it does not initialize it until it reaches the actual declaration line. Any attempt to read the variable before that line causes a ReferenceError. The space between the top of the block and the declaration line is the Temporal Dead Zone (TDZ).

const: Same as let, Plus No Reassignment

const follows the same TDZ rules as let, with one additional constraint: you must assign a value at the time of declaration, and that binding can never be changed. Writing const x; x = 5; is a SyntaxError. And writing const x = 5; x = 10; is a TypeError. The variable name is permanently attached to whatever value you provide at creation.

⚠

const Does Not Mean "Constant Value"

A common misconception is that const makes the value immutable. It does not. If you write const arr = [1, 2, 3];, you can still call arr.push(4) because you're modifying the array's contents, not reassigning arr to a different array. What const prevents is arr = [5, 6, 7]; — pointing the name at an entirely new value.

SECTION 5

Temporal Dead Zone — Visualized

The Temporal Dead Zone is one of the trickiest concepts for new JavaScript developers. The diagram below walks through the lifecycle of a var variable versus a let variable, showing exactly when each becomes usable as code execution flows from top to bottom.

Temporal Dead Zone: var vs letvar xlet yEngine hoists declarationx = undefined ✓ accessibleconsole.log(x); // undefinedNo error — just undefinedvar x = 10;x is now 10console.log(x); // 10Works as expected ✓TEMPORAL DEAD ZONEEngine knows y exists butwill NOT let you touch itconsole.log(y);❌ ReferenceError!let y = 20;y is now initialized and usableconsole.log(y); // 20Works perfectly ✓var: silently gives undefined(hides bugs)let: throws an error early(catches bugs!)
Left: var x is hoisted and initialized to undefined, so accessing it early silently returns undefined. Right: let y enters the Temporal Dead Zone (red dashed region) and throws a ReferenceError if accessed before its declaration.

The TDZ is actually a feature, not a bug. By throwing an error immediately when you try to use a variable before declaring it, JavaScript helps you catch mistakes early in development instead of letting them slip through as mysterious undefined values at runtime. This is one of the strongest reasons to prefer let and const over var.

Featurevarletconst
ScopeFunctionBlockBlock
Hoisted?Yes (initialized to undefined)Yes (but in TDZ)Yes (but in TDZ)
Re-declaration in same scope?✓ Allowed✗ SyntaxError✗ SyntaxError
Reassignment?✓ Allowed✓ Allowed✗ TypeError
Must initialize at declaration?NoNoYes

Quick reference comparison of var, let, and const

SECTION 6

Worked Example — Choosing the Right Keyword

Let's walk through a realistic scenario: building a simple function that processes a list of student scores and returns some statistics. At each step, we'll decide whether to use let or const.

Score Statistics Function

  1. 1

    Step 1 — Identify Values That Will Not Change

    The input array scores is passed into our function, and we won't reassign it. The variable name should always point to the same array, so we use const.
    const scores = [88, 72, 95, 63, 91];
  2. 2

    Step 2 — Identify Values That Will Be Reassigned

    We need a running total that we'll add each score to inside a loop. Since this value changes on every iteration, we use let. We also need a variable to track the highest score, which will be updated whenever we find a larger one.
    let total = 0; and let highest = 0;
  3. 3

    Step 3 — Loop With a Block-Scoped Counter

    In a for loop, the counter variable i is reassigned on every iteration (i++ means i = i + 1). Therefore, let is the correct choice. If we used var, i would leak outside the loop.
    for (let i = 0; i < scores.length; i++) { ... }
  4. 4

    Step 4 — Compute the Average (a Derived Constant)

    After the loop finishes, we calculate the average. This value is computed once and never changes, making const the right choice.
    const average = total / scores.length;
  5. 5

    Step 5 — Full Code

    Putting it all together, the final function uses const for values that don't change and let for values that must be reassigned. Notice there is no var anywhere in the code.
    function getStats(scores) { let total = 0; let highest = 0; for (let i = 0; i < scores.length; i++) { total += scores[i]; if (scores[i] > highest) { highest = scores[i]; } } const average = total / scores.length; return { average, highest }; }
📝

DECISION RULE

Start by declaring every variable with const. If you later find that the value needs to change, switch it to let. Never use var in modern JavaScript. This simple habit — const by default, let when needed — is the standard practice in professional development.

SECTION 7

Strengths, Limitations & Common Pitfalls

Now that you know how let and const work, let's compare their strengths and limitations side by side. Understanding these tradeoffs will help you make confident choices in your own code.

AspectStrengthLimitation / Gotcha
constSignals intent clearly — readers know this value won't change. Prevents accidental reassignment bugs.Does NOT freeze objects or arrays. You can still push, pop, or modify properties on a const object.
letBlock-scoped and allows reassignment — perfect for counters, accumulators, and toggles.Can lead to less readable code if overused. If a value can be const, making it let hides your intent.
Block scopingVariables are confined to the block where they're declared, preventing unexpected side effects.If you accidentally declare a variable inside a block but need it outside, you'll get a ReferenceError. You must declare it in the correct scope.
TDZCatches bugs early by throwing errors when you access variables before declaration.Can be surprising to beginners who are used to var's forgiving hoisting behavior.

Strengths and limitations of modern variable declaration

✦

KEY TAKEAWAY

Think of choosing const vs let like choosing between a pen and a pencil on a test. Use the pen (const) when you're certain of your answer, because it signals confidence and permanence. Use the pencil (let) when you know you'll need to erase and revise. The goal is to use the pen as much as possible.

SECTION 8

Connection to Advanced Concepts

Understanding let and const is the first step toward mastering several advanced JavaScript topics. The concepts of scope, binding, and mutability that you've learned here reappear in more complex forms as you progress.

This Lesson's ConceptAdvanced Extension
Block scoping with let/constClosures — functions that "remember" variables from their enclosing scope. Block scoping makes closures in loops far more predictable with let than with var.
const prevents reassignmentObject.freeze() — a built-in method that makes an object's properties truly immutable, going a step beyond what const alone provides.
Temporal Dead ZoneModule scope — ES modules have their own scope rules that build on block scoping. Understanding TDZ prepares you for import/export patterns.
Choosing const vs letFunctional programming — a programming style that favors immutable data. Using const by default trains you to think in terms of data that doesn't change, a key functional concept.

How let & const connect to advanced JavaScript topics

One of the most important advanced topics is closures in loops. With var, a common bug occurs when using setTimeout inside a for loop: all callbacks share the same i variable and print the final value. With let, each iteration of the loop gets its own copy of i, automatically solving this classic problem. You'll encounter this topic in more detail when you study closures and asynchronous JavaScript.

SECTION 9

Practice Problems

Test your understanding of let and const with these five problems. They progress from conceptual understanding to critical thinking about real-world code.

Question 1: CONCEPTUAL

Explain in your own words why const does not make an array truly "constant." If you write const colors = ["red", "blue"];, can you add "green" to the array? Can you reassign colors to a completely different array?
Show answer
Yes, you can add "green" by calling colors.push("green") because const only locks the binding — the link between the name colors and the array in memory. Modifying the array's contents (push, pop, changing an element) is allowed because you're not changing which array the variable points to. However, writing colors = ["yellow"]; would throw a TypeError because that would reassign the variable to an entirely different array, which const forbids.

Question 2: BASIC

What will the following code print, and why? { let x = 10; x = 20; console.log(x); } console.log(x);
Show answer
The first console.log(x) inside the block prints 20 because let allows reassignment, so x is updated from 10 to 20. The second console.log(x) outside the block throws a ReferenceError because let is block-scoped, meaning x ceases to exist once the closing } is reached.

Question 3: INTERMEDIATE

Rewrite the following code using let and const (no var). Explain your choice for each variable. var taxRate = 0.08; var price = 49.99; var total = price + (price * taxRate); var message = "Your total is $" + total; console.log(message);
Show answer
All four variables should be declared with const because none of them are ever reassigned after their initial value is set. The corrected code is: const taxRate = 0.08; const price = 49.99; const total = price + (price * taxRate); const message = "Your total is $" + total; console.log(message); Using const for all of these makes the code's intent crystal clear: these values are computed once and never modified. If you later tried to write price = 59.99;, JavaScript would immediately throw a TypeError, alerting you to the mistake.

Question 4: APPLIED

A student writes a function to count how many even numbers are in an array. Their code uses only var. Identify the potential scoping bug and rewrite the function using let and const. function countEvens(nums) { var count = 0; for (var i = 0; i < nums.length; i++) { if (nums[i] % 2 === 0) { var count = count + 1; } } return count; }
Show answer
The bug is the re-declaration of var count inside the if block. With var, this doesn't create a new variable — it silently shadows the existing one without error. While this particular example may still produce the right output due to var's function scoping, it represents sloppy code that could cause confusion in larger programs. The rewritten version: function countEvens(nums) { let count = 0; for (let i = 0; i < nums.length; i++) { if (nums[i] % 2 === 0) { count = count + 1; } } return count; } Here, count uses let because it's reassigned. i uses let because the loop counter changes. nums (the parameter) is already block-scoped to the function. If you tried to re-declare let count inside the if-block, JavaScript would throw a SyntaxError, immediately catching the bug.

Question 5: CRITICAL THINKING

Consider this code that uses var in a loop with setTimeout: for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 1000); } What does this print after 1 second? What would it print if you replaced var with let? Explain why the outputs differ.
Show answer
With var, the output is 3, 3, 3. Because var is function-scoped, there is only ONE i variable shared across all three iterations. By the time the setTimeout callbacks run (after 1 second), the loop has already finished and i has reached 3. All three callbacks reference the same i, so they all print 3. With let, the output is 0, 1, 2. Because let is block-scoped, each iteration of the loop creates a fresh copy of i with its own value. Each setTimeout callback captures its own unique i, so they print 0, 1, and 2 respectively. This is a classic example of how block scoping with let solves a real-world closure bug that has tripped up JavaScript developers for over a decade.

SUMMARY

Summary — let & const

Summary

Modern JavaScript provides two variable declaration keywords: const for values whose binding should never change, and let for values that need to be reassigned. Both are block-scoped, meaning they exist only within the curly braces where they are declared. They also enforce the Temporal Dead Zone, which throws a ReferenceError if you try to access them before their declaration line — catching bugs early rather than hiding them with undefined.

The best practice is to use const by default and switch to let only when reassignment is truly necessary. Remember that const protects the binding, not the contents — you can still modify objects and arrays declared with const. The older var keyword, with its function-level scoping and silent hoisting, should be avoided in all new code. By following the const-first approach, you write cleaner, more predictable, and more maintainable JavaScript.

Varsity Tutors • JavaScript • let & const — Declare variables using let and const appropriately