AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 13 — Advanced Types: Mapped, Conditional, Literal, and Recursive

13 — Advanced Types: Mapped, Conditional, Literal, and Recursive

August 13, 20266 min read
Download as Markdown

"Type-level wizardry beyond me" was how I filed the advanced features, until the analogy landed. Writing it down reframed all of it: mapped, conditional, literal, template-literal, and recursive types are just programming constructs — loops, conditionals, constants, string interpolation, recursion — lifted to the type level. [1] I can compute types the way I compute values, with the same building blocks, and once that analogy landed the whole category stopped feeling arcane.

The motivation is that the built-in utility types (Partial, Pick, etc.) cover common transformations, but real codebases sometimes need transformations they don't provide — and those transformations are themselves built from a small set of type-level primitives. Understanding the primitives is what lets me read the utility types' source and write my own when the built-ins fall short.

Literal types: types that are exact values

A literal type is a type that represents one exact value, not a category [2]:

let status: "active";        // can only ever hold the string "active"
let dice: 1 | 2 | 3 | 4 | 5 | 6; // one of these exact numbers

The union of literal types is how I express "this value must be one of a closed set" — "GET" | "POST", a set of states, the allowed keys of an object. Literal types are the atoms that the more advanced features combine. The as const assertion (covered earlier) is the bridge that turns widened types (string) into literal types ("active").

Conditional types: the type-level ternary

A conditional type chooses between two types based on a condition, exactly like a JavaScript ternary but at the type level [3]:

type IsString<T> = T extends string ? "yes" : "no";
// IsString<"hi"> is "yes"; IsString<42> is "no"

The condition T extends U checks whether T is assignable to U, and the type resolves to the true or false branch accordingly. Conditional types become powerful with the infer keyword, which introduces a type variable to capture part of a type during the check — that's how ReturnType<F> is implemented (it infers the return type of a function signature). Conditional types are the foundation of almost every non-trivial utility type.

Mapped types: the type-level loop

A mapped type iterates over the keys of an existing type and transforms each property, producing a new type [4]:

type Nullable<T> = {
[K in keyof T]: T[K] | null;
};
// every property of T, but each can also be null

[K in keyof T] is the loop — for each key K of T, define a property whose type is T[K] | null. This is literally a for...of over keys at the type level. The built-in Partial<T> is exactly this pattern with T[K]? instead of T[K] | null. Once I saw mapped types as loops, every Readonly, Partial, Required, and custom transformer I write became a readable transformation rather than magic.

Template literal types: string interpolation at the type level

A template literal type composes string literal types using the same backtick syntax as JavaScript template strings [5]:

type Greeting = `hello ${string}`;
// any string starting with "hello "

type ApiRoute = `/${"users" | "posts"}/${string}`;
// "/users/...", "/posts/..."

This lets me express families of strings by pattern — API paths, event names, CSS property strings. Combined with keyof and infer, template literal types can derive getter names from property names ("name" → "getName"), build type-safe route strings, and enforce string formats the compiler can check. They turn string conventions into compiler-enforced contracts.

Recursive types: types that reference themselves

A recursive type is one that refers to itself in its own definition, which is how I model nested or hierarchical data [6]:

type TreeNode = {
value: number;
children: TreeNode[];
};

A tree node contains an array of tree nodes — the self-reference is what expresses "this structure nests arbitrarily deep." Linked lists, JSON values, file systems, and DOM-like structures all need recursive types to be described accurately. The compiler handles the recursion as long as it terminates (an object with a field of the same type, rather than an infinitely-expanding alias), so I model self-similar data the same way I would in code.

How they compose

The payoff of seeing these as type-level programming constructs is that they compose the same way code does. A real-world type might use a conditional to check a constraint, then map over the keys, then build template literal types for the output names — all in one definition. Reading Partial<T> as [K in keyof T]?: T[K] (a loop making each property optional) is the same skill as reading a function that maps over an array. The vocabulary is small; the expressiveness comes from composition.

value level type level for...of loop [K in keyof T] mapped type cond ? a : b T extends U ? X : Y `hello ${name}` `hello ${Name}` template literal function call UtilityType<T> same constructs, operating on types instead of values

How I use this

My defaults: reach for literal types whenever a value is one of a closed set; reach for a conditional type (often with infer) when a type depends on the shape of another — and reach for an existing utility before writing my own, since ReturnType/Parameters/Awaited already cover the common cases; reach for a mapped type when I need to transform every property of a type in a uniform way; use template literal types to make string conventions (routes, event names, accessors) compiler-checked; and reach for recursive types when modeling nested data. The discipline that pays off is reaching for the built-in utilities first, and only writing custom advanced types when none of them fit — because a hand-rolled conditional/mapped type adds reading cost, and it's worth it only when it removes more duplication than it introduces.

References

[1] Microsoft, "Advanced Topics," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/type-compatibility.html#advanced-topics

[2] Microsoft, "Literal Types," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types

[3] Microsoft, "Conditional Types," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#handbook-content

[4] Microsoft, "Mapped Types," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#handbook-content

[5] Microsoft, "Template Literal Types," TypeScript Handbook, 2024. [Online]. Available: https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html#handbook-content

[6] Microsoft, "Recursive Type References," TypeScript Playground docs, 2019. [Online]. Available: https://www.typescriptlang.org/play/3-7/types-and-code-flow/recursive-type-references.ts.html

Knowledge check · Question 1 of 5

A literal type like `"active"` represents…

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!