Free Handbook · Runs in your browser

Advanced & Utility Types

Transform an existing type into a new one instead of hand-writing it again — mapped types, conditional types, the built-in utility types, template literals and infer.

0 / 105 lessons🔥 0 day streak
ShareXLinkedIn

Module 08 · what you'll be able to do

  • Write a mapped type that transforms every property of another type
  • Write a conditional type and understand it as a compile-time if/else
  • Reach for the right built-in utility: Partial, Required, Pick, Omit, Record, Readonly
  • Build a string literal type out of other types with template literal types
  • Use infer inside a conditional type to pull a piece out of a matched type
01

Mapped types

A mapped type builds a new type by iterating over another type’s keys and transforming each one the same way. { [K in keyof T]: T[K] } is the identity case: keyof T produces a union of T’s property names, [K in ...] loops over each one, and T[K] looks up that property’s type — the result is a type shaped exactly like T. Change what happens to T[K] on the right and you get a genuinely new type.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
true false
Your turn
Add a third property to Features, exportsEnabled: boolean, and set it in the flags object too.
  • keyof T — a union of every property name of T, as string literal types.
  • [K in Union] — loops the mapped type once per member of that union, binding K to the current one.
  • T[K] — an indexed access type, looking up the type of property K on T.
This is how Partial, Readonly and Record are actually defined
Every built-in utility type in the next lessons is itself a mapped type under the hood — Partial<T> is roughly { [K in keyof T]?: T[K] }, Readonly<T> is { readonly [K in keyof T]: T[K] }. Learning the mapped-type syntax here is what makes the utility types in this module feel obvious rather than magical.
JuniorWhat does { [K in keyof T]: T[K] } do?

It is a mapped type: keyof T produces a union of T's property names, [K in ...] iterates over each one, and T[K] looks up that property's type — so the whole expression rebuilds a type identical to T. It becomes genuinely useful once you change what happens per key, like wrapping T[K] in boolean for a "flags" type, or in Promise for an async version of T.

What they are really testing: Basic comfort reading mapped-type syntax, the building block every utility type in this module rests on.

02

Conditional types

A conditional type, written T extends U ? X : Y, is an if/else evaluated entirely by the compiler, at the type level. If T is assignable to U, the whole expression resolves to type X; otherwise it resolves to Y. It looks like the runtime ternary operator, but nothing here executes — it is resolved once, wherever T gets substituted with a concrete type.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
yes
no
Your turn
Add a second call, check(true), and predict what IsString resolves to for a boolean before running it.
VisualizeThe runtime check mirrors the compile-time conditionStep 1 / 5
type IsString<T> = T extends string ? "yes" : "no"
function check<T>(value: T): IsString<T> {
return (typeof value === "string" ? "yes" : "no") as IsString<T>
}
console.log(check("hello"))
console.log(check(42))
Line 1

IsString is a compile-time-only computation: given a type T, it resolves to the literal type "yes" if T is assignable to string, else "no". It produces no runtime code by itself.

Variables now

nothing yet

All 5 steps as a table
StepLineWhat happenedVariables now
11IsString is a compile-time-only computation: given a type T, it resolves to the literal type "yes" if T is assignable to string, else "no". It produces no runtime code by itself.
25check("hello") runs. At runtime, typeof value === "string" is an ordinary check against the real value "hello".value = 'hello'
33The condition is true, so "yes" is returned — exactly what IsString concluded at compile time for T = string.
46check(42) runs; typeof value === "string" is false this time.value = 42
53The condition is false, so "no" is returned.
Mid-levelWhat does T extends U ? X : Y mean as a type?

It is a compile-time if/else over types: if T is assignable to U, the whole expression resolves to type X, otherwise to type Y. Unlike a runtime ternary, this is evaluated once by the compiler at every place T gets substituted with something concrete — it lets you write a type that itself branches based on what it is given.

What they are really testing: Understanding conditional types as type-level branching, not a runtime construct.

03

Partial, Required and Readonly

TypeScript ships a handful of generic utility types built from mapped types, ready to use without importing anything. Partial<T> makes every property optional. Required<T> does the opposite, making every optional property mandatory. Readonly<T> makes every property read-only after the value is created.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1 Bea
Your turn
Add an optional email field to User, then call updateUser passing only { email: "[email protected]" } as the changes object.
typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
localhost 8080
Your turn
Remove port from the object literal and see the exact compile error Required produces for a missing property — the next error card shows this pattern.
typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1 2
Your turn
This run block only prints the values, since mutating a Readonly field is a compile error the runner here would not catch — the error card below shows exactly what tsc says when you try.
Error you will hit

TS2740: an object literal missing properties Required<T> demands

typescript
interface Config {
  host?: string
  port?: number
}
type FullConfig = Required<Config>
const cfg: FullConfig = { host: "localhost" }
app.ts:6:7 - error TS2740: Type '{ host: string; }' is missing the following properties from type 'Required<Config>': port

6 const cfg: FullConfig = { host: "localhost" }
        ~~~
Why the compiler said that

Required turns both optional properties, host? and port?, into mandatory ones. The object literal only supplies host, so port is missing from a type that now requires it.

The fix

Supply every property Required demands.

