TLDR: For readable code, let should be used to communicate that the object referred to has “persistent identity.” Otherwise, const is best.
Background on Mutation and Persistent Identity
This article is a response to ES6 const is not about immutability, which I mostly agree with.
Here are two programs that do almost the same thing.
Mutable Identity Example
Persistent Identity Example
In the mutable identity example, the array on the line 1 is equal to the array on line 3. The identifier list is a constant that points to an object that is mutated on line 2. The binding between the constant “list” and the object it points to does not change.
In the persistent identity example, the array on line 1 is not equal to the array on line 3. The arrays on lines 1 and 3 respectively each do not change. However, the binding for the variable “list” does change. “list” points to different objects on lines 1 and 3.
In my opinion, the biggest advantage of having let and const is that they help communicate intent, making code easier to read. When you see const, you know that the binding between the constant the thing it points to do not change in the current scope. let signals that the reader can expect the binding to change.
Now why would a programmer change the binding of a variable within a scope? For example, here’s something you don’t see much of:
In general, if you’re going to give two distinct objects the same name in the same scope, the two distinct objects are sort-of the same thing, even though they have distinct identities. The notion of “persistent identity” captures how two things can be same-y without being identical.
Here is the persistent identity example again:
Persistent Identity Example
Conceptually, what’s going on in the example is that we’re “building up a list”, even though the concrete steps we’re taking are to throw away the array from line 1 (it will be garbage collected) and create a new array.
I found the term “persistent identity” in Purely Functional Data Structures, which contains this stab at a definition:
A unique identity that is invariant under updates. For example, in a stack-based interpreter, we often speak informally about “the stack” as if there were only one stack, rather than different versions at different times. We will refer to this identity as a persistent identity. This issue mainly arises in the context of persistent data structures; when we speak of different versions of the same data structure, we mean that the differ- ent versions share a common persistent identity.
If you are binding an identifier to a variable and the binding will not change, const communicates that intent clearly.
The only case in which makes sense to reuse a variable name in the same scope is if the object has persistent identity, so that’s the only time you’ll need let. let is useful when doing side-effect free operations. These include string operations, certain array operations like concat, map, filter and reduce as well as methods on immutable data structures(such as with Immutable.js or seamless-immutable).
Hopefully this will help guide with reading and writing code with const and let, and help circulate terminology (“persistent identity”) that makes it easier to talk about things that are hard to talk about.