The unknown Type in TypeScript
Tags: typescript
Published: 2021-04-21 00:00:00

---
title: The unknown Type in TypeScript
date: "2021-04-21T00:00:00.000Z"
tags: ["TypeScript"]
---

Introduced in [TypeScript 3.0](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type), the `unknown` type exists to provide a type-safe counterpart to the `any` type. To understand why this was considered a necessary or desirable addition to TypeScript, we need to revisit `any` and why its use can be problematic. 

## The `any` type
The `any` type has been in TypeScript since its initial release and represents all possible [JavaScript values](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures) (making it a [top type](https://en.wikipedia.org/wiki/Top_type) of the type system).

`any` can be problematic because of its flexibility - it allows typing to be circumvented:

~~~typescript
// TypeScript

let value: any;

// Any value can be assigned to an 'any' type:
value = true;
value = 0;
value = "Hello world";
value = [];
value = {};
value = null;
value = undefined;
value = Date.now;
value = new Error();
value = Symbol();
// ...

// No check on use:
value * value;
value[1];
value.foo.bar.baz();
new value();
// ...
~~~

While convenient, `any` undoubtedly increases the risk of errors or unexpected behaviour sneaking into code simply by negating one of the primary benefits of using TypeScript in the first instance - strong typing.

## The `unknown` type
The `unknown` type is less permissive than `any`. While any value is assignable an `unknown` can only be assigned to itself (or to`any`).

Revisiting the previous example:

~~~typescript
// TypeScript

let value: unknown;

// Any value can be assigned to an 'unknown' type:
value = true;
value = 0;
value = "Hello world";
value = [];
value = {};
value = null;
value = undefined;
value = Date.now;
value = new Error();
value = Symbol();
// ...

// An 'unknown' type can be assigned to 'any' or 'unknown':
const toAny: any = value;
const toUnknown: unknown = value;

// Assignments to other types will fail:
const toBoolean: boolean = value;
const toString: string = value;
// ...
~~~

### Narrowing and asserting
Operations cannot be performed on an `unknown` type without first [narrowing](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) to a more specific type or asserting:

~~~typescript
// TypeScript

let value: unknown;

// Operations including the following will error:
value[1];
value.foo.bar.baz();
new value();
// ...
~~~

One method of narrowing is with the `typeof` operator:

~~~typescript
// TypeScript

const value: unknown = 'hello world';

if (typeof value === 'string') {
	// Narrowed to a string type, can safely call 'toUpperCase()'
	console.log(value.toUpperCase());
}
~~~

Types can be asserted using the `as` operator:

~~~typescript
// TypeScript

const value1: unknown = 'hello world';
const value2: unknown = null;

console.log((value1 as string).toUpperCase()); // Ok
console.log((value2 as string).toUpperCase()); // Error
~~~

🔔 Type assertions should be used with caution as they may result in expected run-time issues.

### Unions and intersections
In a [union](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types), an `unknown` absorbs everything else (the exception to this rule is a union with `any`).

With an [intersection](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types), the converse applies - that is, other types will subsume the `unknown`.

~~~typescript
// TypeScript

// Unions:
type union1 = unknown | null;
type union2 = unknown | undefined;
type union3 = unknown | boolean;
type union4 = unknown | number;
type union5 = unknown | string;
type union6 = unknown | null | undefined | boolean | number | string;
// ...
type unionN = unknown | any; // any

// Intersections:
type intersection1 = unknown & null;
type intersection2 = unknown & undefined;
type intersection3 = unknown & boolean;
type intersection4 = unknown & number;
type intersection5 = unknown & string;
type intersection6 = unknown & null & string; // never
// ...
type intersectionN = unknown & any; // any
~~~

## When to use `unknown`
Wherever possible specify an explicit type and avoid using `any` or `unknown`. Explicitly specifying types allows you to take advantage of TypeScript's compile-time checking, provides a more explicit contract (something especially important for library writers to consider), and should result in fewer run-time issues.

Where this is not possible, prefer `unknown` over `any` as its characteristics require more deliberate and thoughtful use.