ยท hands on
Type Inference & Type Annotations in TypeScript
TypeScript has two concepts: type annotations and type inference. Type annotations involve explicitly specifying the type of a parameter or variable, while type inference occurs when TypeScript automatically determines the type based on the implementation.
In this TypeScript tutorial, we explore the concepts of type annotations and type inference. We clarify the distinction between the two and discuss their significance in writing maintainable and robust code.
Contents
- Type Annotations vs. Type Inference
- Implicitly any type
- When you need annotations
- Best Practices
- Video Tutorial
Type Annotations vs. Type Inference
A Type annotation involves explicitly specifying the type of a parameter or variable using the colon symbol followed by the type we want to use.
Type inference occurs when TypeScript automatically determines the type. When we hover over our add
function, we can see that TypeScript has inferred its return type to be number
. This inference is based on the implementation of our function and knowing that its input parameters are also numbers. While we could manually annotate the return type ourselves, it's often unnecessary since TypeScript is capable of inferring it automatically.
Implicitly any type
There are cases though where TypeScript cannot infer the type accurately, and we have to provide explicit annotations:
Although VS Code suggested using numbers based on our console.log
statement, the +
operator can be used with various other types in JavaScript. Without the annotation, any type could have been supplied as input, resulting in TypeScript inferring the type as any
. This does not align with the idea of a strongly typed codebase, where the goal is to know the precise type being used rather than relying on any
type.
While we could modify our TSConfig to allow implicit typings of any
, it is not good practice to begin with. IntelliSense even shows us a hint that suggest using a more specific type:
When you need annotations
Type inference occurs in many scenarios. If we create a variable and assign a value to it, TypeScript will automatically infer its type based on the value. While it's possible to explicitly annotate types, which is useful for practicing type annotations, doing so can lead to higher maintenance costs when we want to change the type. Therefore, it's generally best to rely on TypeScript's type inference and only use type annotations when necessary.
VS Code offers an Inlay Hints functionality that displays the inferred return types, eliminating the need to constantly hover over function names to check their return types.
A case where we need explicit typings are recursive functions. Those require explicit return types to be annotated.
Let's take the calculation of the factorial of a number as an example. The factorial function multiplies all positive integers from our chosen number down to 1.
The factorial function takes an input number n
and recursively calls itself with n - 1
until it reaches the base case of n === 0
. At that point, the function returns 1
to terminate the recursion.
The key thing to note is that the function calls itself within its own body, which is what makes it recursive.
As you can see, without specifying an explicit return type annotation, we will see TypeScript error 7023 as long as noImplicitAny is activated in our compiler options. Let's fix this problem by adding a return type and rerun our code:
We can run the local TypeScript compiler using npx
. Once the compilation is complete, we can execute the generated JavaScript code using node
:
Best Practices
To conclude, here are several examples of where type annotations can be applied:
- To variables and constants
- To signatures of arrow function expressions
- To parameters and function return values
- To specify the context of the
this
keyword, which is particularly useful for callback functions
To minimize code maintenance, it is recommended to reduce the usage of type annotations and instead rely on TypeScript's type inference. Type annotations should be employed when the inferred types become overly broad or lead to implicit "any" typings.