Recursive Types
A recursive type refers to itself within its own definition, letting you describe data of unbounded depth — JSON, trees, and linked lists — with a single type that nests as far as the data does.
Learn Recursive Types in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Think of two mirrors facing each other — each reflection contains another, smaller reflection, on and on. A recursive type is a definition that contains itself the same way: a folder holds files and other folders , which hold files and folders, without limit. The structure can go as deep as the real data does, because the type keeps referring back to itself.
1. The Canonical Example: a JSON Type
JSON is the textbook recursive type. A JSON value is a primitive, or an array of JSON values, or an object whose values are JSON — so the type name appears inside its own definition, in two places. That self-reference is exactly what allows arbitrary nesting. To process such data, you write a recursive function whose branches mirror the type's cases.
2. Trees and Linked Lists
Two everyday structures are inherently recursive. A tree node contains an array of child nodes of the same type. A linked-list node holds a next pointer to another node of the same type, ending in null . Both definitions reference themselves, and both are traversed with recursion (for trees) or a simple loop following next (for lists).
3. & Recursion Limits
Recursive types aren't limited to data — they power recursive utility types too. is a mapped type that makes every nested property readonly by applying itself to each property's type. The runtime equivalent is a recursive deep-freeze. One caveat: TypeScript caps recursion depth (around 50 levels) to prevent infinite expansion, so deeply self-feeding types can hit an "excessively deep" error.
🎯 Your Turn
Compute a tree's depth with a function that calls itself on each child. Fill in the blank marked ___ with the recursive call, then run it.
No blanks this time — just a brief and a starting outline. Build the recursive flatten yourself, run it, and check your output against the example in the comments.
Practice quiz
What is a recursive type?
- A type that runs in a loop
- A type with many generics
- A type whose definition refers to itself
- A type that imports itself
Answer: A type whose definition refers to itself. A recursive type references itself, directly or indirectly, describing data of unbounded depth.
Why is the JSON type the canonical recursive example?
- A Json value can be a primitive, an array of Json, or an object of Json
- It is the only recursive type
- It uses classes
- It cannot nest
Answer: A Json value can be a primitive, an array of Json, or an object of Json. Json references itself in two places - Json[] and { [k: string]: Json } - allowing unlimited nesting.
How is a tree node modeled recursively?
- value plus next: number
- value only
interface TreeNode { value: number; children: TreeNode[] } - children are the same type.
How does a singly linked list node end?
- With another node
- With 'next: ListNode | null' where null ends the chain
- With an empty string
- It never ends
Answer: With 'next: ListNode | null' where null ends the chain. Each node points to the next, or null at the end of the list.
Can both interfaces and type aliases be recursive?
- Yes, both support recursion
- Only interfaces
- Only type aliases
- Neither can
Answer: Yes, both support recursion. Modern TypeScript supports recursive type aliases and recursive interfaces.
What does DeepReadonly<T> do?
- Freezes at runtime
- Only the top level readonly
- Recursively makes every nested property readonly at the type level
- Removes readonly
Answer: Recursively makes every nested property readonly at the type level. type DeepReadonly<T> = { readonly [K in keyof T]: DeepReadonly<T[K]> } recurses into each property.
What error signals recursion that doesn't terminate at the type level?
- Maximum call stack exceeded
- Type instantiation is excessively deep and possibly infinite
- Out of memory
- Cannot find name
Answer: Type instantiation is excessively deep and possibly infinite. TypeScript caps recursion depth (~50) and reports this when a type expands without shrinking.
Why must 'typeof json === object' be combined with a null check in a JSON walker?
- null is a number
- objects are null
- strings are objects
- typeof null is 'object'
Answer: typeof null is 'object'. typeof null === 'object', so check json !== null before recursing into objects.
What does every recursive traversal need to terminate?
- A loop counter
- A base case - empty children or a null next
- A generic parameter
- A try/catch
Answer: A base case - empty children or a null next. Without a base case (empty children array, null next) recursion never stops.
How do you avoid infinite loops on data with cyclic references?
- Use a smaller array
- Freeze the object
- Track visited nodes, e.g. with a Set
- Increase the stack size
Answer: Track visited nodes, e.g. with a Set. A visited Set prevents revisiting a node that points back to an ancestor.