Interview question01 / 100
Practice session
TypeScript
One hundred questions on the type system, compiler options, generics, inference, and production patterns — forty beginner, forty mid-level, and twenty expert, including scenario-based items.
100 curated questions10 coding exercisesQuestions tab: swipe cards or keyboard
Practice for TypeScript
Progress
1 / 100
Arrow keys to move · Space or Enter to revealOn touch: swipe the card right or left
Hands-on
Coding exercises
Read the scenario, sketch your solution in the editor, reveal hints only when you need them, then compare against our reference implementation. Exercises run from beginner through mid-level.
In this track
10
scenarios
Narrow string | number
Scenario
Implement `formatValue(value: string | number): string` that:
• If `value` is a `number`, return its fixed two-decimal string using `toFixed(2)`.
• If it is a `string`, return it **trimmed** (empty string after trim becomes `"(empty)"`).
Use `typeof` narrowing—no type assertions (`as`) in the implementation.
- Keep the parameter type as a union; do not overload for this exercise.
Generic identity function
Scenario
Write `function identity<T>(value: T): T` that returns the argument unchanged.
Then declare `const a = identity("hi")` and `const b = identity(42)` in comments or a tiny demo block showing that `a` is inferred as `string` and `b` as `number` without explicit type arguments.
Add a second overload-free helper `asConstArray` that takes a readonly tuple type argument conceptually—or simply document that `identity` preserves literal types when passed `as const` values.
- No any; use a single type parameter T.
Pick and Partial for updates
Scenario
Given:
```ts
interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'member';
}
```
Define `type UserPreview = Pick<User, "id" | "name">`.
Implement `function applyUserPatch(user: User, patch: Partial<Pick<User, "name" | "email">>): User` that returns a **new** object: same `id` and `role`, but `name` and `email` overridden when provided in `patch`. Do not mutate `user`.
- Use object spread or explicit property copy.
- Return type must be User.
Discriminated union + exhaustiveness
Scenario
Model async results:
`type Ok<T> = { status: "ok"; data: T }`
`type Err = { status: "error"; message: string }`
`type Result<T> = Ok<T> | Err`
Implement `function describeResult<T>(r: Result<T>): string` using a `switch (r.status)` that returns a human-readable string for each case. Add `default` with `const _exhaustive: never = r` (or `return assertNever(r)`) so adding a new variant causes a compile error.
- Use switch(true) on r.status or switch (r.status).
- Include a never helper assertNever(x: never): never.
Type guard from unknown JSON
Scenario
Implement `function isUserRecord(x: unknown): x is { id: string; name: string }` that returns `true` only when `x` is a non-null object with string `id` and string `name` own properties (use `typeof` checks).
Implement `function parseUser(input: unknown): { id: string; name: string }` that throws `Error` with a clear message if `isUserRecord` fails.
Avoid `as` on `input` in the guard—use `Reflect.get` or equivalent after `in` checks.
- Use type predicate return type on isUserRecord.
- Check typeof x === "object" && x !== null before reading keys.
satisfies for config literals
Scenario
Declare `const routes` describing page paths. Each value must be `{ path: string; label: string }`. Use `as const satisfies Record<string, { path: string; label: string }>` so:
• Keys stay as literal key types where possible.
• Extra wrong properties or wrong value shapes are rejected.
Export `routes` and a helper type `RouteKey = keyof typeof routes`.
- Use satisfies, not only as const.
- Include at least three route entries (e.g. home, about, settings).
Branded primitive for IDs
Scenario
Create a **nominal** `UserId` type so plain `string` cannot be assigned accidentally:
`type UserId = string & { readonly __brand: unique symbol }` (declare `declare const userIdBrand: unique symbol;`).
Export `function createUserId(raw: string): UserId` that trims and throws if empty; export `function userIdEquals(a: UserId, b: UserId): boolean`.
Demonstrate in a comment that `const x: UserId = "u1" as any` is the wrong pattern—callers should use `createUserId`.
- Use unique symbol branding pattern.
- Do not use enums for this exercise.
Zod schema + inferred type
Scenario
Using Zod (`z` from `"zod"`), define `const ProductSchema` for:
• `sku`: string, min length 3
• `price`: number, non-negative
• `tags`: array of strings, default `[]`
Export `type Product = z.infer<typeof ProductSchema>`.
Export `function parseProduct(input: unknown): Product` using `.parse()` (let Zod throw on failure).
- Reference uses z.object / z.string().min(3) / z.number().min(0) / z.array(z.string()).default([]).
Conditional type: unwrap array
Scenario
Implement a utility type:
`type UnwrapArray<T>` — if `T` is an array of `infer U`, result is `U`; otherwise `T`.
Add `type Examples` in comments showing `UnwrapArray<string[]>` is `string` and `UnwrapArray<number>` is `number`.
Export a function stub `function head<T>(arr: T extends readonly (infer U)[] ? readonly U[] : never): T extends readonly (infer U)[] ? U | undefined : never` **or** a simpler version `function head<U>(arr: readonly U[]): U | undefined` and explain in the scenario that the exercise focuses on the **type** `UnwrapArray`.
- Use T extends ... ? infer U : ... pattern.
Mapped type: strip readonly
Scenario
Given `interface Config { readonly apiUrl: string; readonly timeout: number }`, define:
`type Mutable<T> = { -readonly [K in keyof T]: T[K] }`
Export a function `function cloneMutableConfig(c: Config): Mutable<Config>` that returns a shallow copy with writable properties (same values).
Demonstrate that `Mutable<Config>` allows reassignment of `apiUrl` on the result object.
- Use minus readonly (-readonly) in mapped type.
- Shallow copy only—nested objects would still be readonly references if any.