Explicit types are a better habit than type inference

TypeScript has a handy type inference feature where the compiler will automatically determine types in most situations.

For example:

function fooBar(): number {
	return 42;
const x = fooBar();
// x's type is infered as number

This is quite useful and potentially allows for tidier, more readable code. However, I would actually suggest that avoiding type inference and preferring explicit types is a good habit.

For trivial examples like the above, it probably is overkill to declare x as const x: number = 42. Preferring explicit typing is a valuable habit in larger projects with more complexity. While human readers will lose track of long type chains, the compiler will happily keep infering them to the point where it’s difficult to debug and maintain.

In larger projects with various dependencies and complex types, it’s common to hit obscure type errors from the TypeScript compiler, which require tracing back nested types to try and figure out where the mismatch is. If it’s your habit to just explicitly declare types, you can reduce the incidence of this problem. Explicit types increase the chances that the compiler can isolate the mistake to the exact point where it happens, which makes it trivial to fix.

Habitually adding explicit types also lets you catch mistakes instantly as you write code due to the error highlighting in most editors, and the linting that should be applied quickly after new code is written.

You can even avoid some kinds of bugs at runtime with explicit typing. Truthy promises are one example of this. There are other situations where inferred types can be “correct” from the compiler’s perspective, but incorrect from a business logic perspective due to human error. Getting in the habit of adding explicit types can protect against some of these mistakes.

It’s a bit like routinely checking that you didn’t leave anything behind when leaving a taxi or a restaurant – you’re trying to get in the habit of confirming you didn’t make a simple bad assumption that will cause a problem later.

One other nice thing about having more explicit types in your code is that when reading the code, you can easily see the type of things that might not have been obvious to you otherwise. You can see the type of a function return at a glance, rather than having to read through its source code. This is especially true for libraries as it might not be easy to go and look at the source code for those.

A lot of editors have a jump-to-definition feature that lets you click on something in code and go straight to its source. This is made easier by having an explicit type to click through to the exact type that you’re interested in.

Finally, getting in the habit of adding explicit types can encourage adherence to the Interface Segregation Principle. This principle suggests that “no client should be forced to depend on methods it does not use”. In other words, a type should be limited to the exact use case, rather than dragging in the concerns of other clients. For example, a function that needs read access to some persistence storage would be better off with an argument of type FoobarReader than FoobarRepository, because the former is specific to the exact need at that point.

By adding more explicit types, you’re encouraged to notice interfaces and their habitats in code, and have more chance to identify where interfaces could be better segregated.

Tech mentioned