Why You Should Stop Using let: Embracing const in Modern JavaScript and TypeScript

@rnab
3 min readOct 25, 2024

In the evolving landscape of JavaScript and TypeScript, developers often face choices that seem trivial at first glance but can significantly impact code quality. One such choice is whether to use let or const for variable declarations. While let and const were both introduced in ES6 to replace var, there's a growing consensus among seasoned developers to lean heavily on const over let. This article delves into why this practice might be beneficial for your next project.

The Case Against let

At its core, the let keyword allows us to declare variables that are block-scoped, meaning they only exist within the context in which they're declared. While this is an improvement from the function-scoping issues that plagued var, let inherently introduces mutability—a property not always desirable in modern programming paradigms where immutability yields more predictable and less error-prone code.

Consider the following example using let:

let count = 0;
function increment() {
count += 1;
}
increment();
console.log(count); // Output: 1

In this example, the value of count changes every time we call increment(). If our application grows complex, tracking these mutable state changes can become cumbersome and lead to bugs that are hard to trace.

Benefits of Using const

const, on the other hand, promotes immutability by preventing reassignment of variables. Once a const variable is assigned, its reference cannot be changed, although if it's an object type, the contents of the object can still be modified (shallow immutability).

Here’s how you could use const in place of let:

const count = {
value: 0
};
function increment(counter: { value: number }) {
counter.value += 1;
}
increment(count);
console.log(count.value); // Output: 1

By leveraging objects with const, while maintaining some degree of mutability where required, you strike a balance between constancy and flexibility. Importantly, primary logical references remain constant, reducing unintended side effects.

Enforcing Immutability with Deep Freeze

To fully embrace immutability, especially when working with nested data structures, you can use utility functions like Object.freeze() provided natively by JavaScript or third-party libraries designed for deep freezing objects.

Here’s an example showing shallow versus deep immutability:

// Shallow freeze implementation
const obj = Object.freeze({ a: 1, b: { c: 2 } });
obj.b.c = 3; // This operation is allowed since 'b' is not deeply frozen
console.log(obj.b.c); // Output: 3
// Deep freeze implementation
function deepFreeze(object: any) {
Object.keys(object).forEach(prop => {
if (typeof object[prop] === 'object' && object[prop] !== null) {
deepFreeze(object[prop]);
}
});
return Object.freeze(object);
}
const deeplyFrozenObj = deepFreeze({ a: 1, b: { c: 2 } });
deeplyFrozenObj.b.c = 3; // TypeError: Cannot assign to read-only property 'c'
console.log(deeplyFrozenObj.b.c); // Output: 2, as expected

Using const alongside techniques such as deepFreeze helps ensure that data remains immutable throughout your application's lifecycle.

Real World Impact on Team Workflow and Code Quality

When a team adopts immutability practices widely facilitated by const, their codebase tends to gain several benefits:

  • Predictability: Immutable data leads to fewer surprises because once data is created, it does not change.
  • Debugging: Immutable states make debugging easier because unexpected changes to variables do not occur.
  • Concurrency: Concurrent programming becomes simpler when data doesn’t mutate, eliminating many potential race conditions.
  • Readability and Maintainability: Developer intent is clearer — if you’re using const, it signals downstream readers that the binding should never change, enhancing maintainability.

Takeaway lessons from functional programming paradigms remind us that constraints foster creativity. By embracing const and limiting unnecessary mutation, we unlock higher-quality, more reliable development processes.

Conclusion

As you progress through developing intricate systems, consider adopting const as your default declaration. Remember, preparedness defines our reactiveness; making conscious decisions about seemingly minute details like variable declarations can exponentially improve readability, efficiency, and safety in ever-growing codebases.

Change begins with a single keyword: elevate your code to the next level by reigning in unwarranted mutations today!

--

--

@rnab
@rnab

Written by @rnab

Typescript, Devops, Kubernetes, AWS, AI/ML, Algo Trading

No responses yet