Type inference in TypeScript allows it to figure out what is the type, without explicitly declaring it.
let mutableVersion = 1 // Type is number
const immutableVersion = 1 // Type is 1
You can try it here
TypeScript is not only smart enough to figure out that 1
is a number (duh) but also to understand that const
declaration for a primitive can't be reassigned (it's a value, not a reference) - hence it will stay 1
forever, contrary to let
which can change.
TypeScript tries to do it's best and usually it works quite well. Together with satisfies
and as const
statements we can write type-safe code barely declaring anything.
However, there are code-architecture downsides to relying too heavily on type inference, which can eventually make maintenance difficult.
Code first or design first
From my experience, most developers tend to write code and figure out the design as the outcome. "Something" eventually works, a few tests are added on top and we are done.
This approach may be more satisfying but is more "artistic" than an "engineering" approach. The implementation of the logic itself is not too important, if:
- It works, which tests proof
- Is performant enough to match our metrics
- Is encapsulated, so it doesn't leak where it doesn't belong to
If all these 3 are met, we can always easily replace the implementation without affecting the rest of the program.
In the context of the design, encapsulation is critical. It draws the boundary of the abstractions (function, classes, modules) and allows us to design how they coexist and communicate.
At this point, you may be thinking - what does it have in common with inference?
Interface vs inference
By allowing language to infer the type, we accept it to follow a "code first" approach.
const wrapCollection = (collection: Array<{id: string; name: string}>) => collection.reduce((acc, next) => {
acc[next.id] = next.string
}, {})
What does it return? Apart from the cognitive load (you force someone to read and understand implementation to understand what is returned), you just let TypeScript figure it out. And this is a simple function, for sure you have seen multiple-layered map/filter/reduce monster, best if placed in some React component, to make testing even harder 🥲
You can also be a good colleague and do this.
type CollectionItem = {id: string; name: string}
type WrapCollection = (collection: Array<CollectionItem>): Record<string, string>
const wrapCollection: WrapCollection = ...
What has changed? You started with declaring the data shape and data flow (in and out), then started to implement. Even empty, not implemented functions will be ready to import and write tests at an early stage, where you can validate the API.
Relying on inference is like writing only half of the interface.
Impact on the maintenance
Using inference not only makes it difficult to design a good code but also makes it harder to maintain.
Our wrapCollection
in a few months can be used by many engineers in many places. They will rely on inferred type... Then you need to refactor.
Say, you want to change it to for
instead of reduce
because you operate on a large amount of data and need to improve performance.
You change the inner implementation, but there is no Typescript boundary preventing you from changing the outer shape. Yes, the rest of the code hopefully will be "red" and your tests will fail. But there are many codebases, including ones with not full TypeScript coverage and missing tests.
But every time, your function defines its outer shape (doesn't have to be an interface, can be just a static declaration of the returned type), you are locally protected from breaking that contract.
Summary
I'm not declaring types literally everywhere, but I believe strong type coverage makes code more maintainable.
The more complex the function is, the higher the ROI is. Simple, especially private functions are not that important if we know that only one caller exists. But public methods, widely used across the codebase, benefit from being typed.
You can use ESLint rule to require explicit function return type as well.
Top comments (0)