DEV Community

Dharan Ganesan
Dharan Ganesan

Posted on

5 2 1 3 2

Day 51: Recursive Types

What Are Recursive Types? 🔄

Recursive types in TypeScript allow a type to refer to itself in its definition. This means you can create types that have nested structures, which can be infinitely deep. This concept is particularly handy when dealing with data structures like trees, linked lists, or JSON-like objects.

To illustrate this, let's start with a simple example of a binary tree:

type TreeNode<T> = {
  value: T;
  left?: TreeNode<T>;
  right?: TreeNode<T>;
};
Enter fullscreen mode Exit fullscreen mode

Handling Recursive Types 🧩

Recursive types can sometimes pose challenges, especially when working with deeply nested data. TypeScript offers several techniques to navigate and manipulate these types effectively:

  1. Type Guards: Use type guards like typeof and instanceof to ensure you're dealing with the right type at each level of recursion.

  2. Mapped Types: Leverage mapped types to transform or modify recursive types to suit your needs.

  3. Utility Types: TypeScript provides utility types like Partial, Required, and Record that can be helpful when working with recursive types.

  4. Conditional Types: Use conditional types to apply different type logic based on conditions within the recursive structure.

Examples

1. Modeling Hierarchical Data 🌳

Imagine you're building a file system navigation component, and you want to represent the hierarchical structure of directories and files. Recursive types can help you model this structure effectively:

type FileNode = {
  name: string;
  isFile: boolean;
  children?: FileNode[];
};
Enter fullscreen mode Exit fullscreen mode

2. Parsing JSON Data 🌐

When working with JSON data from external APIs, you often need to parse deeply nested structures. Recursive types make it easier to define the shape of the data you expect:

type JsonValue = string | number | boolean | null | JsonObject | JsonArray;
type JsonObject = { [key: string]: JsonValue };
type JsonArray = JsonValue[];
Enter fullscreen mode Exit fullscreen mode

3. Implementing Tree Data Structures 🌲

If you're working on algorithms or data structures that involve trees (e.g., binary trees, AVL trees, or B-trees), recursive types are essential for defining the nodes and structures:

type BinaryTreeNode<T> = {
  value: T;
  left?: BinaryTreeNode<T>;
  right?: BinaryTreeNode<T>;
};
Enter fullscreen mode Exit fullscreen mode

Advanced examples with template literal

1. Uppercase a String

You can create a type that transforms a string into uppercase:

type Uppercase<S extends string> = S extends `${infer L}` ? Uppercase<L> : S;

type UppercasedGreeting = Uppercase<"hello">; // UppercasedGreeting = "HELLO"
Enter fullscreen mode Exit fullscreen mode

2. Capitalize the First Letter

Create a type that capitalizes the first letter of a string:

type CapitalizeFirstLetter<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${Uppercase<First>}${Rest}`
  : S;

type CapitalizedGreeting = CapitalizeFirstLetter<"hello">; // CapitalizedGreeting = "Hello"
Enter fullscreen mode Exit fullscreen mode

3. Replace Substrings

Build a type that replaces all occurrences of a substring within a string:

type Replace<S extends string, From extends string, To extends string> = S extends `${infer Prefix}${From}${infer Suffix}`
  ? `${Prefix}${To}${Replace<Suffix, From, To>}`
  : S;

type ReplacedText = Replace<"hello world", "world", "universe">; // ReplacedText = "hello universe"
Enter fullscreen mode Exit fullscreen mode

4. Remove Spaces

Create a type that removes all spaces from a string:

type RemoveSpaces<S extends string> = S extends `${infer Left} ${infer Right}`
  ? RemoveSpaces<`${Left}${Right}`>
  : S;

type NoSpacesText = RemoveSpaces<"hello world">; // NoSpacesText = "helloworld"
Enter fullscreen mode Exit fullscreen mode

5. Reverse a String

Build a type that reverses a string:

type ReverseString<S extends string> = S extends `${infer First}${infer Rest}`
  ? `${ReverseString<Rest>}${First}`
  : S;

type ReversedGreeting = ReverseString<"hello">; // ReversedGreeting = "olleh"
Enter fullscreen mode Exit fullscreen mode

Pitfalls and Considerations 🕳️

While recursive types are a powerful tool, they come with some considerations:

  1. Infinite Types: Be cautious of creating types that could potentially become infinite. TypeScript will eventually enforce a recursion limit.

  2. Performance: Deeply nested recursive types can impact performance, so it's essential to balance type safety with practicality.

  3. Readability: Overly complex recursive types can make your code harder to understand. Use comments and clear type names to enhance readability.

Image of Bright Data

Cut Costs, Save Time – Get your datasets ready for action faster than ever.

Save on resources with our ready-to-use datasets designed for quick integration and application.

Reduce Costs

Top comments (1)

Collapse
 
jm__solo profile image
Juan Oliú

Very good article 👏👏.

SurveyJS custom survey software

JavaScript UI Library for Surveys and Forms

Generate dynamic JSON-driven forms directly in your JavaScript app (Angular, React, Vue.js, jQuery) with a fully customizable drag-and-drop form builder. Easily integrate with any backend system and retain full ownership over your data, with no user or form submission limits.

View demo

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay