I recently read a post that Kyle Simpson, a.k.a. @getify, wrote a couple of months ago about the JavaScript ES6 (ES2015) const keyword. Like most everything Kyle writes, this post is well-worth reading, so if you haven’t read it you can find it here (I’ll wait till you get back ;-)

After reading Kyle’s post, and especially the section on premature const, I thought to respond in the comments section. But as my response grew, I decided it would be better expressed as a standalone post, and here it is.

Before I continue I would like to state that this post should not be taken as an attack on Kyle — I have way too much respect for him. Rather, I strongly disagree with his recommendation regarding const as expressed in that post.

In his post, Kyle stated:

I think the decision steps should generally be:
1. Use var for top-level variables that are shared across many (especially larger) scopes.
2. Use let for localized variables in smaller scopes.
3. Refactor let to const only after some code has been written and you’re reasonably sure that you’ve got a case where there shouldn’t be variable reassignment.

In other words, Kyle recommends generally using let, and only occasionally refactoring to const.

I prefer to think about this issue from a different perspective, by answering the following question: “when do I actually need to reassign values to variables in my JavaScript code?” If this happens often then Kyle’s recommendation makes sense. But if this is a rare occurrence then it’s much better to start with const. Turns out there are five main scenarios where I might perform variable value reassignment:

Initialization Is Separate From Declaration

console.log(v); // outputs ‘undefined’
var v = 42;
console.log(v); // outputs ‘42’

As you can see, initializing v to 42 actually reassigns 42 to a variable already containing the value undefined.

This is not the case with either let or const. For both these instructions, the declaration isn’t hoisted so that actual initialization does take place. In other words, reassignment no longer happens in this scenario. As a result, there is no longer any need to separate declaration from initialization, and in ES6 they should always be one and the same.

Variable Reuse

In addition, reusing variables can actually hurt performance in JavaScript. This is because if the same variable is assigned multiple values having different types, it becomes harder for the JavaScript compiler to optimize the code.

I consider variable reuse in JavaScript to be a bad practice, even prior to ES6. Hence variable value reassignment for the purpose of reuse should be avoided.

Multi-Part Expression

var totalSum = some + thing;
totalSum += other + stuff;

Using compound assignment operators in this way can be appealing, but results in value reassignment — in this case to the variable totalSum.

I have found that it can be useful to assign partial computation values to appropriately named variables:

const sumOfThings = some + thing;
const sumOfStuff = other + stuff;
const totalSum = sumOfThings + sumOfStuff;

While this does result in a bit more typing, it also yields code that is more self-documenting. Another alternative when dealing specifically with string expressions is to use ES6 template string instead of multi-part expressions.

Variables In Loops

var sum = 0;
for (let i = 0; i < vs.length; i++) {
sum += vs[i];
}

I much prefer using iteration methods instead of explicit for loops, as they allow me to focus on the what rather than the how. The result is code that is simultaneously more concise, expressive, and less bug-prone:

const sum = vs.reduce((partialSum, v) => partialSum + v, 0);

Or even better, using lodash:

const sum = _.sum(vs);

As you can see, using iteration methods instead of explicit for or while loops eliminates the need for reassigning values to variables on each iteration.

BTW if you use the new for…of mechanism for looping over iterable collections or sequences, or even the old (and yucky) for…in, you can specify the variable as const:

const a = [1,2,3,4];
for (const v of a) {
console.log(v);
}

this is because the scope of v is inside the loop, so it’s recreated and initialized on every iteration, rather than being reassigned a new value.

Asynchronous Computation

With ES6 and beyond we have much improved mechanisms for managing asynchronous computations, in the form of promises, generators, and async/await. These mechanisms facilitate writing asynchronous operations in a synchronous manner, thus significantly reducing the need for reassignment:

const result = await lengthyComputation();

In this example, JavaScript waits for lengthyComputation to complete, without blocking execution of other code. When lengthyComputation finishes and returns a value, that value is assigned to result, and execution of this code segment resumes. This “synchronous-looking asynchronous code” eliminates much of the need for variable reassignment in such scenarios.

Conclusion

I prefer using const for variables that aren’t reassigned because it’s a clear indication of my intent to anyone who reads my code. It also prevents me from being lazy about refactoring. Unlike Kyle, I therefore urge you to use const for all scoped variables as your default behavior. Switch to let only if const doesn’t work, and even then consider refactoring your code so that it does.

I’m a Performance Specialist at Wix.com. My job is to make 80 million websites hosted on the Wix platform load and execute faster. Opinions are my own

I’m a Performance Specialist at Wix.com. My job is to make 80 million websites hosted on the Wix platform load and execute faster. Opinions are my own