JavaScript var, let and const Explained — Scope, Hoisting and When to Use Each
Every program you've ever used — a weather app, a game, a shopping cart — needs to remember things while it's running. It needs to know the current temperature, your score, how many items are in your basket. Variables are how programs remember things. Without them, your code would be a one-shot firework: bright for a second, then gone, with nothing to show for it. In JavaScript specifically, variables are the very first building block you'll write in almost every line of real code.
What a Variable Actually Is — And How to Create One
A variable is a named slot in your computer's memory where you can store a value and retrieve it later by name. You create one using a keyword (var, let, or const), then a name you choose, then optionally an equals sign and an initial value.
The name you choose is completely up to you — but good names describe what the value represents. 'playerScore' is miles better than 'ps', because when you read it three weeks later you'll still know what it means.
JavaScript is case-sensitive, so 'playerScore' and 'PlayerScore' are two completely different variables. The convention in JavaScript is camelCase — first word lowercase, every word after that starts with a capital letter. 'userName', 'totalPrice', 'isLoggedIn' — that's the style professional developers use and that you should adopt from day one.
Once a variable holds a value, you can read that value, change it, pass it to a function, or do maths with it. It's just a labelled box — incredibly simple, but the foundation of everything.
// --- Creating variables with let (the modern default choice) --- // Declare a variable and assign a value to it in one line let playerName = "Alex"; // Declare a variable now, assign a value later let playerScore; playerScore = 0; // we set it here when we're ready // Read the value by using the variable name console.log(playerName); // prints: Alex console.log(playerScore); // prints: 0 // --- Update the value stored inside the variable --- playerScore = 150; // Alex just scored 150 points console.log(playerScore); // prints: 150 // --- JavaScript can store many types of value --- let gameTitle = "Space Quest"; // text (called a 'string') let numberOfLives = 3; // a number let isGameOver = false; // true or false (called a 'boolean') console.log(gameTitle, numberOfLives, isGameOver); // prints: Space Quest 3 false
0
150
Space Quest 3 false
var, let, and const — The Differences That Actually Matter
JavaScript has three keywords for declaring variables, and they behave differently in two important ways: scope (where the variable can be seen and used) and mutability (whether the value can be changed after it's set).
'var' is the original keyword from 1995. It's function-scoped — meaning it's visible anywhere inside the function it was created in. It also gets 'hoisted', which causes some truly weird bugs we'll get to shortly. Avoid var in new code.
'let' was introduced in 2015 and is block-scoped — it only exists inside the curly braces {} where it was declared. This is far more predictable and is your go-to for any value that will change over time.
'const' is also block-scoped, but with one extra rule: once you assign a value to a const variable, you can't reassign it. Think of const as a box that's been glued shut — you can look inside, but you can't put something different in. Use const as your default choice. Only switch to let when you know the value needs to change.
// ============================================================ // PART 1 — const: for values that should never change // ============================================================ const maxLivesAllowed = 3; // this rule never changes in our game console.log(maxLivesAllowed); // prints: 3 // Trying to change a const causes an error: // maxLivesAllowed = 5; // TypeError: Assignment to constant variable. // ============================================================ // PART 2 — let: for values that will change over time // ============================================================ let currentLives = 3; // starts at 3 console.log(currentLives); // prints: 3 currentLives = currentLives - 1; // player lost a life console.log(currentLives); // prints: 2 // ============================================================ // PART 3 — Block scope with let and const // The {} curly braces create a 'block' // Variables declared inside cannot be seen outside // ============================================================ let roundNumber = 1; if (roundNumber === 1) { let roundMessage = "Welcome to Round 1!"; // only lives inside this block console.log(roundMessage); // prints: Welcome to Round 1! } // console.log(roundMessage); // ReferenceError: roundMessage is not defined // It no longer exists out here — the block ended // ============================================================ // PART 4 — var ignores block boundaries (this is the problem) // ============================================================ if (true) { var leakyVariable = "I escaped the block!"; // var ignores {} scope } console.log(leakyVariable); // prints: I escaped the block! // ^ This is unexpected behaviour — var 'leaked' out of the if block // This is why modern JavaScript uses let and const instead
3
2
Welcome to Round 1!
I escaped the block!
Hoisting — The Weird var Behaviour That Trips Everyone Up
Hoisting is one of JavaScript's most surprising quirks, and it's the main reason var fell out of favour. When JavaScript reads your file before running it, it 'hoists' — meaning it mentally moves — all var declarations to the top of their function. The declaration moves up, but the value assignment stays where you wrote it.
The practical effect: you can reference a var variable before the line where you wrote it, and instead of crashing, JavaScript quietly gives you 'undefined'. That silent failure is dangerous because your code keeps running with broken data.
let and const are also technically hoisted, but they are placed in a 'Temporal Dead Zone' (TDZ) — if you try to access them before their declaration line, you get a clean, clear ReferenceError that tells you exactly what went wrong. That's far better than a mystery 'undefined'.
Hoisting is something interviewers love asking about, so understanding the difference between var's silent undefined and let/const's noisy ReferenceError is worth burning into your memory.
// ============================================================ // PART 1 — var hoisting: silent and confusing // ============================================================ console.log(welcomeMessage); // prints: undefined (NOT an error!) // ^ JavaScript hoisted the *declaration* of welcomeMessage to the top // but the *value* "Hello!" hasn't been assigned yet at this point var welcomeMessage = "Hello!"; console.log(welcomeMessage); // prints: Hello! // What JavaScript actually sees (after hoisting) is: // var welcomeMessage; <-- moved to top automatically // console.log(welcomeMessage); <-- undefined, not assigned yet // welcomeMessage = "Hello!"; <-- value assigned here // console.log(welcomeMessage); <-- Hello! // ============================================================ // PART 2 — let hoisting: loud and helpful // ============================================================ // Uncommenting the next line would cause: // ReferenceError: Cannot access 'errorMessage' before initialization // console.log(errorMessage); let errorMessage = "Something went wrong"; console.log(errorMessage); // prints: Something went wrong // let is in the Temporal Dead Zone before this line — // any access before here throws an error immediately // ============================================================ // PART 3 — A realistic hoisting bug with var // ============================================================ function checkHighScore(newScore) { if (newScore > 100) { var congratsText = "New high score!"; } // var leaks out of the if block — it's now undefined here, not missing console.log(congratsText); // prints: undefined (silent bug!) } checkHighScore(50); // score is NOT > 100, so congratsText was never set // But JavaScript doesn't throw an error — it just says undefined
Hello!
Something went wrong
undefined
const With Objects and Arrays — The Gotcha You Need to Know
Here's something that surprises even experienced developers. When you use const with an object or an array, it doesn't make the contents frozen — it only prevents you from pointing the variable at a completely different object or array.
Think of it like this: const means the label on your box is permanently attached. You can still reach inside the box and rearrange what's in it — you just can't pick up the label and stick it on a different box.
This means you can add properties to a const object, update them, and delete them. The same goes for arrays — you can push, pop, and sort. What you cannot do is say 'now this variable points to a brand new object'.
This trips up almost every beginner who first hears 'const means it can't change' — that statement is only partially true. The binding is constant; the contents are not. If you truly need to freeze an object so nobody can change its contents, JavaScript has a built-in method for that: Object.freeze().
// ============================================================ // PART 1 — const object: the binding is locked, not the contents // ============================================================ const userProfile = { username: "alex_42", level: 5, }; console.log(userProfile.username); // prints: alex_42 // You CAN change properties inside the object userProfile.level = 6; // user levelled up! userProfile.badges = ["explorer"]; // add a new property console.log(userProfile); // prints: { username: 'alex_42', level: 6, badges: [ 'explorer' ] } // You CANNOT reassign the variable to a new object: // userProfile = { username: "someone_else" }; // TypeError: Assignment to constant variable. // ============================================================ // PART 2 — const array: same rule applies // ============================================================ const inventoryItems = ["sword", "shield"]; inventoryItems.push("potion"); // adding to the array is allowed console.log(inventoryItems); // prints: [ 'sword', 'shield', 'potion' ] // inventoryItems = ["axe"]; // TypeError — can't swap the whole array // ============================================================ // PART 3 — Object.freeze() if you truly want immutability // ============================================================ const gameConfig = Object.freeze({ maxPlayers: 4, startingLives: 3, }); gameConfig.maxPlayers = 10; // silently fails in non-strict mode console.log(gameConfig.maxPlayers); // still prints: 4 // Object.freeze() makes the contents truly read-only
{ username: 'alex_42', level: 6, badges: [ 'explorer' ] }
[ 'sword', 'shield', 'potion' ]
4
| Feature | var | let | const |
|---|---|---|---|
| Introduced in | ES1 (1995) | ES6 (2015) | ES6 (2015) |
| Scope | Function scope | Block scope {} | Block scope {} |
| Can be reassigned? | Yes | Yes | No (primitives) |
| Can be redeclared? | Yes (dangerous) | No — throws SyntaxError | No — throws SyntaxError |
| Hoisting behaviour | Hoisted as undefined | Hoisted but in TDZ — throws ReferenceError | Hoisted but in TDZ — throws ReferenceError |
| Must be initialised? | No | No | Yes — must assign on declaration |
| Use today? | Avoid in new code | Yes — for changing values | Yes — default choice |
🎯 Key Takeaways
- Use const by default for every variable — switch to let only when you specifically need to reassign the value later. Never use var in new code.
- var is function-scoped and hoisted as undefined, which creates silent bugs. let and const are block-scoped and throw a clear ReferenceError if accessed early — making bugs easier to find.
- const does NOT make objects or arrays fully immutable — it locks the reference (which box the label points to), not the contents. Use Object.freeze() for true deep immutability.
- Block scope means a variable declared inside curly braces {} only exists within those braces. The moment execution leaves the block, the variable is gone — this is precise, predictable, and exactly what you want.
⚠ Common Mistakes to Avoid
- ✕Mistake 1: Using var instead of let or const — Symptom: variables 'leak' out of if blocks and for loops, creating mystery undefined values far from where the bug started — Fix: replace var with const by default, and let when you need to reassign. Do a global find-and-replace on var in any new codebase you start.
- ✕Mistake 2: Declaring a const variable without initialising it — Symptom: SyntaxError: Missing initializer in const declaration — the engine refuses to run the file at all — Fix: always assign a value on the same line as const. Write 'const apiUrl = "https://..."' not 'const apiUrl;' followed by 'apiUrl = "...";' on the next line.
- ✕Mistake 3: Assuming const makes an object fully immutable — Symptom: you declare a const config object, a colleague updates a property deep in the code, and your 'constant' configuration silently changes at runtime — Fix: wrap the object in Object.freeze() if you genuinely need read-only contents, or use a library like Immer for deep immutability in larger apps.
Interview Questions on This Topic
- QWhat is the difference between var, let, and const in JavaScript? Can you explain scope and hoisting in your answer?
- QWhat happens if you try to access a let variable before it is declared? How does that differ from var?
- QIf I declare 'const user = { name: "Sam" }' and then write 'user.name = "Alex"', does that throw an error? Why or why not?
Frequently Asked Questions
Should I use var, let, or const in JavaScript?
Use const as your default for every new variable. If you later realise the value needs to change (like a counter or a flag that flips), switch it to let. Avoid var entirely in modern JavaScript — it has confusing scoping and hoisting behaviour that let and const fix cleanly.
What does 'undefined' mean when I log a var variable?
It means JavaScript hoisted the variable declaration to the top of the function before running your code, but the value assignment hasn't happened yet at the point you're logging it. The variable exists (so no error), but nothing has been put inside it yet. This is one of the key reasons to avoid var — it fails silently instead of telling you what went wrong.
Can I change the contents of a const array or object?
Yes, you can. const only prevents you from reassigning the variable to a completely new value — it doesn't freeze what's inside. So you can push items into a const array, update properties on a const object, and so on. If you need the contents to be truly unchangeable, wrap the object in Object.freeze() at the point of creation.
Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.