DEV Community

derek lawless
derek lawless

Posted on • Originally published at dereklawless.ie

The unknown Type in TypeScript

Introduced in TypeScript 3.0, 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 (making it a top type of the type system).

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

// 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();
// ...
~~~{% endraw %}

While convenient, {% raw %}`any`{% endraw %} 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 {% raw %}`unknown`{% endraw %} type
The {% raw %}`unknown`{% endraw %} type is less permissive than {% raw %}`any`{% endraw %}. While any value is assignable an {% raw %}`unknown`{% endraw %} can only be assigned to itself (or to{% raw %}`any`{% endraw %}).

Revisiting the previous example:{% raw %}

~~~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;
// ...
~~~{% endraw %}

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

~~~typescript
// TypeScript

let value: unknown;

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

One method of narrowing is with the {% raw %}`typeof`{% endraw %} operator:{% raw %}

~~~typescript
// TypeScript

const value: unknown = 'hello world';

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

Types can be asserted using the {% raw %}`as`{% endraw %} operator:{% raw %}

~~~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
~~~{% endraw %}

🔔 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 {% raw %}`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
~~~{% endraw %}

## When to use {% raw %}`unknown`{% endraw %}
Wherever possible specify an explicit type and avoid using {% raw %}`any`{% endraw %} or {% raw %}`unknown`{% endraw %}. 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 {% raw %}`unknown` over `any` as its characteristics require more deliberate and thoughtful use.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)