Checkpoint: Advanced Types
This checkpoint consolidates the advanced half of the course — utility types, mapped types, conditional types and infer , generic constraints, keyof / typeof , discriminated unions, modules, and tsconfig — into one recap, one build challenge that combines several features, and a short quiz.
Learn Checkpoint: Advanced Types in our free TypeScript course — an interactive lesson with runnable examples, a practice exercise and a quick reference.
Part of the free TypeScript course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What This Checkpoint Covers
1. Quick Recap (run it)
Each line below exercises one advanced feature at runtime: a utility-type-style pick, a mapped-type-style key walk, a keyof getter, and a discriminated-union switch. Run it to refresh all of them at once.
📚 Cheat-Sheet: When to Reach for What
You want to…
Reach for
Make all fields optional / drop a key / select keys
Partial / Omit / Pick
Type a dictionary or lookup table
Record<K, V>
Transform every property of a type
{ [K in keyof T]: ... }
Choose a type based on a condition
T extends U ? X : Y
Extract a type from inside another
infer
Restrict a generic / give it a fallback
<T extends C = D>
Read a property by a safe key
keyof T + T[K]
Model mutually-exclusive states
discriminated union
2. Build Challenge: A Typed Event Bus
Time to combine three features in one program: a discriminated union of events, a mapped-type handler registry (one handler per event type), and a generic constraint so you can only register a handler for a known event type. Finish the starter so the test prints the expected lines, then reveal the full TypeScript solution to compare.
Note: the small as casts bridge the gap between the precise per-event handler types and a single runtime map — a common, contained use of a type assertion inside an otherwise fully-typed API.
Common Errors (and the fix)
- ❌ Handler param typed too widely: if on isn't generic, e becomes the whole union and you can't read event-specific fields. ✅ Make on generic over K extends AppEvent["type"] and use Extract<AppEvent, { type: K }> .
- ❌ Forgetting the exhaustiveness guard: emitting an unknown event silently does nothing. ✅ Throw (or assertNever ) when no handler is found.
- ❌ Duplicating a derived type: hand-writing a "summary" type that mirrors part of User . ✅ Derive it with Pick / Omit so it can't drift.
- ❌ Widening the discriminant: typing type as string breaks narrowing in emit . ✅ Keep it as the exact literal union.
- ❌ Leaving strict mode off: without strictNullChecks the missing-handler case slips through. ✅ Keep "strict": true so optional lookups must be handled.
Frequently Asked Questions
📝 Checkpoint Quiz
Answer each in your head, then expand to check. No peeking first!
Omit<User, "id"> . Omit keeps every property except the ones you name, so it's the idiomatic way to strip server-managed fields from an input type.
{ readonly [K in keyof T]: T[K] } — this is exactly the built-in Readonly<T> . Adding -readonly instead would strip read-only from every property.
X captures the function's return type; this is the built-in ReturnType<F> . infer introduces X inside the conditional's extends pattern.
Constraining K to keyof T rejects keys that don't exist (a typo becomes a compile error), and the lookup type T[K] returns the exact property type instead of a vague any .
Exhaustiveness. If every case is handled, x narrows to never and compiles; if someone adds a new variant and forgets a case, x isn't never and the build fails — turning a missed case into a compile error.
strictNullChecks (enabled by "strict": true ). It makes null and undefined distinct types you must handle — via a guard, optional chaining ?. , or ?? — before using a value.
🎉 Checkpoint Complete
- ✅ Derived types with Partial , Pick , Omit , Record , and ReturnType
- ✅ Transformed types with mapped types and their modifiers
- ✅ Branched on types with conditional types and extracted with infer
- ✅ Restricted generics with constraints and defaults; read properties with keyof + T[K]
- ✅ Modelled exclusive states with discriminated unions and exhaustiveness checks
- ✅ Combined them all in a typed event bus, and locked it down with strict mode
- ✅ Next lesson: TypeScript with React — put these types to work in components
Practice quiz
Which utility type builds a 'create' payload that is a User without its server-generated 'id'?
- Pick<User, 'id'>
- Partial<User>
- Omit<User, 'id'>
- Record<'id', User>
Answer: Omit<User, 'id'>. Omit<User, 'id'> keeps every property except the named ones, so it strips the server-managed 'id' from the input type.
Which mapped type makes every property of T read-only, and what built-in is it?
Adding the 'readonly' modifier in the mapped type produces the built-in Readonly<T>. Using '-readonly' would instead strip read-only.
In 'type R<F> = F extends (...a: any[]) => infer X ? X : never', what does X capture?
- The function's parameter types
- The function name
- The 'this' type of the function
- The function's return type — this is ReturnType<F>
Answer: The function's return type — this is ReturnType<F>. 'infer X' captures the function's return type inside the conditional's extends pattern. This is exactly the built-in ReturnType<F>.
What does 'infer' do inside a conditional type?
- Runs type inference at runtime
- Introduces a new type variable to capture part of a matched type
- Forces a type assertion
- Loops over the keys of a type
Answer: Introduces a new type variable to capture part of a matched type. 'infer' declares a fresh type variable inside the 'extends' clause of a conditional type, capturing a sub-type from the matched shape.
Why does a getter use '<T, K extends keyof T>(obj: T, key: K): T[K]' instead of typing key as string?
K extends keyof T rejects typo'd keys at compile time, and the lookup type T[K] yields the precise property type instead of 'any'.
What does the type operator 'keyof T' produce?
- The values of T
- A new object type
- A union of T's property names (keys)
- The number of keys in T
Answer: A union of T's property names (keys). 'keyof T' yields a union of the literal key names of T, which you can then constrain a generic parameter to.
In a switch over a discriminated union, what does an 'assertNever(x)' default branch buy you?
- Faster runtime dispatch
- Exhaustiveness — a forgotten case fails to compile because x is no longer 'never'
- Automatic handling of new cases
- Nothing; it is decorative
Answer: Exhaustiveness — a forgotten case fails to compile because x is no longer 'never'. If every case is handled, x narrows to 'never' and compiles. Add a variant and forget a case, and x is not 'never', so the build fails.
How does 'Record<K, V>' help when typing a dictionary or lookup table?
- It makes all properties optional
- It removes keys from an existing type
- It extracts the return type of a function
- It builds an object type whose keys are K and whose values are all V
Answer: It builds an object type whose keys are K and whose values are all V. Record<K, V> constructs an object type with the given key set K mapped to value type V — ideal for dictionaries and lookup tables.
What does the lookup type 'T[K]' give you?
- The key K itself
- The type of the property at key K within T
- A boolean for whether K exists
- All keys of T
Answer: The type of the property at key K within T. T[K] is an indexed-access (lookup) type: it resolves to the type of the property at key K in T.
Which tsconfig setting most reduces 'Cannot read property of undefined' crashes?
- noImplicitAny
- allowJs
- strictNullChecks (enabled by 'strict': true)
- esModuleInterop
Answer: strictNullChecks (enabled by 'strict': true). strictNullChecks makes null and undefined distinct types you must handle (via guards, ?., or ??) before using a value.
Continue this course
- Previous: tsconfig & Compiler Options
- Next: TypeScript with React