๐ŸŽ“ A1Learner
๐Ÿ”
0 of 37 lessons completed

TypeScript Recursive Types

A recursive type is a type that refers to itself inside its own definition. The reference passes through a container, like an array or an object, so values can nest to any depth.

A JSON value type that names itself

The union lists the plain JSON kinds first. The last two branches hold JsonValue again, once in an array and once in a record.

type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
const profile: JsonValue = { userName: 'Ada', scores: [10, 20], active: true, note: null };
console.log(JSON.stringify(profile));

A tree node type

Each node holds a label and an array of child nodes. The leaf nodes carry an empty children array, and that empty array is where the recursion stops.

type TreeNode = { label: string; children: TreeNode[] };
const tree: TreeNode = {
  label: 'root',
  children: [
    { label: 'left', children: [] },
    { label: 'right', children: [{ label: 'leaf', children: [] }] },
  ],
};
function countNodes(node: TreeNode): number {
  let total = 1;
  for (const child of node.children) {
    total += countNodes(child);
  }
  return total;
}
console.log(countNodes(tree));
console.log(tree.children[1].children[0].label);

A directly circular alias is refused

This alias names itself with no container in between. The compiler refuses it, because a value of this type could never be built.

type Chain = Chain | null;

Example

type Folder = { folderName: string; files: string[]; folders: Folder[] };
function countFiles(folder: Folder): number {
  let total = folder.files.length;
  for (const sub of folder.folders) {
    total += countFiles(sub);
  }
  return total;
}
const rootFolder: Folder = {
  folderName: 'docs',
  files: ['readme.md'],
  folders: [
    { folderName: 'guides', files: ['start.md', 'faq.md'], folders: [] },
    { folderName: 'notes', files: [], folders: [{ folderName: 'old', files: ['draft.md'], folders: [] }] },
  ],
};
console.log(countFiles(rootFolder));
console.log(rootFolder.folders[1].folders[0].folderName);

Key points

  • A JSON value type names itself in the array and record branches of its union.
  • A tree node type holds child nodes of the same type in an array member.
  • Reading a recursive value follows the same nesting the type describes.
  • A directly circular alias is refused, because it never passes through a container.

In this example, the Folder type names itself in the folders member, so the tree of folders can nest to any depth. The countFiles function mirrors that shape, adding the files of each folder and recursing into every subfolder.

Note

The recursion in a type ends where the value ends, with an empty array or a plain leaf value.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท TypeScript Playground

Exercise

Why does the compiler accept JsonValue but refuse type Chain = Chain | null?