TypeScript: Any vs Unknown vs Never | by Jose Granja | Oct, 2022

A deep dive into these three intriguing types

image by author

What is the difference between any, unknown, and never? When should we be using each one of those? What are its drawbacks and strengths?

TypeScript has recently turned ten years. To celebrate its anniversary, I wanted to explore and play with the intricacies of those types.

This article should help you understand and learn their internals. By having that crystal clear in your mind you will easily identify which one should you use in each situation.

Out of those three, the any is the one we tend to learn first. You can think of it as an escape hatch of the TypeScript type system. For example, when declaring a variable as any you can assign the value to any value to it. It is a compiler directive to disable TypeScript and shift the responsibility to the developer.

More often than not, it will create more issues than it will solve.

By default, when the type annotation is missing and can’t be inferred, the compiler will default to the any type.

This is considered a bad practice and has an easy fix. We can enable strict mode on to make the above code fail.

TypeScript strict mode will enable noImplicitAny, noImplicitThis, alwaysStrict, strictBindCallApply, strictNullChecks, strictFunctionTypes, and strictPropertyInitialization under the hood.

The noImplicitAny will raise an error when implicit usages of the any type. Instead of enabling strict you may choose to enable that one only.

The usage of explicit any will not be banned though. That can lead to some nasty errors.

Let’s see a dummy example:

Although we have declared the parameters a and b to be numbers we can execute the function with string by first declaring the variables as any. The error is hard to spot since you are effectively opting out of TypeScript type-safety on those variables.

How can we prevent that? By using some linting rules like typescript-eslint and enabling the no-explicit-any rule.

That rule can help you block those explicit usages. However, sometimes, there is no way around and using any might be the only solution. For those exceptions to the rule, you can just disable the lining rule for that line.

The unknown type is simple but sometimes the most tricky one to grasp. It is simply the parent of all types. Before jumping directly into it, let’s check a new feature on TypeScript 4.8.

When using the --strictNullChecks option on, the empty object {} is now a supertype of all types except null and undefined. Why is that important? It helps us understand the definition of unknown.

As {} is the supertype of all types except null or undefiend it is easy to create our custom unknown equivalent type.

How do we know the above works? Let’s create a mapped type to test if our defined type matches unknown indeed.

Why does it matter? Why is the above cool? Because starting from 4.8 we can now narrow down the unknown type without explicitly providing a type assertion. It will all be done by the control flow analysis.

Let’s check an example:

The usage of unknown is the recommended alternative to any. It lets us define wider types whilst keeping safe type checking.

One common usage of unknown is to be used as a bridge to bypass typings. The compiler is leveraging the typing responsibility of the developer. That means, it should be used with caution.

In summary, unknown is the preferred alternative to any and can be used to better express general types. It can be narrowed down by either control flow analysis or type assertion.

The never is a simple but at first quite confusing type. It is a type to express that nothing is assignable to it. It is a type that should never occur or be assigned to.

Let’s look at how the compiler uses this type to express itself better. Let’s say that we want to express a type as the intersection of string and number. We know that this is not possible since there is no possible intersection but we can syntactically express it.

Let’s see what happens when we try this:

We can see that the type of x becomes never as this scenario can never happen.

What if we try to return never from a function? We get an error.

In order for the above code to work, the method should never reach an end and there’s a way to make that happen — by throwing an exception.

So the never type is an awesome tool that we can use to express the same semantic meaning to our code.

Let’s create a method logger for fun that will accept anything but dates. Here’s the code:

In the above code, we have created a log function that will accept any type except Date. How was that achieved? By a mapped type that maps Date to never.

We have seen the intricacies and nuances of using all of those types. They are all key aspects of the language but with different use cases.

In a nutshell, any should be avoided as much as possible in favor of unknown. However, it is fine to use in some situations where unknown does not work for you.

The never type is useful to help us express type restrictions and identify type restrictions of the language.


News Credit

%d bloggers like this: