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];
UtilityResultTypical use
Partial<T>All members optionalA patch or a draft object
Required<T>All members requiredNormalising config after defaults
Pick<T, K> / Omit<T, K>Keep or drop named membersA list DTO next to the full record
Record<K, V>Object type with keys KLookup tables and dictionaries
Readonly<T>No reassignmentConstants and cached data
Exclude / ExtractFilter a unionNarrowing an event or status union
NonNullable<T>Remove null and undefinedAfter a guard has run
ReturnType / ParametersFunction types read backWrapping 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 T is the union of T's keys and T[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.

Generic patterns without over-engineering Classes, modifiers and abstract types

Last refreshed 2026-09-18.