Should I use const?
When writing JavaScript, I often use const
.
In terms of technical differences between const
and let
, there is very little difference. And some people argue that const
shouldn’t be used often. I recommend reading these pieces:
But I like to use const
a lot, and I’ve been thinking about why.
The team style guide
At work, it was agreed that const
should only be used for “actual constants”. If a value is mutated, it should be defined with let
instead.
let arr = []; // arr is mutated, so we use let
arr.push(3);
Following this idea, anywhere const
is used should be an actual constant. Rather than blindly using const
wherever possible, using const
is a way for a developer to communicate their intention. I like that principle.
Why I like const
Why do I use const
so often? There are two answers which stand out to me.
Firstly, I tend to write code in a functional style. I prefer to use map
and filter
rather than push
. While thinking in a functional style, I use constant variables, and that signals my intent.
In functional code where all values are immutable (or at least treated as immutable), I don’t think that this is particularly controversial.
But thinking about my other reasons for using const, I want to examine the style guide more closely.
Thoughts about mutation
In my work, I write a lot of what we call workflows. For example, for a process of applying for an extension to a deadline, the workflow would manage passing an application between various approvers. This is modelled essentially as a state machine.
Each particular instance of a workflow is represented by an object, by convention referred to as M
. I’ve never heard anybody question the use of const
in code like this:
const M = workflow.instanceForRef(ref);
I’m only interested in one particular workflow instance, so I have no intention of reassigning M
, so const
makes sense technically. But what about style?
Each workflow instance has a state
property, which represents the state of the workflow as a string, and cannot be modified directly. Like basic state machines, state
is changed by transitioning. For example:
const M = workflow.instanceForRef(ref);
console.log(M.state); // "wait_submit"
M.transition("submit");
console.log(M.state); // "wait_approval_supervisor
Based on a strict reading of the style guide, M
is mutated, so I shouldn’t use const
. But, to me at least, const
feels right for communicating what I’m thinking, and I’ve not heard anybody questioning it before.
Each workflow has a unique ID, and in this code I am only interested in one specific workflow instance. Although its state may change, M
is always referring to the very same workflow instance throughout my function. const
is a tool I use to communicate that.
So I seem to have an uncontroversial example where it is okay to not follow the letter of the style guide. Is there some way we can clearly define when the rule can be ignored?
Another example which comes to mind is usage of data structures such as JavaScript’s Map and Set. In my opinion, it makes sense to use const
here:
const uniqueNames = new Set();
for(const person of people) {
uniqueNames.add(person.name);
}
uniqueNames
is being mutated, but it makes sense to me that the set is a constant concept as a whole, rather than a changing value.
Interestingly, that makes more sense to me than something like this:
const results = [];
results.push(something());
if(condition) {
results.push(somethingElse());
}
results.push(yetAnotherThing());
if(skipFirstResult) {
results.shift();
}
return results;
I would (as the style guide dictates) use let result = [];
instead.
I wonder whether an important distinction is whether the variable looks like it represents a concrete concept (which seems like a tricky characteristic to define). M
refers to a particular workflow instance (with a unique, immutable ID). uniqueNames
is a set of all names which have been seen so far. result
is just the list of results I’m collecting together.
In the first two examples, the const
variable is an instance of some class implemented far away from my code, with a clearly defined interface and a hidden implementation. When I think about M
, I don’t think about the exact steps of moving state; I think about the transition
method in the docs. When I think about the array results
, I think about sticking more elements on the end.
What if I had some kind of queue structure, implemented using a bare array?
const queue = [];
while(queue.length) {
const node = queue.shift();
for(const adjacent of node.children) {
queue.push(adjacent);
}
}
Here, push
and shift
are the exact same methods as in the results
example, but they seems a lot more meaningful. It wouldn’t make sense to swap queue
for a different array: that queue is the main part of the algorithm.
In the queue example, I think const
makes more sense, but that doesn’t follow the letter of the style guide.
I wonder:
- Should the style guide be changed?
- If it was changed, how should it be changed?
- Can rules in a style guide ever consider every way in which developers want to express ideas?
- How much value is there in spending time thinking about this? How much value have we lost or gained compared to the previous version of the style guide, when we all used
let
all the time?
I don’t have answers. But I’m inclined to agree with Dan Abramov: just pick a style (as a team), and work with it.