JavaScript • Syntax and Core Types

var vs. let/const — Explain var vs let/const at a high level (scope/hoisting) (conceptual)

Understanding how JavaScript's three variable keywords differ in scope, hoisting, and safety.

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 language had only one way to declare a variable: the var keyword. For nearly two decades, every JavaScript developer used var for everything — numbers, strings, objects, and more. While this simplicity seemed convenient, it actually introduced subtle bugs that frustrated programmers on a daily basis.

The core issue was that var behaved in ways that were surprising even to experienced developers. Variables declared with var could be used before their declaration line, they ignored block boundaries like if and for blocks, and they could be accidentally redeclared without any error. As web applications grew larger and more complex, these quirks became serious sources of bugs.

1995 • JavaScript Is Born

Brendan Eich creates JavaScript in just 10 days at Netscape. The var keyword is the only way to declare variables, borrowing loose scoping rules for quick prototyping.

2009 • ES5 Standardized

ECMAScript 5 introduces strict mode to catch common mistakes, but var scoping remains unchanged. Developers increasingly voice frustration about accidental variable leaks.

2015 • ES6 Introduces let and const

ECMAScript 2015 (ES6) adds let and const — two new keywords with block scoping and stricter rules, solving most of var's design flaws.

2020+ • Modern Best Practice

Today, industry style guides from Google, Airbnb, and Mozilla recommend using const by default and let when reassignment is needed. The var keyword is essentially considered legacy.

So what exactly makes let and const better? The answer centers on two key concepts: scope (where a variable is visible) and hoisting (how JavaScript processes variable declarations before running your code). Let's explore both.

SECTION 2

Core Principles & Definitions

Before comparing the three keywords, you need to understand three foundational ideas that govern how variables behave in JavaScript. These concepts — scope, hoisting, and reassignment — determine where you can use a variable, when it becomes available, and whether you can change its value after creation.

1

Scope

Scope is the region of your code where a variable is accessible. Think of it like a room in a building — a variable created inside a room can only be used inside that room. JavaScript has two main scope types: function scope (the entire function) and block scope (just the nearest curly braces).
2

Hoisting

Hoisting is JavaScript's behavior of moving declarations to the top of their scope before executing code. With var, the variable is initialized to undefined during hoisting. With let and const, the variable exists but cannot be accessed until its declaration line — a zone called the Temporal Dead Zone (TDZ).
3

Reassignment vs. Redeclaration

Reassignment means changing a variable's value after it's been created. Redeclaration means declaring the same variable name again in the same scope. var allows both freely. let allows reassignment but blocks redeclaration. const blocks both.
4

Block vs. Function Scope

A block is any code wrapped in curly braces { } — this includes if statements, for loops, and while loops. Variables declared with var ignore block boundaries and belong to the entire enclosing function, while let and const respect them.
✦

KEY TAKEAWAY

Think of var like writing your name on a whiteboard that the whole classroom can see — anyone in the function can access it. let and const are like writing your name on a sticky note that stays on one desk — only the code inside that specific block can read it. This containment prevents your variable from accidentally interfering with code outside its intended area.

SECTION 3

Visualizing Scope

The diagram below illustrates the fundamental difference between function scope and block scope. Notice how a var variable declared inside an if block leaks out into the entire function, while let stays contained within the block where it was declared.

SCOPE COMPARISON: var vs. letfunction greet( ) — using varfunction greet() {var name = "Alice";if blockif (true) {var age = 16;}console.log(age);→ 16 ✓age leaks outside the if block!}var → scoped to entire functionfunction greet( ) — using letfunction greet() {let name = "Alice";if blockif (true) {let age = 16;}console.log(age);→ Error ✗age is not accessible here!}let → scoped to the block only
Left: var age (yellow) is accessible outside the if block because var is function-scoped. Right: let age (cyan) stays inside the if block, causing a ReferenceError when accessed outside.

The left panel shows the dangerous behavior of var: even though age was declared inside the if block, it "leaks" into the surrounding function and logs 16 successfully. The right panel shows the safer behavior of let: attempting to access age outside the block throws an error, alerting you to the mistake immediately rather than letting a hidden bug slip through.

SECTION 4

How Hoisting Works Under the Hood

When JavaScript runs your code, it doesn't simply read from top to bottom. Instead, the engine performs a two-phase process. In the first phase — the creation phase — the engine scans all declarations and sets up memory space for them. In the second phase — the execution phase — it runs the code line by line. This two-step process is what creates the behavior we call hoisting.

var Hoisting: Initialized to undefined

During the creation phase, a var declaration is hoisted to the top of its function and initialized to undefined. This means you can reference the variable before its declaration line without getting an error — you just get undefined instead of the value you expected. This silent failure is one of the most common sources of bugs in JavaScript.

let/const Hoisting: The Temporal Dead Zone

Technically, let and const are also hoisted — the engine knows they exist during the creation phase. However, they are not initialized. The region between the top of the block and the actual declaration line is called the Temporal Dead Zone (TDZ). Any attempt to access the variable in this zone throws a ReferenceError, which immediately tells you something is wrong.

