Var, let and const in JavaScript: The Infernal Trio Explained (with a Touch of TypeScript)
Hey there, you JavaScript developer pulling your hair out over these variables! Wondering when to use var, let, or const without embarrassing yourself in team meetings? Stay put, we'll sort this out together, calmly and with a smile. On the agenda: we'll explore each keyword, compare their scope (global, function, block), have a laugh about hoisting (that somewhat magical JavaScript mechanism), throw in some practical examples, and finish with a dash of TypeScript. Ready? Let's go!
The var keyword: The unpredictable ancestor
Let's start with var, the elder of JavaScript variables. If JavaScript were a TV series, var would be that old-school character, unpredictable but endearing. Until ES5, it was all we had to declare our variables. You can still use it today, but beware: it has some quirks that might surprise you.
Characteristics of var
- Scope of var: A variable declared with var is limited to the function containing it (or global if declared outside any function). It ignores block curly braces (if, for, etc.). In short, a var declared in a block remains accessible outside the block, as long as we're in the same function.
- Re-declaration allowed: With var, you can declare the same variable multiple times in the same scope without error. For example:
var x = 1; var x = 2;won't trigger an error – JavaScript will just overwrite the first value with the second. Handy for legacy code, but a source of confusion to avoid in new code.
To illustrate var's overly broad scope, here's a small example:
javascript
Yes, even though this fruit was declared inside an if statement, the console.log outside the block displays it without any problem. var doesn't care about block boundaries: it's either global or attached to its containing function, period.
And the best (or worst) for last: hoisting. In English, we sometimes call it "lifting," but let's be honest, we mostly use the term hoisting. It's var's tendency to quietly declare itself at the top of the scope. JavaScript, during execution, acts as if all var declarations were "lifted" to the top of the function. As a result, you can use your variable before its declaration line... with a somewhat strange default value. Let's look at this more closely in the hoisting section below (spoiler: var loves to surprise you with undefined).
In summary, var has served us well, but it's a bit sneaky around the edges: broad scope, silent re-declaration, unpredictable hoisting... We can do better, and luckily, ES6 introduced new keywords to bring order to the chaos!
The let keyword: The well-behaved new variable
In 2015, JavaScript welcomed let (along with const). And there, we got a little variable makeover. let is like the cool, disciplined kid: it fixes most of var's quirks while remaining flexible.
Characteristics of let
- Scope of let: A variable declared with let is limited to the block in which it's defined. A block is anything between { ... } – whether it's a function, an if statement, a for loop, etc. If you declare a let inside a block, it doesn't exist outside. Finally, some order! No more variables escaping from if statements or loops without permission.
- No re-declaration: Unlike var, you can't declare the same let variable twice in the same scope. If you try to do
let x = 1; let x = 2;in the same block, you'll get a niceSyntaxError: Identifier 'x' has already been declared. That avoids a lot of confusion, admit it.
To clearly see the scope difference compared to var, let's compare with the previous example by replacing var with let:
javascript
With let, once you're out of the if statement's curly braces, that's it, the vegetable variable is no longer defined. Try to log it and bam: ReferenceError. That's let enforcing good scoping behavior.
And what about hoisting? let variables (and const) are also hoisted, but differently. They're not usable before being declared. Technically, the JS engine knows they exist in the block (it reserves the memory), but until the declaration line is executed, any attempt to access them will trigger an error. We say these variables are in the Temporal Dead Zone (yes, it sounds like a horror movie, but it's just the fancy name for "inaccessible before initialization"). We'll detail this behavior a bit later, but remember that let won't let you go wild using a variable too early.
In practice, let has become the default choice for variables that will change over time. It brings clarity (block scope) and safety (no accidental re-declaration, no access before declaration). In short, let is your new friend for declaring temporary or modifiable variables.
The const keyword: The safe bet (but not frozen)
Now for const! Introduced at the same time as let, this keyword creates constants... or almost. Let's say it creates variables whose reference won't change. const has the same block scope as let (no more vars wandering around) and the same hoisting rules (temporal dead zone until declaration). The big difference is that once initialized, the variable can't be reassigned.
Characteristics of const
- Must initialize: With const, you must give a value at declaration. No choice.
const x;by itself won't work (direct SyntaxError). You need to doconst x = 42;for example. - No reassignment: Once your constant is defined, there's no way to give it a new value later. If you try, you'll get an error at runtime (TypeError saying "assignment to constant variable"). The value is fixed... well, the reference is fixed.
- Block scope & no re-declaration: Like let, a const lives in the block where it's declared, and you can't declare the same name twice in the same scope. It's clean.
Look at this small example to clearly see the difference with a modifiable variable:
javascript
We define PI as constant, we can use it without worry as long as we don't try to change it. When we try to do PI = 3.15, JavaScript stops us cold: no modifying a constant.
Note: Constant doesn't mean 100% immutable. If the value is an object or an array, you can still modify the inside of the object/array. What doesn't move is the variable itself (the memory reference). For example:
javascript
Here, we could change name in the user object despite const. However, completely reassigning user to another object is forbidden. So, const = constant, not modifiable, but only at the variable level itself. Don't confuse variable constancy with value immutability.
javascript
With an array, it's the same logic: we can modify its content (add, delete, modify elements) because we're only changing the inside of the array. But we can't reassign the fruits variable to a new array. It's like the array is a box: we can change what's inside, but we can't replace the box itself.
In practice, const is ideal for all values that shouldn't change: for example, a configuration, a reference, etc. It's widely used and makes code more robust (we know this variable won't move). In fact, best practices often recommend using const by default, and switching to let only when we know the value will need to change. As for var... well, unless there's a specific reason, we forget about it 😉.
Variable scope: global, function, or block?
Let's talk more about scope. Scope determines "where" a variable is accessible in your code. There are mainly three levels of scope in JavaScript:
Types of scope
- Global scope: A variable is global when it's declared outside any function (in the main script or console). It's then accessible everywhere (in any function or block). ⚠️ Warning, outside a module, a global var becomes a property of the global object (like window in a browser), while let and const create "clean" global variables (not attached to window). In an ES6 module, top-level variables don't leak onto the global object at all.
- Function scope: This is the scope created inside a function. A var declared in a function will only be visible inside that function (and its sub-functions if any). We can't see it outside. Same for let and const declared in a function: they remain local to that function.
- Block scope: This is the scope limited to a block delimited by { ... } (for example inside an if statement, a for loop, a while loop, or simply an isolated block). let and const are block-scoped, meaning they're only accessible in the block where they're defined (and its sub-blocks). var, on the other hand, doesn't have block scope: its reference block is the containing function (or global if no function).
In summary: var is limited to the function (or global), while let and const are limited to the current block. This difference solves many classic pitfalls. For example:
javascript
Here, the loop with var leaves i hanging outside, with its final value 3. The loop with let, on the other hand, cleans up j on exit: impossible to access it afterward. In practice, this means you can have several let variables named the same in different blocks without interference, where a single var would have been shared between all these blocks.
Hoisting: When JavaScript plays hide and seek
Let's move on to hoisting, this declaration lifting mechanism. It's often a source of confusion (and jokes in meetings), so let's clarify. Hoisting is when the JavaScript engine "moves" variable and function declarations to the top of their scope during execution. In reality, nothing is really moved in your code, it's just that JavaScript allocates memory for your variables upfront, before executing step by step.
Hoisting behavior
- For var: The declaration is hoisted AND initialized to undefined by default. So you can absolutely use a var variable before its declaration line, it already exists (with the value undefined until the real assignment comes along later). It's a legitimate language behavior, but it can cause quite sneaky bugs if you're not careful.
- For let and const: The declaration is also hoisted (the engine knows there's a variable in this block), but no default initialization is done. The variable is placed in the Temporal Dead Zone until the declaration line is executed. In practice, this means that if you try to read or use this variable before its actual declaration, JavaScript will throw an error (ReferenceError). In other words, the variable exists in theory, but you can't access it until its declaration/initialization is executed.
Let's do a little hoisting test in practice:
javascript
At the time of the first console.log, the myVar variable has indeed been created in memory via hoisting, but its default value is undefined (since we haven't assigned it yet). No error, just undefined being displayed. Then we assign "Hello", and the second time we get the expected value.
Now let's see with let:
javascript
Here, the first console.log triggers a ReferenceError. Why? Because myLet is hoisted without initialization. It's in its temporal dead zone, inaccessible. JavaScript refuses to give us a value that doesn't really exist yet. Once we execute let myLet = "Hello";, the variable comes out of its dead zone and gets "Hello". The second console.log then works perfectly.
We can see it clearly: var behaves like a ninja by lifting its declaration to the top and quietly giving itself the value undefined, while let/const play it safe and prevent any use until they're initialized. Moral of the story: avoid coding by relying on hoisting, it's a recipe for brain knots (and unexpected undefineds). Better to declare your variables at the top of their scopes and keep your code clear. If you forget, let/const's behavior will remind you with an error, where var would have let you flounder with an undefined.
Ah, and while we're at it, note that classically declared functions (function myFunction() \{ ... \}) are also fully hoisted. You can call a function declared lower in your code, JavaScript will already know it. But be careful, arrow functions or function expressions assigned to variables follow the hoisting rules of var/let according to the keyword used (a topic for another day 😉).
A quick detour through TypeScript: Even more clarity
Before we part ways, let's quickly talk about TypeScript. If you start using TypeScript (the super-set of JavaScript that adds static typing), you'll find our three friends var, let, and const. Good news: their scope and hoisting behaviors remain exactly the same as in pure JavaScript, since TypeScript compiles to standard JavaScript. However, TypeScript brings its personal touch to typing, and it's worth a look, especially for let and const.
TypeScript and variables
- Type inference: TypeScript guesses the type of your variables based on the initial value. If you write
let fruit = "apple";, TypeScript will infer that fruit is of type string. If you writelet age = 25;, it will understand that age is a number, etc. For let, it stays quite broad: the variable can change, so the type is the basic one (string, number, etc.). - Constants and literal types: Where it gets fun is that with const, TypeScript will infer a literal type. In other words, since the value is constant, TS can afford to say "this variable has exactly this value as its type". For example:
typescript
The color variable is of type "red" and not just string. Benefit? If later your code expects exactly the value "red" or "blue" (for example a parameter that can only be one of these colors), and you have const color = "red", you can pass it without worry. With a let color = "red" (broad string type), TypeScript would have complained because "red" is just one possibility among all strings. In short, const in TypeScript allows for ultra-precise typing when it's useful. It's like a bonus that further reinforces the constant concept.
- Same discipline: TypeScript will encourage you to use let and const exactly as in ES6. In fact, most TypeScript projects completely ban var (there's even a linter rule for that). And if you try to use a variable before declaration, TypeScript will yell at you at compilation time – well before the JavaScript runtime does. For example, if you write TypeScript with
console.log(myVar); var myVar = 3;, it will signal that you have a scope or order problem. Same for a let used too early, TS knows the hoisting rules and will protect you (as much as possible) from mistakes.
In summary, TypeScript doesn't change the fundamental rules of var/let/const, but it reinforces them with the safety net of typing. You gain in clarity and catch errors earlier. Remember especially that const in TypeScript is doubly winning: a non-reassignable variable and a precise literal type – what more could you ask for?
Conclusion: When to use what without getting it wrong
We've covered everything, so who's the winner in the var vs let vs const match? No big surprise: in 2025, the let & const duo wins hands down for writing clean and maintainable code. Here's a spicy recap of practical advice:
Best practices
- Use const as much as possible: For any variable whose value doesn't need to change, const is your best friend. It locks down reassignment and clearly indicates to everyone (including yourself in 6 months) that this value won't move. Perfect for configurations, fixed references, etc.
- If you need to change the value, use let: Need to increment a counter, accumulate a result, store a modifiable intermediate value? let is there for that. It behaves well (block scope, no unexpected hoisting traps), and it gives you the flexibility to change the value.
- Avoid var except in weird cases: Honestly, var can practically disappear from your everyday vocabulary. It survives for backward compatibility reasons and some specific scenarios, but in modern code it's not really in vogue anymore. If you're working on an old project or need to deliberately manipulate the global scope (things we generally avoid), okay, var might show its face. Otherwise, you can put it in the JavaScript relics museum 😄.
- When in doubt, explain yourself: If one day you use var and someone asks why in a meeting, have a good reason and explain it (for example, "I need to declare this variable globally for old scripts to access it"). But 99% of the time, you won't need this gymnastics: let and const will cover all your needs without batting an eye.
There you go, you now know when to use var, let, or const in JavaScript, and even a bit about how it works with TypeScript. No more confusing our three companions or getting teased in code review because you pulled a var out of your hat without a valid reason. Go code peacefully, and don't forget: const as much as possible, let if necessary, and var... as little as possible! Happy coding!