Skip to main content

TS Ep 49: Recursive Types

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 49: This Article
How do you type a JSON object? A JSON object can contain arrays of objects, which can contain arrays of objects, infinitely down. You cannot hardcode this depth. You need a Recursive Type.

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 infer to 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:

  1. Check if the string starts with a space.
  2. If it does, extract the rest of the string using infer Rest.
  3. Recursively call TrimLeft on the extracted Rest.
  4. 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"
Warning

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 JSONValue type 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!

typescript - This article is part of a series.
Part 49: This Article