JavaScript offers three ways to declare variables: var
,
let
, and const
. Many programmers aren't entirely clear
on when to use which one, and most tutorials and linters force you to use them
incorrectly. Let's see how to write cleaner and more understandable code
without unnecessary rules that don't actually help us.
Let's Start with the Most Dangerous Part
JavaScript has one treacherous quirk: by simply omitting a variable
declaration, you can unknowingly use a global variable. All it takes is
forgetting var
, let
, or const
:
function calculatePrice(amount) {
price = amount * 100; // Omission! Missing 'let'
return price; // We're using a global variable 'price'
}
function processOrder() {
price = 0; // We're using the same global variable!
// ... some code calling calculatePrice()
return price; // We're returning a completely different value than expected
}
This is every developer's nightmare – the code appears to work correctly until something mysteriously starts failing elsewhere in the application. Debugging such errors can take hours because a global variable can be overwritten anywhere in the application.
That's why it's absolutely crucial to always declare variables using
let
or const
.
Forget About var
The var
keyword has been in JavaScript since its inception in
1995 and carries some problematic properties that were considered features at
the time of the language's creation but proved to be a source of many bugs over
time. After twenty years of language development, JavaScript's authors decided
to address these problems – not by fixing var
(to maintain
backward compatibility) but by introducing the new let
keyword in
ES2015.
You can find plenty of articles on the internet dissecting the problems with
var
in the finest detail. But you know what? There's no need to
get bogged down in the details. Let's just treat var
as a relic of
the past and focus on modern JavaScript.
When to Use let
let
is the modern way to declare variables in JavaScript.
The nice thing is that the variable only exists within the code block (between curly braces) where it was defined. This makes the code more predictable and safer.
if (someCondition) {
let temp = calculateSomething();
// temp is only available here
}
// temp no longer exists here
In loops, the declaration is technically placed before the curly braces, but don't let that confuse you – the variable only exists within the loop:
for (let counter = 0; counter < 10; counter++) {
// The counter variable only exists in the loop
}
// counter is no longer accessible here
When to Use const
const
is used to declare constants. These are typically
important values at the module or application level that should never
change:
const PI = 3.14159;
const API_URL = 'https://api.example.com';
const MAX_RETRY_ATTEMPTS = 3;
However, it's important to understand one key detail: const only prevents assigning a new value to the variable – it doesn't control what happens with the value itself. This distinction is particularly evident with objects and arrays (an array is also an object) – const doesn't make them immutable objects, i.e., it doesn't prevent changes inside the object:
const CONFIG = {
url: 'https://api.example.com',
timeout: 5000
};
CONFIG.url = 'https://api2.example.com'; // This works!
CONFIG = { url: 'https://api2.example.com' }; // This throws TypeError!
If you need a truly immutable object, you need to freeze it first.
The let
vs const
Dilemma
Now we come to a more interesting question. While the situation with
var
vs let
is clear, the use of const
is
the subject of many community discussions. Most tutorials, style guides, and
linters promote the rule “use const
wherever you can.” So we
commonly see const
used in function or method bodies.
Let's explain why this popular “best practice” is actually an anti-pattern that makes code less readable and unnecessarily restrictive.
The approach “if a variable's value isn't reassigned in the code, it
should be declared as const
” seems logical at first glance. Why
else would const
even exist? The more “constants,” the safer
and more predictable the code, right? And faster too, because the compiler can
better optimize it.
However, this entire approach fundamentally misunderstands the purpose of constants. It's primarily about communicating intent – are we truly trying to signal to other developers that this variable should never be reassigned, or do we just happen not to reassign it in our current implementation?
// Real constants - values that are constant by their nature
const PI = 3.14159;
const DAYS_IN_WEEK = 7;
const API_ENDPOINT = 'https://api.example.com';
// vs.
function processOrder(items) {
// These AREN'T constants, we just happen to not reassign them
const total = items.reduce((sum, item) => sum + item.price, 0);
const tax = total * 0.21;
const shipping = calculateShipping(total);
return { total, tax, shipping };
}
In the first case, we have values that are constants by their nature –
they express immutable properties of our system or important configuration data.
When we see PI
or API_ENDPOINT
somewhere in the code,
we immediately understand why these values are constants.
In the second case, we're using const
just because we happen to
not reassign the values right now. But that's not their essential
characteristic – these are regular variables that we might want to change
in the next version of the function. And when we want to do that,
const
will unnecessarily prevent us.
In the days when JavaScript was one big global code, it made sense to try to
secure variables against reassignment. But today we write code in modules and
classes. Today it's common and correct that the scope is a small function, and
within its scope, it makes no sense to worry about the difference between
let
and const
.
Because it creates completely unnecessary mental overhead:
- The programmer has to think while writing: “Will I change this value? No? Then I must use const…”
- It distracts readers! When they see
const
in the code, they wonder: “Why is this a constant? Is this some important value? Does it have any significance?” - In a month we need to change the value and have to deal with: “Can I change const to let? Is someone relying on this?”
Simply use let
and you don't have to deal with these
questions at all.
It's even worse when this decision is made automatically by a linter. That
is, when the linter “fixes” variables to const because it only sees one
assignment. The code reader then unnecessarily wonders: “Why must these
variables be constants here? Is it somehow important?” And yet it's not
important – it's just a coincidence. Don't use the prefer-const
rule in ESLint!
By the way, the optimization argument is a myth. Modern JavaScript engines
(like V8) can easily detect whether a variable is reassigned or not, regardless
of whether it was declared using let
or const
. So
using const
provides no performance benefit.
Implicit Constants
In JavaScript, there are several constructs that implicitly create constants
without us having to use the const
keyword:
// imported modules
import { React } from 'react';
React = something; // TypeError: Assignment to constant variable
// functions
function add(a, b) { return a + b; }
add = something; // TypeError: Assignment to constant variable
// classes
class User {}
User = something; // TypeError: Assignment to constant variable
This makes sense – these constructs define the basic building blocks of
our code, and overwriting them could cause chaos in the application. That's why
JavaScript automatically protects them against reassignment, just as if they
were declared using const
.
Constants in Classes
Classes were added to JavaScript relatively recently (in ES2015), and their
functionality is still gradually maturing. For example, private members marked
with #
didn't arrive until 2022. JavaScript is still waiting for
class constant support. For now, you can use static
, but it's far
from the same thing – it marks a value shared between all class instances,
not an immutable one.
Conclusion
- Don't use
var
– it's outdated - Use
const
for real constants at the module level - In functions and methods, use
let
– it's more readable and clearer - Don't let the linter automatically change
let
toconst
– it's not about the number of assignments, but about intent
Leave a comment