HOISTING: var vs. let/constvar x = 10;HOISTED — x = undefinedconsole.log(x); // undefinedNo error! Silent bug potentialDECLARATION LINEvar x = 10;console.log(x); // 10✓ Works as expected← x exists as undefinedlet y = 10;TEMPORAL DEAD ZONEconsole.log(y); // Error!ReferenceError thrown — bug caught!DECLARATION LINElet y = 10;console.log(y); // 10✓ Works as expected← y exists but can't be accessedvar silently gives undefined • let/const throw an error → bugs are caught immediately
Left: var is hoisted and initialized to undefined, silently hiding the bug. Right: let enters the Temporal Dead Zone and throws an error, catching the mistake immediately.
💡

Why Does This Matter?

Imagine you accidentally use a variable 50 lines before you declare it. With var, your program runs without complaint but uses undefined, potentially causing weird behavior that's extremely hard to track down. With let or const, the program crashes immediately with a clear error message pointing you to the exact line — saving you hours of debugging.

SECTION 5

Detailed Comparison: var, let, and const

Now that you understand scope and hoisting conceptually, let's put all three keywords side by side and compare their behavior across every important dimension. This table is your quick-reference guide whenever you're unsure which keyword to use.

Featurevarletconst
ScopeFunction-scopedBlock-scopedBlock-scoped
HoistingHoisted, initialized to undefinedHoisted, but in TDZ (not initialized)Hoisted, but in TDZ (not initialized)
RedeclarationAllowed (no error)Not allowed (SyntaxError)Not allowed (SyntaxError)
ReassignmentAllowedAllowedNot allowed (TypeError)
Must initialize?NoNoYes — must assign a value
Global object property?Yes (creates window.x)NoNo

Complete feature comparison of JavaScript variable declaration keywords

Understanding const with Objects

One common misconception is that const makes everything immutable (unchangeable). In reality, const only prevents you from reassigning the variable itself. If the value is an object or array, you can still modify its contents. For example, const arr = [1, 2]; means you cannot do arr = [3, 4]; (reassignment), but you can do arr.push(3); (mutation) because the variable still points to the same array in memory.

Restrictiveness Spectrum

Least restrictive Most restrictive
Function scope, hoists to undefined, allows redeclaration (16%)Block scope, TDZ, allows reassignment (50%)Block scope, TDZ, no reassignment (83%)

SECTION 6

Worked Example: Debugging a Loop

One of the most famous bugs caused by var happens inside loops. Let's walk through a scenario where using var causes unexpected behavior, and then fix it using let.

The Classic Loop Bug

  1. 1

    Step 1 — The Buggy Code

    Consider this code that tries to create three functions, each printing a different number: for (var i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 100); }. You might expect this to print 0, 1, 2 — but it actually prints 3, 3, 3. Let's understand why.
  2. 2

    Step 2 — Identify the Scope Issue

    Because var i is function-scoped, there is only one single variable i shared by all three iterations of the loop. By the time the setTimeout callbacks run (100ms later), the loop has already finished and i has been incremented to 3.
    All three callbacks read the same i, which is now 3
  3. 3

    Step 3 — Fix with let

    Simply change var to let: for (let i = 0; i < 3; i++) { setTimeout(function() { console.log(i); }, 100); }. Because let is block-scoped, each iteration of the loop creates a fresh copy of i scoped to that specific iteration.
    Output: 0, 1, 2 — each callback has its own i
  4. 4

    Step 4 — Verify the Concept

    With let, you can imagine the loop unrolling into three separate blocks, each with its own variable: block 1 has i = 0, block 2 has i = 1, block 3 has i = 2. The callbacks capture their own block's i, preserving the correct value.
    Rule: Always use let (or const) in loops to avoid shared-variable bugs.

SECTION 7

Strengths, Limitations & When to Use Each

While modern best practice heavily favors const and let, it's important to understand the trade-offs and know when each keyword is appropriate. Understanding var is still necessary because you'll encounter it in older codebases, tutorials, and Stack Overflow answers.

KeywordStrengthsLimitationsWhen to Use
varWorks in all JavaScript versions; useful in rare advanced closuresFunction-scoped, hoists to undefined, allows redeclaration — all lead to hidden bugsLegacy code maintenance only; avoid in new code
letBlock-scoped, TDZ catches errors early, prevents accidental redeclarationRequires ES6+ environments; some developers overuse it when const would be betterWhen you know the variable will be reassigned (counters, accumulators, flags)
constAll benefits of let plus immutable binding; signals intent clearly to other developersMust be initialized at declaration; doesn't prevent mutation of objects/arraysDefault choice for all variables; use unless reassignment is needed

Practical trade-offs for each variable declaration keyword

🎯

THE MODERN RULE OF THUMB

Think of choosing a variable keyword like choosing a lock for a door. const is a deadbolt — secure by default, and the right choice for most doors. let is a regular lock — still safe, but you use it when you need to open the door sometimes (reassign the value). var is leaving the door unlocked — it might work, but you're asking for trouble. Start with const, downgrade to let only when you must, and avoid var entirely in new code.

