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.
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.
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.
Answer each in your head, then expand to check. No peeking first!
. Omit keeps every property except the ones you name, so it's the idiomatic way to strip server-managed fields from an input type.
{' '} — this is exactly the built-in . Adding -readonly instead would strip read-only from every property.
X captures the function's return type; this is the built-in . 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.
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.