Recursive Types

Types that refer to themselves — the natural way to model trees, JSON, and nested data.

A type is allowed to reference itself, which is exactly what nested data needs. The canonical example is JSON:

type Json =
  | string | number | boolean | null
  | Json[]
  | { [key: string]: Json };

That handful of lines describes any valid JSON value to arbitrary depth. Trees follow the same shape — a node whose children are nodes of the same type.

Recursive type-level computation

Recursion also drives compile-time transforms like a deep readonly:

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K]
};

TypeScript allows deep recursion but has limits to keep the compiler responsive. If you hit "type instantiation is excessively deep," simplify the recursion or add a base case that stops it early.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • A JSON value type is defined recursively to handle arbitrarily nested arrays and objects returned from APIs.
  • A tree node type references itself to model a file system directory structure.
  • A linked list node type refers to itself via an optional next pointer to represent a chain of items.

More examples

Recursive JSON value type

Defines a recursive type that can represent any valid JSON structure, including nested objects and arrays.

Example · ts
type JsonValue =
  | string
  | number
  | boolean
  | null
  | JsonValue[]
  | { [key: string]: JsonValue };

const data: JsonValue = {
  name: 'Alice',
  scores: [98, 72],
  meta: { active: true },
};

Tree node type

A generic recursive interface where children is a TreeNode<T>[] so the same type applies at every level of the tree.

Example · ts
interface TreeNode<T> {
  value: T;
  children: TreeNode<T>[];
}

const tree: TreeNode<string> = {
  value: 'root',
  children: [
    { value: 'child1', children: [] },
    { value: 'child2', children: [
      { value: 'grandchild', children: [] },
    ] },
  ],
};

Recursive DeepReadonly type

A recursive mapped type that applies readonly at every nesting level, unlike the built-in Readonly<T> which is shallow.

Example · ts
type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface Config {
  server: { host: string; port: number };
  db: { url: string };
}

const cfg: DeepReadonly<Config> = {
  server: { host: 'localhost', port: 3000 },
  db: { url: 'postgres://...' },
};
// cfg.server.host = 'x'; // Error

Discussion

  • Be the first to comment on this lesson.
Recursive Types — TypeScript | SoundsCode