Utility types, keyof and mapped types
The built-in transforms you will actually use, keyof and indexed access, mapped type modifiers, and template literal types for derived APIs.
The utility type toolkit
interface Article {
id: string;
title: string;
body: string;
publishedAt: Date;
}
type Draft = Partial<Article>; // every member optional
type Complete = Required<Draft>; // every member required again
type Card = Pick<Article, 'id' | 'title'>; // only these two members
type Meta = Omit<Article, 'body'>; // everything except body
type Headers = Record<string, string>; // any string key maps to string
type Frozen = Readonly<Article>; // no reassignment after creation
type Id = Article['id']; // string - indexed access
type Keys = keyof Article; // the union of its keys
type Values = Article[keyof Article]; // string | Date
// the keys whose value is a specific type
type StringKeys = { [K in keyof Article]: Article[K] extends string ? K : never }[keyof Article];| Utility | Result | Typical use |
|---|---|---|
Partial<T> | All members optional | A patch or a draft object |
Required<T> | All members required | Normalising config after defaults |
Pick<T, K> / Omit<T, K> | Keep or drop named members | A list DTO next to the full record |
Record<K, V> | Object type with keys K | Lookup tables and dictionaries |
Readonly<T> | No reassignment | Constants and cached data |
Exclude / Extract | Filter a union | Narrowing an event or status union |
NonNullable<T> | Remove null and undefined | After a guard has run |
ReturnType / Parameters | Function types read back | Wrapping an API without redeclaring it |
Utility types are pure compile-time transforms: the emitted JavaScript is exactly what you would have written by hand. Use them to keep one source of truth, and avoid them when a named interface would read better in an error message.
keyof, indexed access and mapped types
// K constrained to a key of T ties the value to the field it writes
function set<T, K extends keyof T>(target: T, key: K, value: T[K]): T {
return { ...target, [key]: value };
}
set(article, 'title', 'Hello'); // ok
// set(article, 'title', 42); // error: number is not string
// set(article, 'titel', 'x'); // error: not a key of Article
// a mapped type rebuilds a shape member by member
type Nullable<T> = { [K in keyof T]: T[K] | null };
// modifiers: -? removes optionality, -readonly removes readonly
type Concrete<T> = { -readonly [K in keyof T]-?: T[K] };
// rename keys with the as clause
type Getters<T> = {
readonly [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type CardGetters = Getters<Card>;
// { readonly getId: () => string; readonly getTitle: () => string }keyof Tis the union of T's keys andT[K]is the type of one of them - the pair is what makes a generic setter safe.- A mapped type is a loop over a union of keys, so every member is transformed by the same rule.
- Mapping over a union distributes:
Partial<A | B>transforms each member rather than their common shape. - Deeply nested utility types get unreadable fast. Name the intermediate types so error messages point at something meaningful.
Conditional and template literal types
// a conditional type picks a branch by assignability, with infer to capture a part
type ElementType<T> = T extends (infer U)[] ? U : T;
type Item = ElementType<string[]>; // string
type Self = ElementType<number>; // number
type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T;
type Value = Unwrap<Promise<Promise<boolean>>>; // boolean
// template literal types build string unions out of other unions
type EventName = 'click' | 'focus' | 'blur';
type HandlerName = `on${Capitalize<EventName>}`; // 'onClick' | 'onFocus' | 'onBlur'
// combine them to derive an API surface from one declaration
interface Endpoints {
user: { id: string };
guide: { slug: string };
}
type Path = `/${string & keyof Endpoints}`;
type Query<T> = { [K in keyof T as `${string & K}Query`]?: string };
// built-in string helpers: Uppercase, Lowercase, Capitalize, Uncapitalize
type Slug = Lowercase<'Guide'>; // 'guide'💡
The right amount of type-level programming is the amount that removes a duplicate definition. If a template literal type stops you keeping two lists in sync it earns its place; if it only makes a signature shorter to write, a plain interface is easier for the next reader.
FAQ
Do mapped types cost anything at runtime?
Nothing. They exist while the program is compiled and are erased from the output, so a
Partial<T> is an ordinary object at runtime with no hidden behaviour.When is a hand-written interface better?
When the shape is public or shows up in errors. A named interface produces a short readable message, while a chain of four utility types produces a wall of expanded members that hides which one was wrong.
Related
Generic patterns without over-engineering Classes, modifiers and abstract types
Last refreshed 2026-09-18.