When to add types and when to infer in TypeScript
Type inference is the ability to derive types from other pieces of code. TypeScript's type inference is very powerful, even a minimal amount of typing adds a lot of assertions.
Just because you don't need to add types, doesn't mean you shouldn't. This is how I decide when or when not to explicitly add types in TypeScript.
In this first example, the least amount of types possible were added: the parameters.
function divide(a: number, b: number) { return a / b;} const result = divide(10, 2);
TypeScript infers that result
is a number despite not adding a return type to divide
, or a type declaration to the result
variable.
TypeScript knows that a / b
will return a number, so divide
returns a number. TypeScript knows that result
has the same type of whatever divide
returns, which is a number.
A more explicit version would have a lot more number
.
function divide(a: number, b: number): number { return a / b;} const result: number = divide(10, 2);
When do I add types, and when do I infer? I follow one simple rule: Add types to all function declarations.
My version of the snippet lands somewhere between the two.
function divide(a: number, b: number): number { return a / b;} const result = divide(10, 2);
I type function declarations for two reasons: readability and contract.
Readability: by typing the declaration, I know exactly what to expect of the function without looking at its body.
While we could assume this will return a number, we can't be sure. And assumptions are exactly what we're trying to avoid by using types.
function divide(a: number, b: number)
Contract: by typing the declaration, I'll catch unexpected types in the function body sooner.
This would implicitly set the return type as void | number
.
function divide(a: number, b: number) { if (b === 0) { return; } return a / b;}
Instead, I want to ensure a number is returned.
function divide(a: number, b: number): number { if (b === 0) { throw new Error("Can't divide by 0"); } return a / b;}
While unexpected types like the above are easy to spot in small examples, they can be very subtle in large function bodies.
These rules might look arbitrary: why not add explicit types to variables?
In a way that's true, but it's important to maintain balance. We build software that changes over time. And the tighter you fasten the screws, the harder it is to loosen them.