LearnFrenzy
Sign in Take a free test

Utility and Advanced Types · Lesson 16 of 17

Mapped, Conditional and Template Literal Types

5 min read Updated Nov 8, 2026 Saurabh Samir

The utility types from the last lesson aren't magic – they're written in TypeScript itself using mapped types and conditional types. Learning these lets you read library types (React, Prisma, tRPC) and write your own when the built-ins aren't enough. This is the pro end of TypeScript, so focus on the patterns rather than memorising syntax.

In this lesson

  • Mapped types and key remapping
  • Conditional types and infer
  • Template literal types
  • as const and satisfies

Mapped types

A mapped type loops over keys: { [K in keyof T]: … }. This is how Partial is defined:

type MyPartial<T> = { [K in keyof T]?: T[K] };                 // add ?
type Mutable<T> = { -readonly [K in keyof T]: T[K] };          // remove readonly
type Flags<T> = { [K in keyof T]: boolean };                   // change every value type

interface Settings {
  readonly darkMode: boolean;
  fontSize: number;
  language: string;
}

const touched: Flags<Settings> = { darkMode: true, fontSize: false, language: false };
const partial: MyPartial<Settings> = { fontSize: 18 };
const editable: Mutable<Settings> = { darkMode: false, fontSize: 16, language: "en" };
editable.darkMode = true;                                         // allowed: readonly removed
console.log(touched, partial, editable.darkMode);

Output

{ darkMode: true, fontSize: false, language: false } { fontSize: 18 } true

Key remapping with as

type Getters<T> = {
  [K in keyof T & string as `get${Capitalize<K>}`]: () => T[K];
};

interface Profile {
  name: string;
  age: number;
}

function makeGetters(p: Profile): Getters<Profile> {
  return { getName: () => p.name, getAge: () => p.age };
}

const g = makeGetters({ name: "Meera", age: 29 });
console.log(g.getName(), g.getAge() + 1);

Output

Meera 30

Conditional types and infer

A conditional type picks a type based on a test: T extends U ? X : Y. With infer you can pull a type out of another one:

type IsString<T> = T extends string ? "yes" : "no";
type ElementOf<T> = T extends readonly (infer E)[] ? E : never;        // infer the element
type Unwrap<T> = T extends Promise<infer V> ? Unwrap<V> : T;          // recursive, like Awaited

type A = IsString<"hi">;                 // "yes"
type B = IsString<42>;                   // "no"
type C = ElementOf<string[]>;            // string
type D = Unwrap<Promise<Promise<number>>>;   // number

const a: A = "yes";
const b: B = "no";
const c: C = "TypeScript";
const d: D = 2026;
console.log(a, b, c.length, d);

// distributive: a union is checked member by member
type NonString<T> = T extends string ? never : T;
const x: NonString<string | number | boolean> = 7;      // number | boolean
console.log(typeof x);

Output

yes no 10 2026
number

How NonString<string | number | boolean> is evaluated

  1. Splitstring, number and boolean are tested one at a time
  2. Teststring → never, number → number, boolean → boolean
  3. Joinnever | number | boolean = number | boolean

Template literal types

type Size = "sm" | "md" | "lg";
type Color = "primary" | "danger";
type ButtonClass = `btn-${Color}-${Size}`;                  // all 6 combinations

type EventName<T extends string> = `on${Capitalize<T>}`;
type Handlers = { [E in "click" | "submit" as EventName<E>]: () => string };

type Route = `/courses/${string}` | "/";
function navigate(route: Route): string {
  return `navigating to ${route}`;
}

const cls: ButtonClass = "btn-danger-sm";
const handlers: Handlers = { onClick: () => "clicked", onSubmit: () => "submitted" };
console.log(cls, handlers.onSubmit(), navigate("/courses/typescript"));
// navigate("/blog/x");        // error: not assignable to Route

Output

btn-danger-sm submitted navigating to /courses/typescript

as const and satisfies

const ROLES = ["admin", "editor", "viewer"] as const;     // readonly tuple of literals
type Role = (typeof ROLES)[number];                         // "admin" | "editor" | "viewer"

type RouteConfig = Record<string, { path: string; auth: boolean }>;

const routes = {
  home: { path: "/", auth: false },
  dashboard: { path: "/dashboard", auth: true },
} satisfies RouteConfig;          // checked against the type, but keeps the exact keys

const role: Role = "editor";
console.log(ROLES.includes(role), routes.dashboard.path, Object.keys(routes));
// routes.profile;                // error: 'profile' doesn't exist – a plain annotation would allow any key

Output

true /dashboard [ 'home', 'dashboard' ]

satisfies vs a type annotation

const routes: RouteConfig = … widens the type to Record<string, …>, so TypeScript forgets which keys exist. satisfies RouteConfig checks the value against the type but keeps its precise, inferred type. Use it for config objects, theme tokens and route maps.

Don't over-engineer

Advanced types are powerful but can make code hard to read and slow to compile. Reach for them in shared libraries and core models; in everyday application code, plain interfaces and the built-in utilities are usually clearer.

Practice

Try it yourself

  1. Write Nullable<T> that makes every property T[K] | null, and use it for a form that starts empty.

    Show solution
    type Nullable<T> = { [K in keyof T]: T[K] | null };
    interface Signup { name: string; age: number }
    
    const empty: Nullable<Signup> = { name: null, age: null };
    const filled: Nullable<Signup> = { ...empty, name: "Kiran" };
    console.log(empty, filled);

    Output

    { name: null, age: null } { name: 'Kiran', age: null }

Quick check

What does -readonly do in a mapped type?

Removes the readonly modifier from each property.

What does infer do?

Inside a conditional type, it captures part of a type into a new type variable.

Why use satisfies instead of : Type?

It checks the value but keeps the narrower inferred type.

Summary

  • Mapped types loop over keys; as remaps key names.
  • Conditional types choose types; infer extracts types; they distribute over unions.
  • Template literal types build string patterns; as const and satisfies keep precise types.

Finished this lesson?

Mark it complete to track your progress, then carry on with the next lesson.

Next lesson

Search LearnFrenzy

Esc

Quick links