What is Recursive Generic in TypeScript ?
Last Updated :
14 Feb, 2024
In TypeScript, a recursive generic is a type that refers to itself within its definition, enabling the creation of data structures or types containing references to the same type within their structure. This capability proves invaluable when dealing with nested or hierarchical data structures like trees, linked lists or graphs. In this article, we'll explore the syntax, various approaches, and examples of using recursive generics in TypeScript, with additional insights into their practical applications.
Using interfaces
Interfaces provide a way to define the structure of objects in TypeScript without implementing the details. They are useful for defining recursive generic types, particularly when describing the shape of a type.
Syntax:
interface ListNode<T> {
value: T;
next: ListNode<T> | null;
}
T: It is the type parameter that represents the type of the node value and the type of the next node. One can then use this type to create a linked list of any type, such as ListNode<number> or ListNode<string>.
Example: In the example below, ListNode<T> represents a node in a linked list, where T is the type of the node's value and the type of the next node.
JavaScript
interface ListNode<T> {
value: T;
next: ListNode<T> | null;
}
const list: ListNode<number> = {
value: 1,
next: {
value: 2,
next: {
value: 3,
next: null
}
}
};
console.log(list);
Output:
{ value: 1, next: { value: 2, next: { value: 3, next: null } } }
Example: A function that calculates the depth of a tree.
JavaScript
interface Tree<T> {
value: T;
children: Array<Tree<T>>;
}
function depth<T>(tree: Tree<T>): number {
if (tree.children.length === 0) {
return 1;
}
return Math.max(...tree.children.map(child => depth(child))) + 1;
}
const tree: Tree<number> = {
value: 1,
children: [
{
value: 2,
children: [
{
value: 3,
children: []
},
{
value: 4,
children: []
}
]
},
{
value: 5,
children: [
{
value: 6,
children: []
}
]
}
]
};
console.log(depth(tree));
Output:
3
Using type aliases
Type aliases provide a way to give a type a name in TypeScript. They are another approach for defining recursive generic types, particularly useful when you want to create a new name for a type.
Syntax:
type Tree<T> = {
value: T;
children: Array<Tree<T>>;
}
Tree<T>: represents a tree node with a value of type T and an array of children nodes of the same type.
Example: In the example below, Tree<T> represents a tree node with a value of type T and an array of children nodes of the same type.
JavaScript
type Tree<T> = {
value: T;
children: Array<Tree<T>>;
}
const tree: Tree<number> = {
value: 1,
children: [
{ value: 2, children: [{ value: 3, children: [] }] },
{ value: 4, children: [{ value: 5, children: [] }] }
]
};
console.log(tree);
Output
{ value: 1, children: [ { value: 2, children: [ { value: 3, children: [] } ] }, { value: 4, children: [ { value: 5, children: [] } ] } ] }
Example: A function that reverses a linked list
JavaScript
type ListNode<T> = {
value: T;
next: ListNode<T> | null;
}
function reverse<T>(head: ListNode<T>): ListNode<T> {
if (head === null || head.next === null) {
return head;
}
const newHead = reverse(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
// Create a sample linked list
const list: ListNode<string> = {
value: "a",
next: {
value: "b",
next: {
value: "c",
next: null
}
}
};
console.log(reverse(list));
Output:
{ "value": "c", "next": { "value": "b", "next": { "value": "a", "next": null } } }
Using classes
Classes in TypeScript allow you to create objects with properties and methods. They are another approach for defining recursive generic types, suitable when you want to define the behavior and methods of a type along with its structure.
Syntax:
class BinaryTreeNode<T> {
value: T;
left: BinaryTreeNode<T> | null;
right: BinaryTreeNode<T> | null;
constructor(value: T, left: BinaryTreeNode<T> | null = null, right: BinaryTreeNode<T> | null = null) {
this.value = value;
this.left = left;
this.right = right;
}
}
Here, BinaryTreeNode<T> represents a binary tree node with a value of type T and two child nodes of the same type.
Example: In the example below, BinaryTreeNode<T> represents a binary tree node with a value of type T and two child nodes of the same type.
JavaScript
class BinaryTreeNode<T> {
value: T;
left: BinaryTreeNode<T> | null;
right: BinaryTreeNode<T> | null;
constructor(value: T, left: BinaryTreeNode<T> | null = null, right: BinaryTreeNode<T> | null = null) {
this.value = value;
this.left = left;
this.right = right;
}
}
const binaryTree: BinaryTreeNode<string> = new BinaryTreeNode(
"root",
new BinaryTreeNode("left"),
new BinaryTreeNode("right")
);
console.log(binaryTree);
Output :
BinaryTreeNode { value: 'root', left: BinaryTreeNode { value: 'left', left: null, right: null }, right: BinaryTreeNode { value: 'right', left: null, right: null } }
Similar Reads
What are Recursive Types & Interfaces in TypeScript ?
In TypeScript, recursive types and interfaces are constructs that reference themselves within their definitions, enabling the modeling of complex, nested data structures. They are particularly useful for representing hierarchical data such as trees and linked lists.By allowing types to be defined in
4 min read
How to Implement Recursive Generics in TypeScript ?
In TypeScript, Recursive generics let you use generic types that refer to themselves inside their definition. This is helpful when we are working with nested or hierarchical Data Structures or Algorithms. Using this we can create flexible and reusable code for managing complex Data Structures. There
3 min read
What are Generics in TypeScript ?
In this article, we will try to understand all the facts as well as the details associated with Generics in TypeScript along with some coding examples. Generics in TypeScript: Whenever any program or code is written or executed, one major thing one always takes care of which is nothing but making re
3 min read
Generics Interface in typescript
"A major part of software engineering is building components that not only have well-defined and consistent APIs but are also reusable. " This sentence is in the official documentation we would start with. There are languages that are strong in static typing & others that are weak in dynamic typ
5 min read
What is Declaration Merging in Typescript ?
In Typescript, the term "declaration merging" refers to the compiler combining two declarations with the same name into a single definition. Both of the initial declarations are present in this combined definition. It is possible to merge Interfaces, namespaces and enums and so on but classes cannot
3 min read
What is Type Predicates in Typescript ?
In this article, we are going to learn about the type predicates in Typescript. TypeScript is a statically typed programming language that provides many features to make your code more efficient and robust. Type predicates in TypeScript are functions that return a boolean value and are used to narro
3 min read
What is the Record Type in TypeScript ?
In TypeScript, the Record type is a utility type that represents an object type with keys and values of a specific type. It is often used when you want to define a type for the keys and values of an object. In this article, we will learn what is the Record type in TypeScript. Syntax:RecordKeys: A un
2 min read
What is Type Erasure in TypeScript?
TypeScript is a very mighty superset of JavaScript, which adds static typing to the language and allows developers to catch errors early on as well as write more maintainable code. This being said a TypeScript type system erases type information at compile time (or during the compilation), a phenome
4 min read
Recursive Type Guards In TypeScript
In TypeScript type guards help determine the type of a variable at runtime, they are especially useful when dealing with complex types like unions, discriminated unions or even recursive structures and a recursive type guard is a type guard function that can handle complex nested types, including th
6 min read
What is the Function type in TypeScript ?
TypeScript is a JavaScript-based programming language with a typed syntax. It provides improved tools of any size. It adds extra syntax to JavaScript. This helps in facilitating a stronger interaction between you and your editor. It also helps in catching the mistakes well in advance. It uses type i
3 min read