Skip to content
TR

Phase 8 · Interview prep · Lesson 8.4

Interview

Mock 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

  1. Set a timer for 25 minutes: about one minute per question, like a real screening.
  2. Answer each question before looking at the options if you can, then pick. No scrolling back, no editor.
  3. Keep a tally: one point per correct answer. Note the question numbers you missed.
  4. At the end, use the score bands and the review table to decide what to study next.
  5. 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.

ScoreWhere you areWhat to do
23–25Interview-ready.Rehearse the open-ended answers below out loud and redo the hard type challenges.
18–22Solid. A few gaps.Review only the lessons for the questions you missed (table below), then retake in two days.
12–17Fundamentals are there; type-level work isn't yet.Redo Phase 5 and 6 lessons from the table, then the gotchas.
0–11Start from the core.Work through narrowing, generics and built-in utility types first, then retake.

What each question tests

QuestionTopicReview
1, 21unknown, catch variablesPrimitives and special types, Async and errors
2Inference of array literalsObject types
3Optional and default parametersFunctions
4Control-flow narrowingNarrowing
5Union vs intersectionUnions and literals
6Exhaustiveness checksDiscriminated unions
7Structural typing and private membersStructural typing
8private vs #privateClasses
9as const objectsEnums vs as const
10Type-only importsModules and declarations
11keyof constraintsGeneric constraints
12, 13Utility typesBuilt-in utility types
14Indexed accesskeyof, typeof and indexed access
15Mapped type modifiersMapped types
16inferConditional types and infer
17, 18DistributionDistributive conditional types
19Template literal typesTemplate literal types
20satisfiesType guards and assertions
22, 25Branding, declaration mergingNominal typing and merging
23VarianceVariance
24NoInferModern 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.

  • unknown is 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.parse results, catch variables, untrusted input.
  • never is 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.
  • any isn't really part of the lattice: it opts out of checking. It's assignable to and from everything, and it spreads, because every operation on any produces any.
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:

interfacetype
Can describeObject shapes (and function/constructor shapes)Anything: unions, tuples, primitives, mapped and conditional types
Extendingextends, with an error if members conflict& intersections, which silently produce never on conflicts
Declaration mergingYes: same-name interfaces mergeNo: duplicate names are an error
ImplementingA class can implement either, as long as it's an object typeSame

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: null and undefined are separate types. The most valuable one.
  • noImplicitAny: untyped parameters are errors instead of silent any.
  • strictFunctionTypes: function-typed parameters are checked contravariantly.
  • strictBindCallApply: bind, call and apply are type-checked.
  • strictPropertyInitialization: class fields must be initialised in the constructor.
  • noImplicitThis, alwaysStrict, useUnknownInCatchVariables (catch variables are unknown), and strictBuiltinIteratorReturn.

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), satisfies vs annotation (20), variance direction (23), and private making 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.