Variables in JavaScript
Learn the differences between var, let, and const in JavaScript. Understand block scope vs function scope, and why const should be your default choice in modern JavaScript.
Every program stores information somewhere. And in JavaScript, that “somewhere” starts with a variable. You cannot go five lines of real JavaScript code without declaring one, so getting this right early makes everything else easier.
The tricky part? JavaScript gives you three ways to declare a variable: var, let, and const. They look almost identical at first glance but they behave in completely different ways, especially once your code gets longer than a few lines.
In this article I want to walk you through all three, show you the real differences with working examples, explain why var quietly breaks code in ways that are hard to debug, and help you build a clear mental model for when to reach for let versus const. No theory for theory’s sake. Just the stuff that actually matters when you’re writing real code.
If you haven’t read the JavaScript Syntax Fundamentals article from earlier in this series, I’d recommend a quick skim of it first since it covers statements and expressions, which are the foundation for everything here.
01What Is a Variable, Really?
Think of a variable as a labeled box. You give the box a name, and inside you put a value. Later, you can look inside the box, swap the value for something else, or pass the box to another part of your program.
// A box called "score" holding the number 100
let score = 100;
console.log(score); // 100
// Later we can update it
score = 150;
console.log(score); // 150
Simple enough. Now the question is: which keyword do you use to create that box? And what rules come with each one?
02The Three Variable Keywords at a Glance
Before we go deep, here’s a quick visual overview. We’ll break down every row in detail right after.
var vs let vs const: The Differences That Matter
| Keyword | Scope | Hoisting | Re-declare? | Reassign? | Use today? |
|---|---|---|---|---|---|
| var | Function | Yes (as undefined) |
✓ Yes | ✓ Yes | ✗ Avoid |
| let | Block | Yes (TDZ*) | ✗ No | ✓ Yes | ✓ Yes |
| const | Block | Yes (TDZ*) | ✗ No | ✗ No | ✓ Yes |
* TDZ = Temporal Dead Zone. We’ll cover this below.
Right now some of those terms probably look unfamiliar. Scope, hoisting, TDZ. Let’s take them one by one starting from var because understanding what it does wrong is exactly what makes let and const so satisfying.
03Declaring Variables with var: The Original Way
var was the only way to declare a variable in JavaScript before ES6 landed in 2015. If you’ve looked at older tutorials, Stack Overflow answers from 2012, or jQuery-heavy codebases, you’ve seen a lot of it.
var username = "Daniyal";
var score = 42;
var isLoggedIn = true;
console.log(username); // "Daniyal"
console.log(score); // 42
That works fine for simple cases. The problems start appearing when your code has conditions, loops, or multiple functions.
The Hoisting Trap
Hoisting is one of those JavaScript behaviors that looks like a bug the first time you run into it.
When JavaScript sets up your code before running it, it moves all var declarations to the top of their containing function. Not their values, just the declaration itself. So the variable technically “exists” before the line where you wrote it, but its value is undefined.
console.log(city); // undefined (NOT a ReferenceError!)
var city = "Lahore";
console.log(city); // "Lahore"
JavaScript quietly transformed that code into:
var city; // hoisted to top, value is undefined
console.log(city); // undefined
city = "Lahore"; // assignment stays here
console.log(city); // "Lahore"
Here’s the real danger: instead of getting an error that says “you used a variable before declaring it,” you get undefined silently. That silent undefined is what causes weird bugs that take way too long to track down.
See Hoisting in Action
Click each button to see what happens when you access a variable before its declaration line, depending on the keyword used.
Output
This is the thing people mean when they say hoisting makes var unpredictable. With let and const, using a variable too early gives you a clear error. With var, it just quietly hands you undefined and you’re left wondering why your logic is broken.
Function Scope vs Block Scope: The Core Difference
This is probably the most important distinction to understand, and it trips up a lot of beginners.
Scope answers the question: “from where in my code can I access this variable?”
var is function-scoped. That means a var variable only cares about function boundaries, not if blocks or loops. Watch what happens:
function checkAge() {
if (true) {
var message = "You're in the if block";
}
// You'd expect this to fail, but it doesn't
console.log(message); // "You're in the if block"
}
checkAge();
The if block’s curly braces meant nothing to var. The variable leaked out into the whole function. That’s function scope.
Now look at the same example with let:
function checkAge() {
if (true) {
let message = "You're in the if block";
}
// This correctly throws a ReferenceError
console.log(message); // ReferenceError: message is not defined
}
checkAge();
let respects the curly braces. The variable lives inside the block where you declared it, and nowhere else. That’s block scope, and it’s the behavior you almost always want.
The Classic Loop Problem: var vs let
The loop below runs 3 times and logs i after a 100ms delay each time. With var, by the time the log runs, the loop is already done. With let, each iteration gets its own i.
This loop problem is one of the most famous JavaScript interview questions, and it exists entirely because of how var handles scope. With let, the fix is built in.
var Lets You Redeclare the Same Variable
Here’s another one. var lets you declare the same variable name twice in the same scope with zero complaints:
var count = 1;
var count = 99; // No error. count is now 99.
console.log(count); // 99
In a small script that’s harmless. In a file with hundreds of lines, this lets you accidentally overwrite something you declared much earlier and never notice until production breaks.
04Declaring Variables with let: The Modern Fix
let was introduced in ES6 (2015), and if you haven’t read the JavaScript Versions article from earlier in this series, ES6 was basically the release that modernized the whole language. let was one of its best additions.
It fixes all three of var‘s problems:
// 1. Block scoped
{
let insideBlock = "I only exist here";
}
// console.log(insideBlock); // ReferenceError
// 2. No redeclaration
let total = 10;
// let total = 20; // SyntaxError: 'total' has already been declared
// 3. No silent hoisting — TDZ throws a clear error
// console.log(price); // ReferenceError: Cannot access before initialization
let price = 50;
You can still reassign a let variable, which is the whole point:
let userScore = 0;
userScore = 10; // valid
userScore = 25; // valid
userScore += 5; // also valid
console.log(userScore); // 30
Use let for values you expect to change over the lifetime of the program: loop counters, state that updates with user interaction, anything that gets reassigned.
05The Temporal Dead Zone (TDZ): Why let and const Fail Loudly
We saw the TDZ mentioned in the comparison table. Here’s the proper explanation.
Both let and const are hoisted when JavaScript sets up your code, but unlike var, they don’t get initialized to undefined. Instead they sit in a “dead zone” from the top of the block until the line where you actually declare them.
Any access inside that dead zone throws a ReferenceError. And that’s good. That loud error is what tells you exactly where the problem is.
The Temporal Dead Zone for let and const
TDZ begins
here
ReferenceError ✗
let x = 10;Declaration line
here
Works fine ✓
The variable is “known” to JavaScript (it’s been hoisted) but it isn’t accessible until the declaration line is actually reached during execution. This protects you from accidentally relying on undefined values.
06Declaring Variables with const: When the Value Shouldn’t Change
const stands for “constant,” and it works exactly like let with one extra rule: you cannot reassign it after the initial declaration.
const PI = 3.14159;
const APP_NAME = "MyApp";
const MAX_RETRIES = 3;
// Try to reassign:
// PI = 3; // TypeError: Assignment to constant variable
One thing that trips beginners up: const doesn’t freeze the value itself, it freezes the binding. The binding is the connection between the name and what it points to. For primitive values like numbers and strings, this effectively means the value can’t change. But for objects and arrays, you can still modify the contents.
const user = { name: "Daniyal", age: 25 };
// This works — we're changing a property, not reassigning the variable
user.age = 26;
console.log(user.age); // 26
// This does NOT work — we're trying to point user to a new object
// user = { name: "Ali" }; // TypeError: Assignment to constant variable
const scores = [10, 20, 30];
scores.push(40); // Works fine
console.log(scores); // [10, 20, 30, 40]
// scores = []; // TypeError
const: Reassignment vs Mutation
Try clicking each action to see what const allows and what it blocks.
Output
So const doesn’t mean “this value will never change internally,” it means “this name will always point to the same thing.” That’s an important distinction, and I’d encourage you to sit with it for a moment.
You Must Initialize const Immediately
One more rule with const: you have to assign a value right when you declare it. You can’t declare it and fill it in later like you can with let.
// This is fine
const API_URL = "https://api.example.com";
// This throws a SyntaxError
// const DB_PORT; // SyntaxError: Missing initializer in const declaration
// With let, this is allowed
let currentUser; // undefined for now, assigned later
currentUser = "Daniyal";
07When to Use let vs const: A Practical Rule
Here’s the rule I use, and the one most experienced developers follow:
Default to const. Use let only when you know the value will be reassigned.
That’s it. Start with const for everything. If you hit a point where you need to reassign, switch it to let. You’ll find you use const way more than you expect.
// const: values that won't be reassigned
const TAX_RATE = 0.15;
const userProfile = { name: "Daniyal", city: "Lahore" };
const items = ["apple", "banana", "cherry"];
// let: values that will be reassigned
let currentPage = 1;
let isLoading = false;
function goToNextPage() {
currentPage += 1; // reassignment needed → let is correct
}
function fetchData() {
isLoading = true; // reassignment needed → let is correct
// ... fetch logic ...
isLoading = false;
}
When someone reading your code sees const, they immediately know: this value won’t be reassigned later. That’s useful information. It narrows down what you need to think about. let signals: watch this, it might change. Code that communicates intent clearly is easier to maintain, debug, and collaborate on.
08Practical Examples: Real Code Patterns
Let’s look at some real patterns you’ll write constantly, and see which keyword fits naturally.
Loop Counters
// let because i gets reassigned each iteration
for (let i = 0; i < 5; i++) {
console.log(i);
}
// forEach callbacks often use const for the item itself
const fruits = ["apple", "mango", "banana"];
fruits.forEach(function(fruit) {
console.log(fruit); // fruit is reassigned per callback call, but declared fresh each time
});
Configuration and Constants
const CONFIG = {
baseURL: "https://api.example.com",
timeout: 5000,
retries: 3
};
const COLORS = {
primary: "#6366f1",
danger: "#ff5c7a",
success: "#2ecc71"
};
// You'll use these all over your code, and they never get replaced
console.log(CONFIG.baseURL);
console.log(COLORS.primary);
State That Changes
let totalPrice = 0;
const cartItems = ["keyboard", "mouse", "monitor"];
const prices = [75, 30, 250];
for (let i = 0; i < cartItems.length; i++) {
totalPrice += prices[i]; // totalPrice changes each loop → let
}
console.log("Total:", totalPrice); // Total: 355
Functions and Callbacks
// Functions declared with const are a common modern pattern
const greet = function(name) {
return "Hello, " + name + "!";
};
const double = (n) => n * 2;
console.log(greet("Daniyal")); // Hello, Daniyal!
console.log(double(7)); // 14
You'll still see var in older codebases, tutorials from before 2015, and plenty of Stack Overflow answers. It still works and won't break anything by itself. But in any code you write today, there's simply no reason to reach for it. let and const give you everything var does, without the footguns.
09Naming Variables: Conventions Worth Knowing
Before wrapping up, a quick note on naming. JavaScript variable names are case-sensitive, must start with a letter, underscore, or dollar sign, and cannot be reserved keywords like let, for, or return.
// camelCase: standard for most variables and functions
let firstName = "Daniyal";
let totalItemCount = 42;
// UPPER_SNAKE_CASE: convention for true constants that never change
const MAX_FILE_SIZE = 5242880;
const API_KEY = "abc123";
// PascalCase: classes and constructors (you'll learn this later in the series)
// class UserProfile { ... }
// Avoid single letters except in short loops
for (let i = 0; i < 10; i++) { /* fine here */ }
// Descriptive names always win
let x = 100; // ❌ what is x?
let userAgeInYears = 100; // ✓ immediately clear
10The Mental Model: Three Boxes, Three Rules
Here's the mental model I like. Think of each keyword as a different kind of box with its own rules about where you can put it and what you can do with what's inside.
Three Keywords, Three Boxes
var
A leaky box. Ignores block walls, gets hoisted with an empty value. Useful to know about, not to use.
let
A normal box that respects its room (block). You can swap what's inside whenever you need to.
const
A locked label. The label always points to the same thing. The thing itself can still be changed internally if it's an object.
11Common Mistakes to Avoid
A few patterns that confuse people early on:
// Mistake 1: Thinking const means deeply immutable
const settings = { theme: "dark" };
settings.theme = "light"; // This works — the object's content changed
// If you want truly frozen objects, look into Object.freeze() later in the series
// Mistake 2: Declaring with var inside if/for blocks expecting block scope
for (var i = 0; i < 3; i++) { /* ... */ }
console.log(i); // 3 — leaked out! Use let instead.
// Mistake 3: Forgetting to initialize const
// const LIMIT; // SyntaxError — must assign immediately
// Mistake 4: Using the same name twice with let
let age = 25;
// let age = 30; // SyntaxError in same scope — use reassignment: age = 30;
age = 30; // This is the correct way
Reach for const first. Switch to let only when you need to reassign. Forget about var for new code. That single habit will save you from the majority of variable-related bugs in JavaScript.
12What's Coming Next
Now that you know how to declare and work with variables, the next logical step is understanding what you can actually put inside them. JavaScript has 8 distinct data types, and they behave in some genuinely interesting ways.
In the next article, we'll cover all of them: strings, numbers, booleans, null, undefined, Symbol, BigInt, and objects. We'll also look at the typeof operator and why knowing your types prevents entire categories of bugs before they happen.
Stick with it. Variables are the foundation, and you've just understood them properly. That matters more than you think.
Variables in JavaScript
Loading challenge…
Ready to Practice?
Select a difficulty level above to load your challenge.