Understanding TypeScript type inference and const assertions
TypeScript is “a strongly typed programming language that builds on JavaScript, giving you better tooling at any scale”. By statically checking your code (during development), TypeScript shields you from runtime errors and is a great copilot for small to enormous projects.
That being said, when I introduce TypeScript to newer developers, there comes a moment when it slows them down instead of helping them. I believe this is due to the learning curve of TypeScript: it will first take you time to learn when to use which features of the language, and you might be tempted to type everything “just to be safe”, but this ends up taking you a lot of time for no real benefit.
How to get past this point? This is what I will try to cover in this article, focusing mainly on type inference and const assertions.
Type inference in TypeScript
Unlike some other typed languages with which we are required to type everything, TypeScript is able to understand most of the code we will write without explicitly writing types.
Consider the following code:
Here, TypeScript is able to infer the type of the implicit
variable. This means we don’t need to specify it ourselves! Even more so, not explicitly specifying the type is better because TypeScript infers the type to be 1
instead of number
, which is more precise and closer to reality.
This brings us to a very important rule I follow when writing code with TypeScript:
Only write types when TypeScript does not understand what we are doing.
Expanding on our previous example:
The acceptOnlyOne
function will only accept 1
as an argument. When defining the type as number
for a variable, even if the value is in fact 1
, the function will not accept it. As we said earlier, it is better to not type the variable and let TypeScript infer its type.
The same principle applies to function return types:
Typing the function return does not bring any value, and at worst prevents TypeScript from inferring a precise type.
Const assertions
TypeScript type inference works in the example above because we are defining a variable using the const keyword and assigning it a literal value. TypeScript understands the value is read-only. What would happen if we wanted to assign an object, an array, or the return value of a function?
As we can see, implicit.number
is inferred as a number
, not as 1
.
If we need to pass it to a function that accepts only objects with certain values as properties:
We can see the function will not accept the parameter because the type has not been inferred as a literal value but as a generic type. We need to perform a const assertion.
By using as const
on a variable, we hint TypeScript that it is read-only and we narrow the type to its literal value.
This brings us to a second rule I follow when writing code with TypeScript:
Always use const assertions when working with objects, arrays, and return values of functions if we need precise types.
It also works with arrays:
And for function returns:
Wrapping up
There are a lot of advanced concepts that will make writing code with TypeScript a more pleasant experience, but focusing on type inference and const assertions at the beginning will save you a lot of time and will make TypeScript work for you instead of against you.