What Are Variables?
Variables are containers for data. Think of them as labeled jars — you put a value in, give it a name, and refer to it later. Every time you store a username, a score, or a list of items, you are using a variable.
JavaScript gives you three ways to declare variables: var, let, and const. They all store values, but they behave differently. Understanding those differences will save you from bugs that are hard to track down.
Declaring with let
let is the modern way to declare a variable that will change later. Use it when you know the value will be reassigned.
let score = 0;
score = 10; // reassign — this is fine
let name = "Alice";
name = "Bob"; // also fine
You can also declare let without assigning a value. It will hold undefined until you assign something.
let age;
console.log(age); // undefined
age = 25;
Declaring with const
const works like let, but you cannot reassign it. Once you set a value, that name is locked to that value. Use const by default — it tells anyone reading your code that this value is not supposed to change.
const pi = 3.14159;
// pi = 3; // TypeError: Assignment to constant variable
const username = "Alice";
console.log(username); // Alice
Here is the part that confuses a lot of beginners. const prevents reassignment, but it does not freeze the value. If you store an object or an array in a const, you can still change its contents.
const user = { name: "Alice" };
user.name = "Bob"; // this works — mutating the object
console.log(user.name); // Bob
// user = {}; // TypeError: Assignment to constant variable
const list = [1, 2, 3];
list.push(4); // this works — mutating the array
console.log(list); // [1, 2, 3, 4]
Think of const as locking the jar label, not the contents. You cannot reassign the name, but you can change what is inside.
Declaring with var (The Old Way)
var was the only way to declare variables before 2015. It still works, and you will see it in older code, but it has quirks that cause real bugs. The biggest problem is that var ignores block scope.
var x = 10;
if (true) {
var x = 20; // same variable — overwrites the outer one
console.log(x); // 20
}
console.log(x); // 20 — not 10 like you might expect
In modern code, always use let or const. Reserve var for understanding legacy code.
let, const, and var Compared
| Feature | const | let | var |
|---|---|---|---|
| Scope | Block | Block | Function |
| Reassign? | No | Yes | Yes |
| Redeclare in same scope? | No | No | Yes |
| Hoisting | TDZ (error if accessed before declaration) | TDZ (error if accessed before declaration) | Hoisted, initialized to undefined |
Block Scope vs Function Scope
Scope decides where a variable is visible. let and const are block-scoped — they only exist inside the { } where they are declared. var is function-scoped — it leaks out of blocks.
// let respects block scope
if (true) {
let inside = "only here";
console.log(inside); // only here
}
// console.log(inside); // ReferenceError: inside is not defined
// var ignores block scope
if (true) {
var leaked = "I escape the block";
}
console.log(leaked); // I escape the block
This is why let is the right choice for loop counters. Each iteration gets its own binding.
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// Output: 0, 1, 2
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}
// Output: 3, 3, 3 — var leaked out of the loop
Hoisting and the Temporal Dead Zone
JavaScript processes declarations before running your code. This is called hoisting. But the three keywords handle it differently.
A var declaration is hoisted and initialized to undefined. You can read it before the declaration line — you just get undefined.
console.log(greeting); // undefined — no error
var greeting = "hello";
console.log(greeting); // hello
let and const are also hoisted, but they are not initialized. From the start of the block until the declaration, the variable sits in the Temporal Dead Zone (TDZ). Accessing it throws a ReferenceError.
try {
console.log(name); // ReferenceError (TDZ)
let name = "Alice";
} catch (e) {
console.log(e.name); // ReferenceError
}
The TDZ is a feature. It catches accidental use-before-declaration that var silently hides.
Variable Naming Rules
JavaScript enforces a few hard rules for variable names:
- Names may contain letters, digits, underscores (
_), and dollar signs ($) - Must begin with a letter, underscore, or dollar sign — not a digit
- Case-sensitive:
ageandAgeare different variables - Reserved keywords like
if,for,class,returncannot be used as names
On top of the rules, follow these conventions for readable code:
- Use descriptive names:
userName, notx - Use camelCase for variables and functions:
firstName,itemCount - Use UPPER_SNAKE_CASE for fixed constants:
const MAX_SIZE = 100 - Avoid single letters except for short-lived loop counters (
i,j)
let firstName = "Ada"; // good — descriptive camelCase
const TAX_RATE = 0.2; // good — constant in UPPER_SNAKE_CASE
let $element = "ok"; // legal — $ is allowed
let _private = "ok"; // legal — _ is allowed
// let 1st = "x"; // SyntaxError: cannot start with a digit
JavaScript Is Dynamically Typed
A variable does not have a fixed type. The same variable can hold a string now and a number later.
let thing = "hello";
thing = 42; // perfectly legal
thing = true; // also fine
thing = [1, 2, 3]; // and this
This is different from languages like Java or C++, where you declare the type upfront. JavaScript figures out the type at runtime. It gives you flexibility, but it also means you need to be careful — mixing types accidentally can cause weird bugs.
Common Mistakes
- Using
varin modern code — It causes scope-related bugs. Useletorconstinstead. - Redeclaring with
let—let x = 1; let x = 2;throws a SyntaxError. You cannot redeclare in the same scope. - Assuming
constmakes objects immutable —constprevents reassignment, not mutation. UseObject.freeze()for true immutability. - Using a variable before declaring it — With
letandconst, this throws a ReferenceError due to the TDZ.
Which One Should You Use?
Use const by default. Switch to let only when you know the value needs to be reassigned. Avoid var in new code.
const apiUrl = "https://api.example.com"; // won't change
let counter = 0; // will be incremented
counter += 1;
const user = { name: "Alice" }; // object reference won't change
user.age = 25; // but contents can
This simple rule makes your code predictable and easier to debug.