typescript
const cfg: FullConfig = { host: "localhost", port: 8080 }
Error you will hit

TS2540: mutating a Readonly<T> value

typescript
type ReadonlyPoint = Readonly<{ x: number; y: number }>
const p: ReadonlyPoint = { x: 1, y: 2 }
p.x = 5
app.ts:3:1 - error TS2540: Cannot assign to 'x' because it is a read-only property.

3 p.x = 5
  ~~~
Why the compiler said that

Readonly maps every property of T to a read-only version of itself. The compiler then rejects any assignment to those properties after the object is created, even though the underlying object is an ordinary, mutable JavaScript object at runtime.

The fix

Create a new object with the change instead of mutating the existing one.

typescript
const moved = { ...p, x: 5 }
console.log(moved.x)
Mid-levelWhat's the difference between Partial and Required?

Partial makes every property of T optional, which fits a "patch" or update payload where the caller only sends the fields that changed. Required does the opposite — it makes every optional property mandatory, which fits a fully-resolved value after defaults have already been applied and nothing should be missing anymore.

What they are really testing: Whether the candidate distinguishes the two by direction, not just a vague "they change optionality."

04

Pick, Omit and Record

Pick<T, Keys> builds a new type with only the listed properties of T. Omit<T, Keys> does the reverse, keeping everything except the listed ones. Record<Keys, ValueType> builds an object type from scratch, mapping every key in Keys to the same value type — useful for a lookup table or a dictionary.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1 Ravi
Your turn
Change the Pick keys to just "name" and confirm the object literal can no longer include id.
typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
id, name
Your turn
Add a second sensitive field, ssn: string, to User, and omit both: Omit.
typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
math, art, science
88
Your turn
Change the key type to a union of literal subjects, Record<"math" | "art" | "science", number>, and notice scores.history is now a compile error instead of undefined at runtime.
UtilityWhat it keepsTypical use
Pick<T, K>Only the listed keysA slim view of a larger type, like a list preview.
Omit<T, K>Everything except the listed keysHiding sensitive or internal fields, like a password.
Record<K, V>Every key in K, all mapped to type VA dictionary or lookup table built from scratch.
Quick check

Which utility type would you use to build a version of a User interface with only id and email?

05

Template literal types

A template literal type looks exactly like a JavaScript template string, but built from types instead of values: `on${Capitalize<K>}` takes whatever string literal type K is, capitalizes it, and glues "on" onto the front — producing a new, more specific string literal type at compile time.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
onClick
Your turn
Change the type parameter to EventName<"submit"> and update the value to match the new literal type it produces.
Error you will hit

TS2322: a string that does not match the template literal type

typescript
type EventName<K extends string> = `on${Capitalize<K>}`
const handler: EventName<"click"> = "onclick"
app.ts:2:7 - error TS2322: Type '"onclick"' is not assignable to type '"onClick"'.

2 const handler: EventName<"click"> = "onclick"
        ~~~~~~~
Why the compiler said that

EventName<"click"> resolves to the exact literal type "onClick" — Capitalize<"click"> produces "Click". Only that specific string satisfies it; "onclick" with a lowercase c is a completely different literal type to the compiler.

The fix

Match the exact casing the template literal type actually produces.

typescript
const handler: EventName<"click"> = "onClick"
Where this earns its keep
Typing a props object for event handlers (onClick, onSubmit, onHover, all generated from one list of event names), CSS-in-JS property names, or route strings like `/users/${number}` — anywhere a family of string literals follows one predictable pattern.
06

The infer keyword

infer can only appear inside the extends clause of a conditional type. It introduces a new type variable that the compiler fills in by pattern-matching against the type being checked — instead of just testing whether T matches a shape, infer lets you capture a piece of it and use that piece in the result.

typescriptEdit it. ⌘/Ctrl + Enter runs.
You should see
1
a
Your turn
Write a matching Last conditional type, again with infer, that also captures the element type of an array — the logic only differs in which index the run block returns.
This is exactly how ReturnType<T> works
TypeScript's own ReturnType<T> is defined roughly as T extends (...args: any[]) => infer R ? R : never — it matches T against "a function that returns something," captures whatever that return type is as R, and resolves to R. Parameters<T>, Awaited<T> and InstanceType<T> all use the identical technique on different parts of a type.
SeniorWhat does the infer keyword do inside a conditional type, and where have you seen it used in TypeScript's own standard library types?

infer introduces a new type variable inside the extends clause of a conditional type, letting the compiler capture a piece of a matched type instead of just testing against it. TypeScript's own ReturnType is defined roughly as T extends (...args: any[]) => infer R ? R : never — it matches T against a function shape and infers R as whatever that function actually returns, then resolves to R. Parameters, Awaited and InstanceType use the same technique on other parts of a type.

What they are really testing: Whether the candidate can go beyond using infer and explain how it powers utility types they already rely on.

Quick check

What does T extends (infer U)[] ? U : T resolve to when T is string[]?

Finish the TypeScript handbook, then get hired

Sit the exam for your certificate, run your resume through the ATS checker, and see the jobs that ask for exactly this.

Check my resume
Found this course useful? Share it.
ShareXLinkedIn

Comments

0

Join the conversation. Sign in to leave a comment — we'd love to hear your thoughts.