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.