Checkpoint: Applied TypeScript
This checkpoint consolidates the applied half of the course — declaration merging, namespaces, ambient types, async types, DOM types, generic defaults, function types, variadic tuples, advanced utilities, decorators, and migration — into one build challenge and a quiz.
Learn Checkpoint: Applied TypeScript 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. Recap: The Applied Toolkit
Here's the whole applied half on one page. Each idea below earned its own lesson; together they're what you reach for when typing real code.
2. 🏗️ Build Challenge: A Typed Event Bus
Build a small event bus : createEventBus() with on(event, fn) (returning an unsubscribe function) and emit(event, payload) . Get the runtime behaviour right first, then reveal the fully typed solution to see how a generic event map makes it type-safe per event.
First, the runnable plain-JS solution (this is exactly what the typed version compiles to):
And the fully typed TypeScript, using a generic event map so each event's payload is checked:
3. 📝 Checkpoint Quiz
Answer each in your head, then expand to check. No peeking first!
Interfaces are open and designed to be extended, so TypeScript merges their members via declaration merging. Type aliases are closed definitions — declaring one twice is a duplicate-identifier error. This is why library-facing, augmentable shapes are usually interfaces.
number . Awaited<T> recursively unwraps every Promise layer to the final resolved value type, exactly as await does at runtime.
Because the selector might not match anything. The | null forces you to handle the "not found" case before using the element. Use the generic ( ) to get a more specific element type.
Only when nothing else determines the type parameter — the caller didn't supply it and TypeScript couldn't infer it from an argument. If inference can find a type, the inferred type wins over the default.
It receives the original method plus a context object, and returns a replacement function (or nothing). At its core it's a higher-order function that wraps the method — for example to add logging or timing.
Add // @ts-check at the top of a .js file (or set allowJs + checkJs ) and describe types with JSDoc. You get editor type-checking with zero runtime change, before committing to a .ts rename or build step.
Stretch goal — add a once(event, fn) that auto-unsubscribes after the first emit. Start from your build-challenge solution, add the method, and confirm the listener fires only once.
Practice quiz
Why do two same-name interfaces merge, but two same-name 'type' aliases error?
- Interfaces are global and type aliases are local
- Type aliases merge but interfaces error
- Interfaces are open and support declaration merging; type aliases are closed, so re-declaring one is a duplicate-identifier error
- Both merge automatically
Answer: Interfaces are open and support declaration merging; type aliases are closed, so re-declaring one is a duplicate-identifier error. Interfaces are designed to be extended, so TypeScript merges their members. A type alias is a closed definition; declaring it twice is a duplicate-identifier error.
What is 'Awaited<Promise<Promise<number>>>'?
- number
- Promise<number>
- Promise<Promise<number>>
- never
Answer: number. Awaited<T> recursively unwraps every Promise layer to the final resolved value type — here, number — exactly as 'await' does at runtime.
What is the typical return type of 'document.querySelector("#x")'?
- Element
- HTMLElement
The selector might match nothing, so the type is 'Element | null', forcing you to handle the not-found case. Use the generic form for a more specific element type.
When does a generic's DEFAULT type parameter actually apply?
- Always, overriding inference
- Only when the caller didn't supply it and TypeScript couldn't infer it from an argument
- Only in strict mode
- Never — defaults are not allowed on generics
Answer: Only when the caller didn't supply it and TypeScript couldn't infer it from an argument. A default applies only when nothing else determines the type parameter. If inference can find a type from an argument, the inferred type wins over the default.
How does typing an event bus with '<M>' and '<K extends keyof M>' help?
- It makes emit/on type-safe per event, so each event's payload is checked against the event map
- It makes the bus run faster
- It removes the need for handlers
- It allows any payload for any event
Answer: It makes emit/on type-safe per event, so each event's payload is checked against the event map. A generic event map M plus 'K extends keyof M' ties each event key to its payload type, so emit('login', ...) demands the right shape and on() infers the handler arg.
What does 'Promise.all' return when given a tuple of differently-typed promises?
- An array of 'any'
- A single merged object
- A promise of a tuple preserving each element's resolved type and position
- void
Answer: A promise of a tuple preserving each element's resolved type and position. Promise.all over a tuple resolves to a tuple of the awaited types in the same positions, so destructuring keeps each value precisely typed.
What does a method decorator receive and return in TS 5 (stage-3 decorators)?
- The class instance and returns void only
- The original method plus a context object, and returns a replacement function (or nothing)
- Only the property name
- The prototype and a property descriptor to mutate
Answer: The original method plus a context object, and returns a replacement function (or nothing). A stage-3 method decorator receives the original method and a context object and may return a replacement function — at its core a higher-order function wrapping the method.
What does the 'declare' keyword do in an ambient declaration?
- It generates runtime JavaScript
- It imports a module
- It freezes an object
- It describes the types of existing runtime code without emitting any output
Answer: It describes the types of existing runtime code without emitting any output. 'declare' provides type information for code that already exists at runtime (globals, libraries) and produces no emitted JavaScript. DefinitelyTyped (@types) ships many such declarations.
What do 'Parameters<F>' and 'ReturnType<F>' extract from a function type F?
- Its name and length
- A tuple of its parameter types, and its return type, respectively
- Its 'this' type and prototype
- Its decorators
Answer: A tuple of its parameter types, and its return type, respectively. Parameters<F> yields a tuple of F's argument types; ReturnType<F> yields F's return type. Both are built with conditional types and 'infer'.
What is a safe first step to adopt TypeScript without renaming files?
- Rewrite everything as '.ts' at once
- Type everything as 'any'
- Add '// @ts-check' to a '.js' file (or set allowJs + checkJs) and describe types with JSDoc
- Disable strict mode permanently
Answer: Add '// @ts-check' to a '.js' file (or set allowJs + checkJs) and describe types with JSDoc. '// @ts-check' plus JSDoc (or allowJs + checkJs) gives editor type-checking with zero runtime change, before committing to a '.ts' rename or build step.