Posted by Jonathan Wilkins on February 27, 2020 | typescript

It can be tempting when using TypeScript to use any when you are unsure what type a variable is. This article explains why this could be a bad idea and presents some ways to avoid using it.

What is any?

Why should I care?

Using any can lead to runtime bugs that would’ve been simple to stop at compile time.

Everything may look good and your code will “compile” but the use of any will shift the problems from compile time to runtime, and more importantly, will shift the problem from us as a developer to the end user i.e. our customers.

It is better to spend an extra few minutes defining the type for a variable than the support team spending a day cleaning up data because a request failed half-way through because it tried to access a non-existent property on an object in JavaScript.

Take the example below:

type SomeFunctionInput = { test: string };

const someFunction = (input: SomeFunctionInput) => {
    // problem: someFunction accepts the input
    // which we've told it is an object that has the property test...
    // So... I know my input must have the 'test' property... right?
    console.log("let's try calling input.test", input.test);
};

const myFunction = (input: any) => {
    // problem: we can just reassign input to any type we want...
    input = undefined;

    // second problem: someFunction accepts undefined as an input
    // since it's technically `any`
    someFunction(input);
};

console.log("this should log 'hello'", myFunction({ test: "hello" }));

What actually happens here is a runtime error which the user will see and that will halt execution in the browser - specifically “Cannot read property ‘test’ of undefined” - not a compile time error, which is the whole point of using TypeScript.

Using TypeScript correctly could have prevented this. First of all if we assign input to a real type in the myFunction declaration then we get a compiler error when we try and assign undefined to the variable “input”.

Let’s say we changed it to

(input: object) (which is still pretty bad - as it’s not very explicit)

but, our error would now be:

[typescript] Type 'undefined' is not assignable to type 'object'

Even if we used a type that can be assigned undefined, e.g. unknown (more on this type later), we still get an error when we try and pass this to someFunction:

[typescript] Argument of type 'unknown' is not assignable to parameter of type 'SomeFunctionInput'.

Sure, this is a contrived and trivial example, but it highlights the problems that using any liberally may introduce to your code.

It would be more beneficial to pay the upfront cost of being strict with typing when writing the code initially, to eliminate a whole class of bugs that a language like JavaScript may allow us to introduce.

What can we do about it?

We can take care when writing code and spend a bit of time trying to figure out what type something really is, rather than defaulting it to any. Otherwise use unknown in place of where you would have normally used any.

Tips to help avoid using any in your code:

Use unknown for values that can be anything

Example:

const print = (input: unknown): string => {
    if (typeof x === "string") {
        return `"${x}"`
    }
    if (typeof x === "number") {
        return String(x)
    }
// ...

Use Record when you’re working with something like to a dictionary:

Example:

const dict: Record<string, number> = {};
dict.a = 1;
dict.b = "a"; // TypeScript error: Type 'a' is not assignable to type 'number'.

// Or maybe we're not sure what our values will be?

const dict: Record<string, unknown> = {};
dict.a = 1;
dict.b = "a"; // Works fine! Note that we still didn't have to use `any`

Conclusion

To conclude, any can be used when you want to start gradually adopting TypeScript in a really complicated project, or if you’re accepting constantly changing third-party data - but think carefully before using it in your project.

If you are unsure what type a certain variable is, try using unknown and fall back to runtime type assertions before doing anything with the variable.

Using proper types will enforce compile time checks for your code, ensuring that the errors that are often prevalent in JavaScript won’t happen at runtime and affect the end-user.

It also integrates nicely with every IDE and allows for much better code completion since the IDE knows the type of every variable you’re working with.

If you’re struggling to generate types for certain objects in your code, create an instance of the object, log it, and use something like quicktype.io to generate the TypeScript for you! (There’s also a VSCode plugin )