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
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.
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.
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.
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