---
title: 'any, unknown, and never'
source: 'https://academia.sh/en/courses/typescript/any-unknown-never'
course: TypeScript
language: en
updated: '2026-08-17T18:09:54+00:00'
license: 'CC BY-SA 4.0'
---

# any, unknown, and never

The assignability rules and use sites of any, which turns off checking, unknown, which forces it, and never, which has no values.

The previous lesson wrote the measurement record's first typed version, but the record
was still a hand-written object. Real data comes from a file or the network and comes
out of a `JSON.parse` call. What type is that call's return value?

The answer introduces the type system's two endpoints. The `any` type says "this could
be anything, do not check"; the `unknown` type says "this could be anything, so check
first." The difference between the two is one of the most important decisions for a
record's safety. At the end of the lesson comes a third endpoint: the `never` type,
which has no values at all.

## `any`: Turning Off Checking

`any` tells the type checker to assert nothing about a value:

```typescript
const raw: any = JSON.parse('{"id":"s-01","value":"23.0"}');

console.log(raw.value.toFixed(1));
console.log(raw.missing.field);
const num: number = raw;
```

This file **passes type checking without error**. All three lines are wrong: the
`value` field is a string, there is no field called `missing`, and `raw` is not a
number. The compiler reports none of them, because it has no assertion about which
fields an `any` value has.

`any` behaves in both directions: a value of type `any` can be assigned to any type,
and any type can be assigned to `any`. This takes the type out of the checking
network. Once a field is `any`, every expression derived from it also stays unchecked
— the lack of checking spreads.

The declared return type of the `JSON.parse` function is `any`. The reason is clear:
the content of the string is only known at runtime, the compiler has nothing to say.
The result resembles what was seen in the first lesson — type checking falls silent,
the error is deferred to runtime.

The compiler can also refuse to infer `any` when no type is written:

```typescript
function average(measurements) {
  return measurements.length;
}
console.log(average([1, 2, 3]));
```

```text
c6.ts(1,18): error TS7006: Parameter 'measurements' implicitly has an 'any' type.
```

This diagnostic comes from the `noImplicitAny` option and is part of the strict
configuration. The distinction has to be seen: **implicit** `any` is an omission, the
compiler warns; **explicit** `any` is a decision, the compiler stays silent. Searching
a codebase for `any` notations means searching for where checking was turned off.

## `unknown`: Deferring Checking

`unknown` covers the same value set as `any` — every value can be assigned to type
`unknown` — but the reverse direction is closed:

```typescript
const raw: unknown = JSON.parse('{"id":"s-01","value":"23.0"}');

console.log(raw.value);
const num: number = raw;
```

```text
c2.ts(3,13): error TS18046: 'raw' is of type 'unknown'.
c2.ts(4,7): error TS2322: Type 'unknown' is not assignable to type 'number'.
```

**Nothing can be done** with a value of type `unknown`: its fields cannot be
accessed, it cannot be called, it cannot be assigned to another type. The only thing
that can be done is narrowing its type.

The relationship between the two can be described with an ordering. In the direction
of assignability, `unknown` sits at the top: every type is assignable to it, it is
assignable to none. This is why it is called the **top type**. `any` sits outside
this ordering; assignable in both directions, and precisely for that reason it
carries no guarantee.

Practical rule: **`unknown` is used at the system boundary.** External data is
received as `unknown`, checked, and only enters the model after that check.

## Validation at the Boundary

Reading the measurement record from a file is an application of this rule:

```typescript
type Measurement = { id: string; value: number };

function read(raw: string): Measurement | null {
  const parsed: unknown = JSON.parse(raw);
  if (
    typeof parsed === "object" &&
    parsed !== null &&
    "id" in parsed &&
    typeof parsed.id === "string" &&
    "value" in parsed &&
    typeof parsed.value === "number"
  ) {
    return { id: parsed.id, value: parsed.value };
  }
  return null;
}

console.log(read('{"id":"s-01","value":21.4}'));
console.log(read('{"id":"s-01","value":"23.0"}'));
console.log(read('42'));
```

Output:

```text
{ id: 's-01', value: 21.4 }
null
null
```

