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.