Phase 8 · Interview prep · Lesson 8.4
InterviewMock interview
A 25-question timed mock interview across the whole language, a self-scoring guide that points you to what to review, and four open-ended questions with model answers.
40 min
This is the dress rehearsal. Twenty-five questions of rising difficulty, covering every phase of the course: predict the type, spot which line fails, pick the right utility, explain a concept. Then four open-ended questions of the kind that close a real interview.
How to use this
- Set a timer for 25 minutes: about one minute per question, like a real screening.
- Answer each question before looking at the options if you can, then pick. No scrolling back, no editor.
- Keep a tally: one point per correct answer. Note the question numbers you missed.
- At the end, use the score bands and the review table to decide what to study next.
- For the open-ended questions, answer out loud for about two minutes each, then compare with the model answer.
Part 1: 25 questions
Warm-up (1–8)
1.
Quick check
Which type accepts any value but forces you to check it before using it?
2.
Quick check
What is the type of mixed?
const mixed = [1, "a"];3.
Quick check
Which declaration does the compiler reject?
4.
Quick check
What is the type of rest on the last line?
function handle(value: string | number | boolean) {
if (typeof value === "string" || typeof value === "boolean") return;
const rest = value;
}5.
Quick check
What is Shared?
type A = "a" | "b";
type B = "b" | "c";
type Shared = A & B;6.
Quick check
A teammate adds | { kind: 'triangle'; base: number } to Shape. What happens?
type Shape = { kind: "circle"; radius: number } | { kind: "square"; size: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.size ** 2;
default: {
const unreachable: never = shape;
throw new Error(`Unknown shape: ${JSON.stringify(unreachable)}`);
}
}
}7.
Quick check
const cat: Cat = new Dog(); compiles today. Which change makes it an error?
class Cat {
name = "";
}
class Dog {
name = "";
}
const cat: Cat = new Dog();8.
Quick check
Which of these is enforced by the JavaScript runtime, not only by the compiler?
Core (9–17)
9.
Quick check
What is Role?
const Roles = { Admin: "admin", User: "user" } as const;
type Role = (typeof Roles)[keyof typeof Roles];10.
Quick check
What does import type { User } from './user'; produce in the emitted JavaScript?
11.
Quick check
What is the type of age?
type User = { name: string; age: number };
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user: User = { name: "Ada", age: 36 };
const age = getProp(user, "age");12.
Quick check
What is Left?
type Left = Exclude<"a" | "b" | "c", "a" | "d">;13.
Quick check
Which utility type turns { id: number; name: string } into { readonly id: number; readonly name: string }?
14.
Quick check
What is Element?
type Pair = [string, number];
type Element = Pair[number];15.
Quick check
What is Clean?
type Clean<T> = { -readonly [K in keyof T]-?: T[K] };
type Result = Clean<{ readonly a?: number }>;16.
Quick check
What is Result?
type Unwrap<T> = T extends Promise<infer V> ? V : T;
type Result = Unwrap<Promise<Promise<number>>>;17.
Quick check
What is R?
type IsString<T> = T extends string ? true : false;
type R = IsString<string | number>;Advanced (18–25)
18.
Quick check
Using the same IsString, what is IsString<never>?
19.
Quick check
What is Handlers?
type Handlers = `on${Capitalize<"click" | "focus">}`;20.
Quick check
Which line is a compile error?
const routes = { home: "/", about: "/about" } satisfies Record<string, string>;21.
Quick check
Under strict, what is the type of error inside catch (error)?
22.
Quick check
Which definition makes a plain string not assignable to UserId?
23.
Quick check
With strictFunctionTypes on, Dog extends Animal. Which assignment is rejected?
24.
Quick check
What happens on the last line?
function trafficLight<C extends string>(colors: C[], initial?: NoInfer<C>) {
return { colors, initial };
}
const good = trafficLight(["red", "yellow", "green"], "red");
// trafficLight(["red", "yellow", "green"], "blue");25.
Quick check
One file declares interface Box { size: number } and then interface Box { size: string }. What happens?
Score yourself
Count your correct answers out of 25.
| Score | Where you are | What to do |
|---|---|---|
| 23–25 | Interview-ready. | Rehearse the open-ended answers below out loud and redo the hard type challenges. |
| 18–22 | Solid. A few gaps. | Review only the lessons for the questions you missed (table below), then retake in two days. |
| 12–17 | Fundamentals are there; type-level work isn't yet. | Redo Phase 5 and 6 lessons from the table, then the gotchas. |
| 0–11 | Start from the core. | Work through narrowing, generics and built-in utility types first, then retake. |
What each question tests
| Question | Topic | Review |
|---|---|---|
| 1, 21 | unknown, catch variables | Primitives and special types, Async and errors |
| 2 | Inference of array literals | Object types |
| 3 | Optional and default parameters | Functions |
| 4 | Control-flow narrowing | Narrowing |
| 5 | Union vs intersection | Unions and literals |
| 6 | Exhaustiveness checks | Discriminated unions |
| 7 | Structural typing and private members | Structural typing |
| 8 | private vs #private | Classes |
| 9 | as const objects | Enums vs as const |
| 10 | Type-only imports | Modules and declarations |
| 11 | keyof constraints | Generic constraints |
| 12, 13 | Utility types | Built-in utility types |
| 14 | Indexed access | keyof, typeof and indexed access |
| 15 | Mapped type modifiers | Mapped types |
| 16 | infer | Conditional types and infer |
| 17, 18 | Distribution | Distributive conditional types |
| 19 | Template literal types | Template literal types |
| 20 | satisfies | Type guards and assertions |
| 22, 25 | Branding, declaration merging | Nominal typing and merging |
| 23 | Variance | Variance |
| 24 | NoInfer | Modern features |
Part 2: explain it
Real interviews usually end with open questions. Answer each one out loud in about two minutes, then compare. The model answers are what a strong candidate says, not the only right answer.
1. any vs unknown vs never
Model answer
They're the three special types at the edges of the type system.
unknownis the top type: every value is assignable to it, but you can't do anything with it until you narrow it. It's what you use at boundaries:JSON.parseresults,catchvariables, untrusted input.neveris the bottom type: no value has it, and it's assignable to everything. It appears as the return type of functions that throw, as an empty union, and in exhaustiveness checks.anyisn't really part of the lattice: it opts out of checking. It's assignable to and from everything, and it spreads, because every operation onanyproducesany.
function parse(json: string): unknown {
return JSON.parse(json);
}
const value = parse("42");
// @ts-expect-error -- 'value' is of type 'unknown'.
value.toFixed();
if (typeof value === "number") value.toFixed(); // fine after narrowing
function fail(message: string): never {
throw new Error(message);
}The one-liner to end on: "unknown is the safe any: use it whenever you don't know the type yet, and narrow."
2. type vs interface
Model answer
For plain object shapes they're mostly interchangeable. The real differences:
interface | type | |
|---|---|---|
| Can describe | Object shapes (and function/constructor shapes) | Anything: unions, tuples, primitives, mapped and conditional types |
| Extending | extends, with an error if members conflict | & intersections, which silently produce never on conflicts |
| Declaration merging | Yes: same-name interfaces merge | No: duplicate names are an error |
| Implementing | A class can implement either, as long as it's an object type | Same |
Declaration merging is why library authors use interfaces for things users augment (like Window or a plugin registry). A common team rule: interface for object shapes, type for everything else, or just be consistent. Interfaces used to be faster for the compiler in large extends hierarchies, since their relationships are cached.
3. How would you type an event emitter?
Model answer
Start from a map of event names to payload types, then make on and emit generic over the event name so the payload is looked up with an indexed access type:
type Listener<T> = (payload: T) => void;
class Emitter<Events extends Record<string, unknown>> {
private listeners: { [E in keyof Events]?: Listener<Events[E]>[] } = {};
on<E extends keyof Events>(event: E, listener: Listener<Events[E]>): () => void {
const list = (this.listeners[event] ??= []);
list.push(listener);
return () => {
this.listeners[event] = list.filter((l) => l !== listener);
};
}
emit<E extends keyof Events>(event: E, payload: Events[E]): void {
this.listeners[event]?.forEach((listener) => listener(payload));
}
}
type AppEvents = {
login: { userId: string };
logout: undefined;
};
const emitter = new Emitter<AppEvents>();
emitter.on("login", ({ userId }) => console.log(userId));
emitter.emit("login", { userId: "u1" });
// @ts-expect-error -- Argument of type '"signup"' is not assignable to parameter of type 'keyof AppEvents'.
emitter.emit("signup", {});Points to mention: the listener's parameter type is inferred from the event name, so callers never annotate; on returns an unsubscribe function; and the next refinement is making the payload optional for events whose payload is undefined (with a conditional rest parameter like ...args: Events[E] extends undefined ? [] : [Events[E]]).
4. What does strict do, and what would you add?
Model answer
strict: true turns on a family of flags, and any flag added to the family later turns on automatically when you upgrade:
strictNullChecks:nullandundefinedare separate types. The most valuable one.noImplicitAny: untyped parameters are errors instead of silentany.strictFunctionTypes: function-typed parameters are checked contravariantly.strictBindCallApply:bind,callandapplyare type-checked.strictPropertyInitialization: class fields must be initialised in the constructor.noImplicitThis,alwaysStrict,useUnknownInCatchVariables(catch variables areunknown), andstrictBuiltinIteratorReturn.
What I'd add on top, and why: noUncheckedIndexedAccess (array and record reads include undefined), exactOptionalPropertyTypes (? means "may be missing", not "may be undefined"), noImplicitOverride, noFallthroughCasesInSwitch, and for modern build setups verbatimModuleSyntax or isolatedModules. On an existing codebase, I'd turn flags on one at a time and fix errors per flag rather than flipping strict in one go.
Recap
- Score: 23+ ready, 18–22 review your misses, below that go back to the core lessons in the table.
- The questions people miss most: distribution over
never(18),satisfiesvs annotation (20), variance direction (23), andprivatemaking classes nominal (7). - For open questions: definition, why, example, trade-off, in about two minutes.
- Retake the quiz in two days. Only a second pass shows whether the review stuck.