Every part of the condition does one step of narrowing. After `typeof parsed ===
"object"`, the compiler knows the value is an object; `parsed !== null` eliminates the
possibility of `null`; `"id" in parsed` proves that field exists; the `typeof` check
determines the field's type. By the time the body of the condition is entered, the
compiler has **proven** that `parsed.id` is a `string` — not on the programmer's word,
but based on the checks in the code.

This is the course's axis: the type system is a layer of proof, and checks are the
steps of the proof. The rules of narrowing and less repetitive ways to write it are
covered in the Type Narrowing lesson.

The same job could also have been done by writing `as Measurement`, and it would have
misbehaved as seen in the first lesson. The difference is this: with `as`, the
guarantee comes from the programmer's assertion; in the code above, it comes from
checks that actually run.

## `never`: The Empty Type

`never` is the type with no values. It is the empty set. It shows up in two places.

First, as the return type of functions that never return by the normal path:

```typescript
function halt(message: string): never {
  throw new Error(message);
}

const anything: string = halt("record could not be read");
const nothing: never = "s-01";
console.log(anything, nothing);
```

```text
c5.ts(6,7): error TS2322: Type '"s-01"' is not assignable to type 'never'.
```

The fifth line does not error, the sixth does. The reason is `never`'s place in the
assignability ordering: `never` can be assigned to every type, no type can be assigned
to `never` — much like the empty set being a subset of every set. This is why `never`
is called the **bottom type**. Because the `halt` function returns no value at all,
claiming that the value it returns has to be `string` is a vacuous claim, and the
compiler allows it.

The second appearance is more useful: **exhaustiveness checking**. Let the measurement
record's states be defined with a union:

```typescript
type Status = "valid" | "suspect" | "invalid";

function label(status: Status): string {
  switch (status) {
    case "valid":
      return "measurement valid";
    case "suspect":
      return "measurement suspect";
    default: {
      const unhandled: never = status;
      return unhandled;
    }
  }
}

console.log(label("valid"));
```

```text
c4.ts(10,13): error TS2322: Type '"invalid"' is not assignable to type 'never'.
```

The diagnostic names the unhandled case directly. The mechanism is this: after the two
`case` branches, the compiler knows only `"invalid"` can reach the `default` branch.
Attempting to assign to `never` tests whether the remaining set is empty. If the set is
not empty, a diagnostic appears, and the value inside it is written into the
diagnostic's text.

This pattern's value shows up when a new case is added to the union. If a fourth value
is added to the `Status` type, **every** `switch` block that does not handle that value
errors at compile time. The type system becomes a maintenance tool here: changing the
model forces a review of all code that uses it.

## Where the Three Types Sit

The relationship among the three can be gathered into a single table:

| Type | What can be assigned to it | Where an assignment from it can go | Where used |
|---|---|---|---|
| `any` | Every type | Every type | Temporary, during gradual migration |
| `unknown` | Every type | Nowhere (narrowing required) | System boundary |
| `never` | Nowhere | Every type | Exhaustiveness checking, non-returning functions |

The table's first row shows why `any` is not a type but a gate: it is the only row
with both columns filled, and that means it imposes no constraint at all. `unknown`
and `never` are each other's opposite — one covers everything and allows nothing, the
other covers nothing and goes everywhere.

`any` is not banned outright. As will be seen in the JavaScript Interop lesson, it is
temporarily necessary during gradual migration of a codebase with no type
information. The rule is: `any` is a **transition** marker, not a **solution**; where
it remains is documented and narrowed.

## Summary

- `any` turns off type checking; being assignable in both directions, it carries no
  guarantee and spreads the lack of checking.
- Implicit `any` is an omission and is reported by the `noImplicitAny` option; explicit
  `any` is a decision.
- `unknown` is the top type: every value is assignable to it, it is assignable to no
  type; it has to be narrowed before use.
- Data from a system boundary is received as `unknown` and enters the model only after
  being checked field by field; the `as` notation does not substitute for this check.
- `never` is the bottom type: it has no values, it is assignable to every type; it is
  used for exhaustiveness checking in `switch` blocks.

## Next Step

Up to here, types were written by hand. Yet in most of the examples above, the
compiler already knew types that were never written: the narrowed types inside `read`,
the object literal's field types, the type of array elements. The next lesson covers
where this knowledge comes from — the rules and limits of type inference.
