Understand const, as const and readonly in TypeScript
“const” arrays and objects are not what you think they are!
… because you can actually change contents of arrays and objects, even when they are defined as const!
In this article, we will dig into the hearts of the compiler and the type system in order to understand how to handle TypeScript’s mechanisms of defensive programming, what pitfalls there are, and how to cope with them.
The role of the compiler
To fully understand const, as const and readonly, we have to know that the TypeScript compiler operates sequentially in 2 big steps:
- Type-Checking
- Transpiling TypeScript to JavaScript
There’s a big difference to other programming languages like C#, C++, or Java: Strong Typing exists only until compile time, since at runtime we execute JavaScript!
The compiler tries to use as many JavaScript features as possible for the desired target. If the target JavaScript version supports the const keyword, the compiler will use it. Otherwise it won’t.
But as const and readonly are never translated, because they simply don’t exist in JavaScript. They are only used during the type-checking phase of the compilation.
As an example, look at the following code snippet:
If translated to ES2021 by the TypeScript compiler, the resulting JavaScript code looks like this:
As you can see, the const keyword is being translated, but as const and readonly have no effect on the JavaSript code itself. Therefore, we have to investigate further the type checking phase.
Types in TypeScript
If we don’t explicitly type a variable or constant, the compiler tries to estimate the best fit for a type. How this works in detail also significantly differs from how it works in Java or C#.
For example, if we declare a variable and assign a text, the compiler estimates “string” to be the best matching type for the variable.
But if we assign a text to a content, the compiler creates a new type that has only 1 valid value: The content of our constant:
This makes sense, because a constant should never have a different value than the one it was assigned to at first.
What types do arrays get?
But how about our 3 arrays of colors? They behavior here is a little different:
For arrays (and objects), the const keyword does not impact the type. It just indicates, that we cannot assign anything else to the constant once it was declared. In detail:
- const/let defines, if we can assign a new value to colors or colors2.
- as const defines, if the assigned array is readonly or not. The generated type is a type with exactly the values that colors2 and colors3 was initially assigned to.
The impacts of “const” and “as const”
Here’s the thing: We are allowed to change the values of an array or an object, even if it’s const. If we want them to be readonly, we have to declare them as const.
Wow! Let’s wrap this up and add some details:
const prohibits us from assigning something new to the constant. But the initially assigned content itself can change, if it’s an array or an object:
as const defines a readonly type that is fixed to the initially assigned value and therefore prohibits changes:
Look at the second error message: It indicates a type-mismatch. Since colors2 is not a constant but a variable, a new assignment is technically possible — but only if we assign something with the type readonly[“red”, “green”, “blue”].
This is exactly the type that colors3 has, so we can assign colors3 to colors2, but we cannot do it the other way round (since colors3 is constant):
Also “const” objects can change
What is true for arrays is also true for objects: The const keyword prohibits us to make any assignments to the constant, but the contents of the object can change:
To make the object readonly, we have to declare it with as const:
How to use readonly
So what about the readonly keyword? We can use it, for example, to make parameters readonly in function calls. I guess, most of your functions are designed in a way they don’t change the parameters within the function body. So why not mark them readonly and get an additional check for free?
Anoter way we can use the readonly keyword is within classes: Here, we can declare properties as readonly. While constants can only be assigned to directly when they are declared, readonly properties (and fields) can be assigned in the constructor:
Conclusio
Defensive programming helps to avoid mistakes and therefore can reduce the amount of bugs produced in our programs. It is important to know the features that help us with defensive programming:
- const indicates that no new value can be assigned to the constant, but the content of the value can change anyways, if it’s an array or an object.
- as const declares the content of a variable or constant as readonly and makes sure that the contents of the variable or constant are not changed
- readonly is a keyword that can be used to declare function parameters or class fields and properties readonly.
More TypeScript stuff
If you want to learn more cool stuff about TypeScript, check out our youtube channel — here’s an example: