Difficulty: Advanced
What are recursive types in TypeScript? Show practical examples of self-referencing type definitions.
Recursive types are types that reference themselves in their definition. They model data structures that are inherently recursive: trees, nested objects, linked lists, and JSON.
TypeScript supports recursion in: - Type aliases: type Tree<T> = { value: T; children: Tree<T>[] } - Conditional types: type Deep<T> = T extends object ? { [K in keyof T]: Deep<T[K]> } : T - Template literal types: type Path = ... (with depth limits)
Recursive conditional types (TS 4.1+) enable deep transformations like DeepPartial, DeepReadonly, and type-safe dot-path access.
// JSON type - fully recursive
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const data: JSONValue = {
name: 'Alice',
age: 30,
hobbies: ['reading', 'coding'],
address: {
city: 'NYC',
coordinates: [40.7, -74.0],
},
};
// Tree structure
interface TreeNode<T> {
value: T;
children: TreeNode<T>[];
}
const fileSystem: TreeNode<string> = {
value: 'root',
children: [
{
value: 'src',
children: [
{ value: 'index.ts', children: [] },
{ value: 'utils.ts', children: [] },
],
},
{ value: 'README.md', children: [] },
],
};
// Linked list
type LinkedList<T> = {
value: T;
next: LinkedList<T> | null;
};
const list: LinkedList<number> = {
value: 1,
next: { value: 2, next: { value: 3, next: null } },
};
Recursive types naturally model hierarchical data. The key is that the type references itself as a property type, creating potentially infinite nesting.
// DeepPartial - makes all nested properties optional
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
// DeepReadonly - makes all nested properties readonly
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
// DeepRequired - makes all nested properties required
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
interface Config {
server: {
host: string;
port: number;
ssl?: {
cert: string;
key: string;
};
};
database: {
url: string;
pool?: { min?: number; max?: number };
};
}
// All nested fields optional for merging defaults
type PartialConfig = DeepPartial<Config>;
const overrides: PartialConfig = {
server: { port: 8080 }, // OK: everything else optional
database: { pool: { max: 20 } } // OK: deeply partial
};
// All nested fields readonly for immutability
type FrozenConfig = DeepReadonly<Config>;
// frozenConfig.server.port = 3000; // Error at any depth
Deep utility types use recursive conditional types to transform nested structures. DeepPartial is essential for config merging and partial updates of complex objects.
// Flatten nested arrays recursively
type DeepFlatten<T> = T extends (infer U)[]
? DeepFlatten<U>
: T;
type A = DeepFlatten<number[][][]>; // number
type B = DeepFlatten<string[][]>; // string
type C = DeepFlatten<boolean>; // boolean
// Get all leaf paths of an object as dot-notation strings
type Paths<T, Prefix extends string = ''> = T extends object
? {
[K in keyof T & string]: T[K] extends object
? Paths<T[K], `${Prefix}${K}.`>
: `${Prefix}${K}`;
}[keyof T & string]
: never;
interface AppState {
user: { name: string; age: number };
settings: { theme: string; lang: string };
}
type StatePaths = Paths<AppState>;
// 'user.name' | 'user.age' | 'settings.theme' | 'settings.lang'
// Type-safe getter using recursive path
type GetByPath<T, P extends string> =
P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? GetByPath<T[Key], Rest>
: never
: P extends keyof T
? T[P]
: never;
type NameType = GetByPath<AppState, 'user.name'>; // string
type AgeType = GetByPath<AppState, 'user.age'>; // number
Recursive types with template literals enable type-safe dot-notation access. The Paths type generates all valid paths, and GetByPath resolves the type at a given path.
Recursive Types, Self-Referencing Types, Deep Utilities, JSON Type, Tree Structures