TL;DR (Quick Summary)#
- Recursive Type: A type alias or interface that references itself in its own definition.
- Tree Structures: Essential for modeling file systems, DOM elements, or AST (Abstract Syntax Tree) nodes.
- JSON Modeling: The standard way to model arbitrarily nested JSON values in TypeScript.
- Template Literal Recursion: Used with
inferto parse or manipulate strings character-by-character at compile time. - Depth Limits: The TypeScript compiler enforces recursion limits (around ~50 depth) to prevent infinite loops during type instantiation.
1. What is a Recursive Type#
A recursive type is simply a Type Alias or Interface that references itself inside its own definition.
Example: A File System Tree#
When building a file explorer UI, you usually model the data as a hierarchical tree. A folder contains an array of nodes, and those nodes can either be files, or more folders.
interface TreeNode {
name: string;
// A node can optionally contain an array of MORE TreeNodes!
children?: TreeNode[];
}
const fileSystem: TreeNode = {
name: "root",
children: [
{ name: "package.json" },
{
name: "src",
children: [
{ name: "index.ts" }
]
}
]
};2. Typing JSON#
The most classic and essential use case for recursive types in full-stack development is defining a valid JSON payload.
By definition, JSON values can be strings, numbers, booleans, nulls, arrays of JSON values, or objects mapping strings to JSON values. We capture this perfectly via a recursive union.
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
// 🟢 Fully typesafe JSON validation!
const myData: JSONValue = {
id: 1,
active: true,
metadata: {
tags: ["typescript", "json"],
nested: {
deeply: null
}
}
};3. Recursive String Parsing (TrimLeft)#
In previous episodes, we used the infer keyword to extract tokens from strings. When you combine infer with Recursive Types, you can parse a string iteratively, character by character!
Let’s build a utility type that trims all whitespace from the left side of a string.
The Logic:
- Check if the string starts with a space.
- If it does, extract the rest of the string using
infer Rest. - Recursively call
TrimLefton the extractedRest. - If it doesn’t start with a space, return the string as-is.
type TrimLeft<T extends string> =
T extends ` ${infer Rest}`
? TrimLeft<Rest> // Recursion!
: T;
// The compiler executes TrimLeft multiple times until the space is gone!
type Trimmed = TrimLeft<" hello">;
// Inferred Type: "hello"
Recursive types are incredibly powerful, but they place a heavy burden on the TypeScript compiler. If the compiler has to recurse too many times (usually around ~50 levels deep for types, though it varies by version), it will throw a Type instantiation is excessively deep and possibly infinite error. Keep your string manipulations and deep object mappings reasonable!
4. Deep Immutability via Recursion#
We can also use recursive types to make entire object structures recursively read-only:
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};Summary & Next Steps#
In this episode:
- We defined Recursive Types (types that call themselves).
- We modeled infinite nested hierarchies (File system trees).
- We wrote a definitive
JSONValuetype alias. - We used recursion combined with template literals to iteratively parse and manipulate strings (
TrimLeft).
In Episode 50: Deep Readonly and Deep Partial, we will combine Mapped Types with Recursive Types to transform properties infinitely deep down an object tree!

