← Home ← Codex ← DEBT ← Engine
Browse by Category
+ added · updated 7d
← Back to glossary

Recursive Types

TypeScript 3.7 Advanced
debt(d5/e3/b3/t5)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list TypeScript itself (the compiler/type checker) as the sole tool, and automated detection is marked 'no'. Bare recursive aliases or excessively deep instantiation surface as compiler errors or 'Type instantiation is excessively deep' errors — caught by the TypeScript compiler when you actually invoke the type, not by a default linter pass. The code_pattern hint (any[]) suggests misuse via fallback to any is a secondary signal, also caught by tsc or strict mode rather than a plain linter.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix prescribes switching to interface for simple cases, or wrapping the recursive reference behind an object property or array boundary — a targeted, localised change within the type definition itself. For the any[] anti-pattern it recommends adopting existing library types (ts-essentials). None of these span multiple files or require architectural rework; they are small, focused corrections within one type definition or one component.

b3 Burden Structural debt — long-term weight of choosing wrong

Closest to 'localised tax' (b3). Recursive types apply to web and cli contexts but are typically confined to specific type definition files or utility type modules. They don't impose a gravitational pull on the entire codebase; only the code that consumes those types is affected. If a DeepReadonly or Json recursive type is wrong it impacts consumers, but the fix is still contained to the type definition and its direct usages rather than being a cross-cutting architectural burden.

t5 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'notable trap' (t5). The misconception field states developers believe recursive types always cause infinite compiler loops, when in fact only unconstrained instantiation does. The common mistake of writing a bare recursive alias (type T = T | string) triggers a compiler error, while the correct pattern (behind an object/array boundary) works fine. This is a documented gotcha that most TypeScript developers eventually learn, matching the t5 anchor — a real but learnable trap rather than a catastrophic one.

About DEBT scoring →

Also Known As

self-referential types recursive type aliases nested types

TL;DR

Types that reference themselves to describe arbitrarily nested structures — trees, nested menus, JSON, linked lists — without requiring any escape hatch.

Explanation

TypeScript supports recursive type aliases and interfaces. Interface recursion has always worked: interface TreeNode { value: number; children: TreeNode[] }. Type alias recursion requires the recursive part to be behind an object/array (not a bare generic): type Json = string | number | boolean | null | Json[] | { [k: string]: Json }. Recursive conditional types (type DeepReadonly<T>) use conditional + infer to transform nested structures. Mutual recursion (A references B, B references A) works with interfaces. Pitfall: infinite instantiation — types like type Infinite<T> = Infinite<T[]> cause the compiler to loop.

Diagram

flowchart TD
    JSON[Json type]
    JSON --> STR[string]
    JSON --> NUM[number]
    JSON --> BOOL[boolean]
    JSON --> NULL[null]
    JSON --> ARR[Json array]
    JSON --> OBJ[object Json values]
    ARR --> JSON
    OBJ --> JSON

Watch Out

TypeScript has a recursion depth limit for conditional types (~100 levels). Deeply nested real-world data hitting this limit should use interface recursion instead, which has no such restriction.

Common Misconception

Recursive types always cause infinite loops in the compiler — only unconstrained infinite instantiation does. Properly structured recursive types (behind an object/array boundary) are fully supported and common.

Why It Matters

Without recursive types, deep tree or JSON structures require unsafe any or a finite number of nested wrapper types — recursive types express the real shape precisely.

Common Mistakes

  • Bare recursive type alias without an object/array boundary — type T = T | string is disallowed; wrap in an array or object.
  • Creating infinitely deep conditional types that cause 'Type instantiation is excessively deep' errors.
  • Using any[] for JSON values instead of a proper recursive Json type.
  • Not using interface for simple recursive structures — interfaces handle recursion more efficiently than type aliases.

Avoid When

  • When the nesting depth is always finite and known — a fixed set of nested types is clearer.
  • When recursive conditional types cause 'excessively deep' compiler errors — switch to interfaces.

When To Use

  • Typing JSON, abstract syntax trees, file system trees, nested menu structures, or linked lists.
  • Building recursive utility types like DeepReadonly, DeepPartial, or DeepRequired.

Code Examples

💡 Note
Shows the Json recursive type alias, a generic TreeNode interface, and DeepReadonly — a recursive conditional type that makes every nested property readonly.
✗ Vulnerable
// Inaccurate — any allows anything, no structure
type JsonValue = any;
type TreeNode = { value: number; children: any[] };
✓ Fixed
// Recursive type alias for JSON
type Json =
    | string | number | boolean | null
    | Json[]
    | { [key: string]: Json };

// Recursive interface for a tree
interface TreeNode<T> {
    value: T;
    children: TreeNode<T>[];
}

// Recursive conditional type — deep readonly
type DeepReadonly<T> = T extends (infer U)[]
    ? ReadonlyArray<DeepReadonly<U>>
    : T extends object
        ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
        : T;

Added 11 Apr 2026
Views 103
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 0 pings S 2 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T
No pings yet today
No pings yesterday
SEMrush 8 Ahrefs 7 Amazonbot 6 Bing 5 PetalBot 5 Google 4 Scrapy 4 Perplexity 3 Meta AI 2 Twitter/X 2 Applebot 2 ChatGPT 1 Unknown AI 1 Brave Search 1
crawler 49 crawler_json 2
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: High
⚡ Quick Fix
Use interface for simple recursive structures. For type aliases ensure the recursive reference is behind an object property or array. Use DeepReadonly/DeepPartial patterns from ts-essentials instead of writing your own.
📦 Applies To
typescript 3.7 web cli
🔗 Prerequisites
🔍 Detection Hints
any\[\]|: any(?! extends)
Auto-detectable: ✗ No typescript
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File


✓ schema.org compliant