SECTION 8

Connection to Advanced Topics

Understanding scope and hoisting is foundational, but these concepts connect directly to more advanced JavaScript topics you'll encounter as you continue learning. The way variables interact with scope is at the heart of some of JavaScript's most powerful — and sometimes confusing — features.

Concept You LearnedAdvanced Topic It Leads ToHow They Connect
Block scope (let/const)ClosuresClosures capture variables from their enclosing scope; block scope ensures each closure captures the correct value.
Temporal Dead ZoneExecution ContextThe TDZ is part of how JavaScript's execution context and lexical environment work under the hood.
const immutabilityImmutable Data & Object.freeze()const prevents rebinding; Object.freeze() prevents mutation. Together they create truly immutable data.
Function scope (var)IIFE PatternBefore let/const, developers used Immediately Invoked Function Expressions to create private scope — a workaround for var's limitations.

How scope and hoisting connect to advanced JavaScript concepts

As you progress, you'll see that mastering closures — functions that "remember" the variables from the scope where they were created — becomes much easier once you fully understand block scoping. The loop example from Section 6 was actually a closure problem at its core. The IIFE (Immediately Invoked Function Expression) pattern, which you may see in older code, was essentially a manual way to create block-like scoping before let existed. Understanding why these patterns exist gives you deeper insight into JavaScript's evolution.

SECTION 9

Practice Problems

Test your understanding of scope, hoisting, and the differences between var, let, and const with these five problems. Try to answer each one before reading the solution.

Question 1: CONCEPTUAL

Explain in your own words why let is said to be "block-scoped" while var is "function-scoped." Give a one-sentence example scenario where this difference matters.
Show answer
Block-scoped means a variable declared with let only exists within the nearest pair of curly braces (a block), such as an if statement or a for loop. Function-scoped means a var variable ignores block boundaries and is accessible anywhere within the entire enclosing function. This matters when you declare a temporary variable inside a loop — with var, that variable is still accessible after the loop ends, which can lead to accidental misuse of stale data.

Question 2: BASIC CALCULATION

What does the following code output and why? console.log(x); var x = 5; console.log(x);
Show answer
The first console.log(x) outputs undefined, and the second outputs 5. This happens because of hoisting: JavaScript moves the var x declaration to the top and initializes it to undefined. The assignment x = 5 stays in place and only executes when that line is reached. So when the first log runs, x exists but hasn't been assigned yet.

Question 3: INTERMEDIATE

Consider: let score = 100; if (true) { let score = 50; console.log(score); } console.log(score); What are the two outputs? Now replace both let keywords with var. What changes and why?
Show answer
With let, the outputs are 50 then 100. The let score = 50 inside the if block creates a separate variable scoped to that block, so it doesn't affect the outer score. With var, both outputs are 50. Since var is function-scoped and allows redeclaration, the second var score = 50 overwrites the original variable rather than creating a new one — a common source of accidental data loss.

Question 4: APPLIED

You're building a student grade tracker. You need to store a student's name (which won't change), a running total of their scores (which will change as grades are entered), and a temporary variable inside a loop that processes each grade. Which keyword (var, let, or const) would you use for each, and why?
Show answer
Use const for the student's name because it shouldn't change after being set — const name = "Alice". Use let for the running total because it needs to be updated each time a grade is added — let total = 0. Use const for the loop variable if you're using for...of (since each iteration creates a fresh binding), or let for a traditional for loop counter. Never use var — it would let the loop variable leak into the surrounding scope.

Question 5: CRITICAL THINKING

A classmate argues: "Since const prevents changes, I should use it for an array of student names so nobody can modify the list." They write const students = ["Ana", "Ben"]; students.push("Cal"); and are surprised it works. Explain why const didn't prevent the push(), and describe what const actually prevents.
Show answer
const prevents reassignment of the variable binding, not mutation of the value it holds. The variable students points to an array in memory, and const ensures that students always points to that same array. However, the array itself can be changed — items can be added, removed, or modified. Writing students = ["new array"] would throw a TypeError because that's reassignment. To truly prevent modifications to the array's contents, you would need Object.freeze(students), which is a separate concept from const.

SUMMARY

Lesson Summary

Summary

JavaScript provides three keywords for declaring variables: var, let, and const. The original var keyword is function-scoped — it ignores block boundaries and is hoisted with an initial value of undefined, which can silently introduce bugs. The ES6 keywords let and const are block-scoped and enter a Temporal Dead Zone (TDZ) where accessing them before their declaration throws an error, making bugs easier to catch.

Modern best practice follows a simple hierarchy: use const by default for any variable that won't be reassigned, switch to let when reassignment is necessary (like loop counters or accumulators), and avoid var in new code. Remember that const prevents reassignment of the variable binding, not mutation of objects or arrays. These scoping and hoisting concepts are foundational to understanding more advanced topics like closures and execution contexts.

Varsity Tutors • JavaScript • var vs. let/const — Scope and Hoisting