Why avoid the Any Type in Typescript
We do a deep dive on any in Typescript, why it exists, why you should avoid using it and remedies and alternatives to using any within your codebase
So, in the last few issues, we have covered avoiding type assertions and why you should prefer narrower types in Typescript. In this issue, I wanted to take a closer look at the any
type and why you should avoid it, alternatives to the any
type and when it’s okay to use the any
type (spoiler ‼️ - not many situations).
The any
Type in Typescript
One of the most controversial types in Typescript is any, this is because it could be anything, and can be assigned to all variable types and assigned to all variable types, and as such Typescript doesn’t do any type checking when the any
type is involved. Yes, that’s how powerful the any
type is in Typescript.
As you can imagine, for a statically typed language that aims to provide type safety, this is a misnomer, an anti-pattern and any (pun intended☺) type nerds worth their salt in the Typescript will frown at you for using any.
So I guess the question now is, why does it even exist? In order to understand why any exists, you need to understand the Typescript type system. If you are trying to master Typescript, one of the very first mental models you need to form is that a Type represents a possible set of values. What do I mean, if the type is a number, then that represents only numbers, and boolean, just true and false.
This means that when a variable has a type, it can only be assigned values within the set, and errors if it's assigned variables outside the set. When it comes to the any
type, this is an open set, meaning any possible value from string, numbers, boolean, etc. forms the set of the any
type. Basically, it means all possible values in existence can be assigned to a variable of the any
type.
This made sense for Typescript originally, if Typescript was going to experience widespread adoption from Javascript codebases, it needed to support a way of doing gradual migrations. Hence, you could use the any
type, both implicitly and explicitly, for any parts of our codebase that we weren't ready to adopt Typescript fully yet. We still do this, even today, when migrating to Typescript from a Javascript codebase.
So, why avoid any
then?
If you have learned anything so far, the any
type is very powerful. It has a few superpowers that can easily be abused.
First, the any
type can be assigned to any variable of all other types → string, number, object, etc, it doesn’t matter. And the vice-versa is also true, any value can be assigned to a variable with any
type.
This can cause unintended bugs within our system because any could be anything, the Typescript type checker won’t type check the code when any is involved. You could end up in a situation where you expected a number for customer balance calculation, and instead got something completely different, at the very least providing an unreliable experience to users of the system or could be worse.
This is something we Javascript/Typescript developers need to be keenly aware of, as the any
type also has another superpower, disabling the Type checker. Typescript has no way of Type checking a variable of any type and developers mostly use the any
type to disable Type checking in Typescript applications.
Unlike all other types in Typescript and by extension Javascript (yes, Javascript has types too, just not static types → I have an article in the draft that I should finish about this), any
is very contagious, and if you assign a variable to the any
in place of any other variable or vice versa, Typescript will not warn you, while in case you pass a string in place of a number, Typescript will not let you.
My point is to avoid using any
unless it’s absolutely necessary or type checking is not really necessary, which in most cases is. As developers, we are paid big bucks to make these decisions, but just like tests, types are not only there to prevent regressions within our codebase, but also help improve the developer experience, as we can refactor and make changes within our codebase with a lot more confidence, as compared to when types don’t exist.
What should I do now?
Let’s say you buy into my argument (hooray 🥳), what should you do? There are two main types of any within your codebase, the explicit any and the implicit any. I don’t want to dive too much into the explicit any because those are any types that we as developers (or our teams) have introduced knowingly within our codebase. The solution is to provide proper types, check out the next section if you can’t, you might find better solutions than just using any. If you are struggling with this in your codebase, consider tracking Type coverage.
The second one is implicit, this could come from us not providing types and Typescript doesn’t have enough information to infer the type of a variable and hence implicit any, for instance, the following function:
functions doSomething(var) {
// ^ implicit any
}
You can remedy the above issue by setting typescript to throw an error instead of implicitly inferring a variable type as any. This can be achieved by updating the tsconfig
settings and setting noImplicitAny
to true
. You can also update the useUnknownInCatchVariable
property to true while at it so that errors are inferred as any instead of any in try-catch blocks.
The second possible source of this is third-party libraries that ship without Type declarations or ship incomplete or inaccurate Type declarations. NPM packages ship as Javascript, not Typescript, they have to be usable in the whole ecosystem, including Javascript-only projects. To support Typescript, libraries can ship with Typescript declaration files, which provide Type information about a library that Typescript can use for type checking.
You need to be aware of this and can remedy this by installing types from Definitely Typed if they exist or you provide the type declaration yourself. For instance, if you using React, which doesn’t ship with type declaration, you can do the following:
npm i --save-dev @types/react
And just like that, you will Typescript and react to play nice. I will cover providing your own type declarations in a future issue, let me know if you are interested below.
Also, as I previously touched on at the beginning of this section, please consider tracking Type coverage to aid in improving type safety in your codebase and be aware of where you are trending. You can learn more here in one of my previous issues.
What if I can’t avoid any?
There are situations where you can’t avoid the any
type, maybe because you don’t have enough information to define the type or it’s not important because you are not consuming the data within your application, think of a log dump application or a proxy server. In both cases, you don’t care much about the content shape, as long as save the data being sent to you properly or redirect the requests to the correct server and responses back to the client.
In this case, I would advise you to consider preferring the unknown
type. The unknown type is another powerful type in Typescript, and just like any, all other types can be assigned to the unknown
type. However, this is how it differs from the any
type, it can only be assigned to another unknown
type or any
type.
This means that before using unknown, you will need to resolve the underlying type either using assertions (please avoid) or Type narrowing. This makes the unknown
type is not contagious as it can’t spread within the codebase just like the any
type.
If you can’t avoid the any
type, I would encourage you to use a more narrower version of the any
type. For instance, any[]
or Record<string, any>
is a lot better than just any. In both cases, we are containing the superpowers of the any
type but limiting what types can be assigned to the two types, in the first case, we are saying it has to be an array, the content could be anything, and in the second case we are indicating it has to be an object, with the string keys, but the values could be of any type.
Is this the best way? Definitely not, but in my eyes and in some special circumstances it’s better than than just using any.
Conclusion
That’s it, we have reached the conclusion of this article, where we discussed about the any
type, why it exists, and why you should avoid it. We learned that any exists to make it easy to turn off Typescript hence aiding in the gradual adoption of Typescript, which played a hand in helping in the adoption of Typescript. We also learned that any can be contagious and spread within our codebase, which can lead to bugs.
We also learned about the unknown
type which is a good alternative to any when we can’t avoid it and how we can instead prefer a narrower version of the any
Type instead of just using any when we don’t have a choice.