TypeScript ANY vs UNKNOWN—A Deep Dive
Tags: typescript
Published: 2021-11-14 21:23:30

In this blog post, we'll dive deep into what `any` and `unknown` types are, what are their similarities and differences, and when (not) to use them.

(You can find a video version of this article on YouTube! 📺)

{% youtube O6UUEeq0oSg  %}

# TLDR;
```typescript
// ANY
const anyValue: any = 'whatever'

// OK in TypeScript (error in runtime!) 👇
anyValue.do.something.stupid() 


// UNKNOWN
const unknownValue: unknown = 'whatever, too'

// Fails TypeScript check (prevents runtime error) 👇
unknownValue.do.something.stupid() 
```

Mnemonics to help you remember the difference 👇
- **`any`** -> first letter is an "A" -> *Avoid* TypeScript
- **`unknown`** -> first letter is a "U" -> *Use* TypeScript

<br />
<br />
# Table of Contents
1. [The `any` Type](#chapter-1)
2. [The `unknown` Type](#chapter-2)
3. [Use Cases](#chapter-3)

<a name="chapter-1"></a>
# The `any` Type
The `any` type is something like an escape hatch from TypeScript. 

What does that mean?

If your variable is of type `any`, you can:
**1. Assign whatever you want to it 👇**
```TypeScript
let anyValue: any

anyValue = 'hi, how is you? :)'
anyValue = { login: () => { alert('password?') } }
```

**2. "Do" whatever you want with it 👇**
```TypeScript
let anyValue: any = false

// OK in TypeScript but error in runtime
// ("...is not a function")
anyValue.toUpperCase()

// OK in TypeScript but error in runtime
// ("...Cannot read properties of undefined")
anyValue.messages.hey(':)')
```

Generally speaking, using `any` allows you to use variables *without* type checking.

![soldier being shot by an arrow saying "any"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sndm73objhbmpssml6gx.jpg)
(https://devrant.com/rants/3015646/i-dont-usually-post-memes-but-this-one-is-just-a-little-too-on-the-nose)

This means that you lose the main benefit TypeScript has to offer–preventing runtime errors due to accessing non-existing properties.

You might now wonder **why the heck would I even use `any`** if it means giving up type checking altogether?

Generally speaking, you should strive to avoid it. To do that, I'd advise you to:
**1) Use `"strict": true` in your `tsconfig.json` file to disable *implicit* `any` types**
> *Implicit any* means that if you don't annotate a variable or a function parameter in TypeScript, it'll be `any` by default. With `"strict": true`, the TypeScript compiler will throw an error if you've got an unannotated variable of type `any`.

**2) Use the [`no-explicit-any`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md) rule in [TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint).** This will give you an ESLint warning whenever you use `any`.

However, there are some situations where `any` is helpful. We'll cover the main use cases in [the final section](#chapter-3) in depth. Nonetheless, it can be useful when migrating JavaScript code to TypeScript or when dealing with untyped external libraries.

**Careful! Some built-in TypeScript types use `any`**
When using functions like `JSON.parse(...)` or `fetch(...).then(res => res.json())`, the type of the result is `any` by default.

You can use something like `JSON.parse(...) as { message: string }` to give it a proper type. Nonetheless, it's useful to know about these as it's very easy to accidentally use `any` without even knowing about it.
> You can use rules such as [`no-unsafe-assingment`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md) in [TypeScript ESLint](https://github.com/typescript-eslint/typescript-eslint) to get warnings in such scenarios.

Why does TypeScript behave in this *not* type-safe manner? Well, one of the possible explanations is that there was no other way to type these built-in JavaScript functions as there was no `unknown` type that would be better suited for this job. Let's have a look at what it does and how it differs from the `any` type.

<a name="chapter-2"></a>
# The `unknown` Type
The `unknown` type was added to TypeScript in 2018 with its version `3.0` release. Its purpose was to provide a type-safe alternative to `any`.
> From the official docs:
TypeScript 3.0 introduces a new top type `unknown`. `unknown` is the type-safe counterpart of `any`. (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type)

**What does that mean?**
As the phrase "type-safe counterpart of `any`" suggests, the `unknown` type is similar to the `any` type in some way but different in others (namely, unlike `any`, it's type-safe).

Let's first examine the similarities.

**You *can* assign whatever you want to an `unknown` type**
This works pretty much the same way as the `any` type 👇
```TypeScript
let unknownValue: unknown

unknownValue = 'hey, how\'re you doing? :)'
unknownValue = { signUp: () => { alert('email?') } }
```

With variables of type `any` you could do anything with such variables (call them, access random properties on them, etc.). This is *not* the case with `unknown`.

With `unknown`, TypeScript **makes *no* assumptions about your variables**.
```TypeScript
let unknownVariable: unknown

// Error in TypeScript 👇
unknownVariable.toUpperCase()

// Error in TypeScript 👇
unknownVariable.how.are.you('today?')

// Error in TypeScript 👇
const newVariable = unknownVariable + 10
```

This is very helpful as it prevents you from accidentally accessing non-existent properties, treating strings like functions etc.

You can *think* of the `unknown` type as of something like
```TypeScript
type unknown = string | number | boolean | object | null | ...
```
> Disclaimer: this is *not* the actual definition of the `unknown` type. It's just a simplified model to give you a better intuition.

## Type narrowing with `unknown`
As we saw in the code snippet above, TypeScript doesn't allow you to do almost anything with the `unknown` type. 

On one hand, it's very useful as it keeps your code type-safe and prevents you from the dreaded runtime JavaScript TypeErrors.

On the other hand, it's quite limiting as we'd like to be able to manipulate our variables, call properties on them, etc.

We've got 2 options for how to do that.

**1) Type Guards**
We can use type guards to narrow down the possible types. You can use `if` statements or custom [type predicates](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#using-type-predicates) to do that.
```TypeScript
function logSecretMessage(message: unknown) {
  if (typeof message === 'string') {
    // in this if-block we know `message` is of type string
    // so we can call the `toLowerCase()` method on it
    console.log(message.toLowerCase())
  } else {
    console.log(message)
  }
}
```

**2) Type Assertions (*not* type-safe)**
Alternatively, we can always use type assertions. This is way easier but we lose the type-safety as we use whatever type we want and TypeScript will just "trust" us that we made no mistake:
```TypeScript
const unknownVariable: unknown = 'hello';

// OK 👇
(unknownVariable as string).toUpperCase();

// OK in TypeScript but it *fails* in runtime 👇
(unknownVariable as number).toFixed(2)
```

<a name="chapter-3"></a>
# Use Cases for `any` and `unknown` Types
Now that we've got understanding of what the `any` and `unknown` types mean, let's have a look at when (not) to use each of them.

The rule of thumb is that `any` should be avoided since using it makes you lose most of the TypeScript benefits. If you don't know what type a certain variable or a function parameter is, always prefer `unknown`.

With that being said, there are some valid use cases for `any`.

## Migrating to TypeScript (from JavaScript)
`any` is very useful when migrating JavaScript codebase into TypeScript. 

Let's say you've got a large JavaScript file which exports many functions and you want to convert it to TypeScript. Without using `any`, you'd need to type every single function in this file.

That's a lot of work and you might just be interested in typing one of the exported functions.

In this case, you can use `any` to quickly type the functions you're not interested in and only give proper types to the one function you're currently working with.

It might be also useful to create an alias for `any` (such as `type TODO = any`) so that you can later come back to your temporarily typed functions and give them proper types.
```TypeScript
// auth.ts (migrating from auth.js)
type TODO = any

// We can just type the `signUp` function
export function signUp(options: {
  email: string
  password: string
}) { /* ... */ }

// Use `TODO` to quickly type the `resetPassword` function
export function resetPassword(options: TODO) { /* ... */ }

// Use `TODO` to quickly type the `logIn` function
export function logIn(options: TODO) { /* ... */ }
```

## Functions with unknown arguments
As previously stated, the `unknown` type should be preferred when dealing with variables which types we can't determine.

An example could be a generic logger function 👇
```TypeScript
function logger(message: unknown) {
  if (development) {
    console.log(message)
  } else {
    sendMessageToAPI(message)
  }
}
```

Had we used `any` instead, we could have accidentally tried to use properties such as `.toLowerCase()` (wrongly) assuming that `message` is of type `string`.

Thus, using `unknown` instead keeps things safe for us 😇