CodeOath
← All posts
TypeScript75 min total · 21 parts

TypeScript Fundamentals: Types, Interfaces, Generics, and Why It Catches Bugs Before Runtime

Contents — Part 14 of 21: Mapped Types and Conditional Types
Part 14 of 21 · ~1 min

Mapped Types and Conditional Types

Partial, Readonly, Pick, and friends aren't compiler magic — they're written using mapped types, and you can write your own the same way:

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

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

// A custom mapped type: flip every property to a getter function
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }

That last example uses a key remapping clause (as) together with a template literal type (`get${...}`) to derive brand-new property names from existing ones at the type level — genuinely powerful for modeling APIs that programmatically generate methods from a data shape.

Conditional types let a type branch on a check, much like a ternary at the type level:

type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"

// infer extracts a type from within a matched structure
type ElementType<T> = T extends (infer U)[] ? U : never;
type Item = ElementType<string[]>; // string

infer is what makes built-ins like ReturnType<F> possible under the hood (conceptually, ReturnType<F> = F extends (...args: any[]) => infer R ? R : never) — it lets a conditional type pull a piece out of a larger matched shape and bind it to a new type variable.