Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      Decoding The SVG path Element: Curve And Arc Commands

      June 23, 2025

      This week in AI dev tools: Gemini 2.5 Pro and Flash GA, GitHub Copilot Spaces, and more (June 20, 2025)

      June 20, 2025

      Gemini 2.5 Pro and Flash are generally available and Gemini 2.5 Flash-Lite preview is announced

      June 19, 2025

      CSS Cascade Layers Vs. BEM Vs. Utility Classes: Specificity Control

      June 19, 2025

      I recommend this Chromebook over many Windows laptops that cost twice as much

      June 23, 2025

      Why I recommend this flagship TCL TV over OLED models that cost more (and don’t regret it)

      June 23, 2025

      Finally, a Lenovo ThinkPad that impressed me in performance, design, and battery life

      June 23, 2025

      3 productivity gadgets I can’t work without (and why they make such a big difference)

      June 23, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      SQL Joins

      June 23, 2025
      Recent

      SQL Joins

      June 23, 2025

      Dividing Collections with Laravel’s splitIn Helper

      June 23, 2025

      PayHere for Laravel

      June 23, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Distribution Release: IPFire 2.29 Core 195

      June 23, 2025
      Recent

      Distribution Release: IPFire 2.29 Core 195

      June 23, 2025

      TeleSculptor – transforms aerial videos and images into Geospatial 3D models

      June 23, 2025

      Rilasciato IceWM 3.8: Gestore di Finestre per il Sistema X

      June 23, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»Recursive Types in TypeScript: A Brief Exploration

    Recursive Types in TypeScript: A Brief Exploration

    May 8, 2025

    It is said that there are two different worlds in TypeScript that exist side by side: the type world and the value world.

    Consider this line of code:

    const firstName: string = 'Maynard';
    

    While firstName and 'Maynard' live in the value world, string belongs to the type world.

    Or, consider the typeof operator that exists in both worlds.

    Here it is in the value world:

    console.log(typeof 42); // number
    

    And, here it is in the type world, used for extracting the type of the function increment, which is then passed to a utility type called ReturnType:

    function increment(n: number) {
      return n + 1;
    }
    
    type T = ReturnType<typeof increment>; // number
    

    One of the wonders of the type world is the existence of recursive types – types that refer to themselves. They are somewhat similar to recursive functions that you might already be familiar with.

    Here is a recursive function:

    function addUpTo(n: number): number {
      if (n === 0) {
        return n;
      }
    
      return n + addUpTo(n - 1);
    }
    

    And, here is a recursive type:

    type JSONValue =
      | string
      | number
      | boolean
      | null
      | { [key: string]: JSONValue };
    

    The function addUpTo calls itself, each time with a lesser value of n, gradually reaching the base case. (n is assumed to be a nonnegative number, otherwise, we’ll have the Maximum call stack size exceeded error.)

    The type JSONValue is a union type that has an index signature in one of its possible types ({ [key: string]: JSONValue }), and the value is a reference to itself.

    To see it with an example, let’s say we have an object person that has the properties name, age, and friends:

    const person = {
      name: 'Alice',
      age: 25,
      friends: {
        0: {
          name: 'Bob',
          age: 23,
          friends: {
            // ...
          }
        },
        1: {
          name: 'Carol',
          age: 28,
          friends: {
            // ...
          }
        },
        // ...
      }
    };
    

    The value of friends is an object that has numbers as its properties, each having the value that looks just like the person we’ve just defined. So, we can create a Person type for it where the value of friends is an object that also has the value type Person:

    type Person = {
      name: string;
      age: number;
      friends: {
        [key: number]: Person;
      };
    };
    

    Note: In JavaScript, object keys are either strings or symbols. Even if we define key as a number, JavaScript eventually will coerce the object keys to strings.

    In this article, we’ll not only take a look at what recursive types are in TypeScript, but we’ll also see how they can apply to recursive data structures and how they can be useful with two different use cases.

    It can be an excellent tool in your toolkit for very specific circumstances such as when you need to extend a utility type or get the “inner” type of a multidimensional array.

    Let’s begin our exploration.

    Here’s what we’ll cover:

    1. Recursive Types for Trees and Linked Lists

    2. Recursion with Mapped and Conditional Types

    3. Use Cases for Recursive Types

      • Use Case 1: DeepPartial

      • Use Case 2: UnwrapArray

    4. Conclusion (and a Warning!)

    Recursive Types for Trees and Linked Lists

    Recursive types are probably best understood with a data structure like a tree:

    A binary tree where the root node has two children, left and right. The left child has left and right children, each of them also has left and right children of their own. The right child of the root node has a left child that has a left child of its own, and a right child that is a leaf node.

    For example, a node of a binary tree has at most two children, left and right. A child node, if it also has children, is the root of a subtree itself.

    We can create a TreeNode type for a binary tree:

    type TreeNode<T> = {
      value: T;
      left: TreeNode<T> | null;
      right: TreeNode<T> | null;
    };
    

    It is a generic type, so the value can be any type that we pass to it. The left and right children can be either TreeNode themselves or null.

    Let’s say we have this binary tree:

    A binary tree that has the value 8 as its root node. It has a left child with the value 3 and a right child with the value 10. The left child has a left child node that has the value 1 and a right child node that has the value 6, which has a left child node with the value 4 and a right child node with the value 7. The right child of the root node that has the value 10 has a right child node with value 14, which has a left child node that has the value 13.

    We can represent it just like this, with the type of TreeNode that we’ve just defined:

    const binaryTree: TreeNode<number> = {
      value: 8,
      left: {
        value: 3,
        left: {
          value: 1,
          left: null,
          right: null
        },
        right: {
          value: 6,
          left: {
            value: 4,
            left: null,
            right: null
          },
          right: {
            value: 7,
            left: null,
            right: null
          }
        }
      },
      right: {
        value: 10,
        left: null,
        right: {
          value: 14,
          left: {
            value: 13,
            left: null,
            right: null
          },
          right: null
        }
      }
    };
    

    Since this is a generic type, we’re passing to it the type number that’s used as the type of value.

    Similarly, we can create a type for a linked list where each node has a value and a next property that either points to another node or null:

    type LinkedList<T> = {
      value: T;
      next: LinkedList<T> | null;
    };
    

    So, if our linked list looks like this:

    A linked list where the head points to the node with the value 1, which points to the node with the value 2, which points to the node with the value 3 that points to null.

    We can represent it like this, with the type LinkedList:

    const linkedList: LinkedList<number> = {
      value: 1,
      next: {
        value: 2,
        next: {
          value: 3,
          next: null
        }
      }
    };
    

    Note: We used type aliases in the examples above, but we can also use an interface instead:

    interface TreeNode<T> {
      value: T;
      left: TreeNode<T> | null;
      right: TreeNode<T> | null;
    }
    
    interface LinkedList<T> {
      value: T;
      next: LinkedList<T> | null;
    }
    

    Recursion with Mapped and Conditional Types

    Applying recursive types to values representing recursive data structures is a bit obvious and not too exciting, but we can explore other options where recursion can also be used, such as mapped and conditional types.

    Mapped types are a convenient way to create a new type based on another one. We can, for example, create a new type using the keys of an object, where we map the keys to a different value type.

    Let’s say we have a colors object that has color names and the corresponding hex values. The values are of string type, but let’s say we want to create an object type where the keys are the same, except that the type of the values should be boolean:

    const colors = {
      aquamarine: '#7fffd4',
      black: '#000000',
      blueviolet: '#8a2be2',
      goldenrod: '#daa520',
      indigo: '#4b0082',
      lavender: '#e6e6fa',
      silver: '#c0c0c0'
    };
    
    type ColorsToBoolean<T> = {
      [K in keyof T]: boolean;
    };
    
    type Result = ColorsToBoolean<typeof colors>;
    

    The Result type then will look like this:

    A screenshot of the code block that's defined above when it's hovered over the type `Result`. Keys are the same as the ones in the `colors` object, and all the values are `boolean`.

    In order to create a recursive mapped type, however, we need a reference to the same type that we’re creating, like this:

    type Recursive<T> = {
      [K in keyof T]: Recursive<T[K]>;
    };
    

    Before going further, let’s also take a look at the conditional types, which look very similar to the conditional expressions that use the ternary operator:

    AType extends AnotherType ? ResultTypeIfTrue : ResultTypeIfFalse;
    

    It has this familiar form:

    condition ? resultIfTrue : resultIfFalse
    

    Now, we can combine both mapped and conditional types to create a recursive mapped conditional type:

    type Recursive<T> = {
      [K in keyof T]: T[K] extends number ? T[K] : Recursive<T[K]>;
    };
    

    We map the keys of the given object type to the same value type if it’s a number, otherwise, continue with the recursion.

    Use Cases for Recursive Types

    Use Case 1: DeepPartial

    One use case of recursive types is extending the capabilities of the utility type Partial.

    Let’s say we have a type for an article, using an interface this time:

    interface IArticle {
      title: string;
      description: string;
      url: string;
      author: {
        name: string;
        age: number;
      };
    }
    

    Partial makes all the properties of an object type that it’s given optional.

    But, if we try to do this:

    const article: Partial<IArticle> = {
      title: 'Navigating the Mysteries',
      description:
        'As we walk our questions into a troubled future, storyteller and mythologist Martin Shaw invites us to subvert today’s voices of certainty and do the hard work of opening to mystery.',
      url: 'https://emergencemagazine.org/essay/navigating-the-mysteries',
      author: {
        name: 'Martin Shaw'
      }
    };
    

    We’ll have an error: Property 'age' is missing in type '{ name: string; }' but required in type '{ name: string; age: number; }'. All the properties are optional as expected, except for age which is a property of the property author. So, Partial is not going to work for objects with more than one level of depth.

    In our example, we don’t want to pass in an age property for the author, so we need to find a way to make it work.

    In fact, the recursive mapped conditional type that we’ve just defined above is a perfect use case for this. We can use recursion so that all the properties are optional, no matter their depth:

    type DeepPartial<T> = {
      [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
    };
    

    Now, if we try our example with DeepPartial, there are no errors, and the problem is resolved:

    const article: DeepPartial<IArticle> = {
      title: 'Navigating the Mysteries',
      description:
        'As we walk our questions into a troubled future, storyteller and mythologist Martin Shaw invites us to subvert today’s voices of certainty and do the hard work of opening to mystery.',
      author: {
        name: 'Martin Shaw'
      }
    };
    

    Use Case 2: UnwrapArray

    Another use case we can take a look at is when we need to have the “inner“ type of a multidimensional array.

    Consider this one:

    type UnwrapArray<A> = A extends Array<infer T> ? UnwrapArray<T> : A;
    

    We define an UnwrapArray generic type. If the type we pass to it is yet another array (A extends Array), then it’s passed to UnwrapArray again until we reach a type that doesn’t extend Array.

    Note that we use the infer keyword to extract the type. infer is only used with conditional types when extends is used, so it’s perfect for our purpose here.

    Now, we can get the inner type:

    type Result = UnwrapArray<string[][][]>; // string
    

    Using the keyword Array will have the same result:

    type Result = UnwrapArray<Array<Array<Array<string>>>>; // string
    

    Conclusion (and a Warning!)

    The universe of recursive types in TypeScript is fascinating and very powerful. But, of course, with great power comes great responsibility. TypeScript’s own documentation warns us:

    Keep in mind that while these recursive types are powerful, they should be used responsibly and sparingly. (Source)

    It’s not only that recursive types can result in longer time for type-checking, but with enough complexity, it can also result in a compile-time error. In fact, the documentation also tells us not to use them at all if possible.

    So, was all this learning for nothing?

    The answer depends on what you make of it. Recursion is a powerful concept that definitely has use cases in TypeScript as we’ve seen in this article, and if used responsibly, it can be an excellent tool.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleCode a Dropbox Clone with NextJS
    Next Article The Front-End Performance Optimization Handbook – Tips and Strategies for Devs

    Related Posts

    Security

    Canada says Salt Typhoon hacked telecom firm via Cisco flaw

    June 23, 2025
    Security

    Critical Teleport Vulnerability Let Attackers Remotely Bypass Authentication Controls

    June 23, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    Nebulous Mantis Targets NATO-Linked Entities with Multi-Stage Malware Attacks

    Development

    CVE-2024-6235: NetScaler Console Flaw Enables Admin Access, PoC Publishes

    Security

    CVE-2025-2761 – GIMP FLI File Parsing Out-Of-Bounds Write Remote Code Execution Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-4493 – Devolutions Server Privilege Escalation Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    Highlights

    Microsoft will enhance the Edge Sidebar with Copilot Chat agents

    April 7, 2025

    Microsoft will enhance the Edge browser by integrating Copilot Chat agents directly into the Edge…

    CVE-2025-5016 – Relevanssi WordPress Stored Cross-Site Scripting Vulnerability

    May 31, 2025

    CVE-2025-46236 – Link Software LLC HTML Forms Stored Cross-site Scripting (XSS)

    April 22, 2025

    Everything Google just announced at I/O 2025: Gemini upgrades, AI Search, Android XR, and more

    May 20, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.