TypeScript Quiz: Generics, Narrowing and Utility Types Test
Test your TypeScript with 12 questions and three programs on inference, any and unknown, structural typing, discriminated unions, generics and narrowing.
- Course: JavaScript study plan
- Module: TypeScript preview
- Kind: Checkpoint — cleared at 70%
- Reading time: 24 min
- Runtime: Node 16
Checkpoint — TypeScript preview is the checkpoint that closes the TypeScript preview module: a graded quiz and whole-program exercises, passed at 70%.
Instructions
This checkpoint covers erased types and inference, literal types and widening, any/unknown/never; object types, interface versus type, structural typing and excess-property checks, intersections, discriminated unions and satisfies; function types, generics with constraints, keyof/indexed access and the utility types; narrowing, exhaustiveness, type guards, assertion functions and the escape hatches; and the toolchain — tsconfig, declaration files, builds, migration, runtime validation.
How it works. Twelve questions and three programs; 70% on the questions and every program accepted clears the module. The programs are JavaScript (the judge runs Node) that model the type system's own rules — inference and widening, structural assignability, utility types over schemas, narrowing — so you reason about what the compiler would do.
Before you start, make sure you can answer:
- What
const x = "a"versuslet x = "a"infers; whatas constdoes; the difference betweenany,unknownandnever. - Why a class name does not matter for assignability; when excess properties are an error; what a discriminated union's tag is for.
- What
K extends keyof TandT[K]express; howOmitis built; what a mapped type is. - Which checks narrow; why
typeof x === "object"keepsnull; whatx is Tandasserts x is Tdo; whenasis acceptable. - What
strictturns on; what a.d.tsis; whytsc --noEmitmust run in CI when esbuild builds; how to migrate JavaScript.
The programs are a schema builder that both validates values and prints the TypeScript type it would infer, an exhaustive state machine over discriminated-union states and events, and a declaration-file generator that infers .d.ts text from runtime values.
Common questions
What is the difference between any, unknown and never?
any accepts every value and disables checking wherever it is used; unknown accepts every value but must be narrowed before use; never has no values at all and marks functions that never return and unions that have been exhausted.
Why does a class name not matter for assignability in TypeScript?
TypeScript is structurally typed: assignability depends on the shape — the properties a value has — not on the name it was declared with. Two classes with the same public members are interchangeable unless they have private members.
Why must tsc --noEmit run in CI when esbuild builds the code?
esbuild strips TypeScript types without checking them, so a build can succeed with type errors in it. A separate tsc --noEmit step in CI is what actually type-checks the project and stops those errors shipping.
Exercises
A schema builder that validates and infers
Line 1 is a schema DSL: space-separated key:type fields where a trailing ? on the key means optional and a type is string, number, boolean, a T[] array, or a union of string literals "admin"|"user". Compile it, print type = { key: type; key?: type; … } in TypeScript syntax (a literal union in an array is parenthesised), then parse each following JSON line collecting every problem: <key>: required, <path>: expected <type>, got <string|number|boolean|null|array>, <path>: expected <"a" | "b">, got <JSON>, with array items addressed as tags[1]; a non-object root gives <root>: expected an object. Print ok <JSON> or error <problems joined by " | ">.
Example: name:string age:number email?:string tags:string[] role:"admin"|"user" →
type = { name: string; age: number; email?: string; tags: string[]; role: "admin" | "user" }
ok {"name":"Ada","age":36,"tags":["x"],"role":"admin"}
error age: expected number, got string | tags[1]: expected string, got number | role: expected "admin" | "user", got "guest"
error name: required
error <root>: expected an objectAn exhaustive state machine
States: { status: "idle" } | { status: "loading", attempt } | { status: "success", data } | { status: "failure", error, attempt }. Events: FETCH, RESOLVE <data>, REJECT <error>, RETRY, RESET. Write transition(state, event) switching on event.type: FETCH from idle → loading(1); RESOLVE from loading → success; REJECT from loading → failure (keeping the attempt); RETRY from failure → loading(attempt + 1); RESET → idle from anywhere; any other combination returns the same state object; an unknown event type reaches assertNever (unhandled: <JSON of the event>). Print <TYPE>: <before> -> <after> with (ignored) when the state object did not change, or <TYPE>: <error message>; describe states as idle, loading(attempt n), success(<JSON data>), failure(<error>, attempt n).
Example: FETCH, FETCH, REJECT timeout, RETRY, RESOLVE {"items":3}, RESET, EXPLODE →
FETCH: idle -> loading(attempt 1)
FETCH: loading(attempt 1) -> loading(attempt 1) (ignored)
REJECT: loading(attempt 1) -> failure(timeout, attempt 1)
RETRY: failure(timeout, attempt 1) -> loading(attempt 2)
RESOLVE: loading(attempt 2) -> success("{\"items\":3}")
RESET: success("{\"items\":3}") -> idle
EXPLODE: unhandled: {"type":"EXPLODE"}Generate a declaration file
The input is a JSON object describing a module's exports: a primitive, array or plain-object value is a constant; {"fn": <arity>} is a function; {"class": [fields], "methods": [names]} is a class. Infer types the way a declaration emitter would — string, number, boolean, null, arrays as T[] or (A | B)[] in first-seen order (unknown[] when empty), objects as { k: T; … } — and print one declaration per export in key order: export declare const NAME: T;, export declare function f(arg0: unknown, arg1: unknown): unknown;, and multi-line export declare class C { with field: unknown; and method(): unknown; lines and } (an empty class prints {} on one line).
Example (excerpt):
export declare const VERSION: string;
export declare const flags: { debug: boolean; level: string };
export declare const mixed: (number | string | null)[];
export declare function add(arg0: unknown, arg1: unknown): unknown;
export declare class Point {
x: unknown;
y: unknown;
dist(): unknown;
toString(): unknown;
}
export declare class Empty {}In this module: TypeScript preview
- What TypeScript adds — erased types, inference, and any versus unknown versus never
- Object types, interfaces, structural typing and discriminated unions
- Function types, generics, keyof and the utility types
- Narrowing, type guards, assertions — making the compiler trust a value
- TypeScript in practice — tsconfig, declaration files, builds, migration and the errors you will see
- Checkpoint — TypeScript preview (this lesson)
← TypeScript in practice — tsconfig, declaration files, builds, migration and the errors you will see · ES2020–ES2022 — the features that changed everyday code →