TypeScript Interview Mock sample questions with answers

10 questions from the TypeScript Interview Mock practice bank, spread across its domains. Pick your answer, then open the explanation to see why each option is right or wrong.

  1. Question 1Generics and Type-Level Programming

    This type resolves to 0 | 1, because boolean is the union true | false and the conditional distributes over it.

    type A = boolean extends true ? 1 : 0;
    • A

      True

    • B

      False

    Show the answer and explanation

    Answer: B

    Distribution happens only when the checked type is a generic type parameter that is later instantiated with a union. Here boolean appears literally, so the check is true | false extends true, which fails: the result is 0. With type IsTrue<T> = T extends true ? 1 : 0, IsTrue<boolean> would be 0 | 1.

    Why the other options are wrong

    • A. It is false: distribution needs a naked type parameter, and here boolean is written directly.

  2. Question 2Type System Foundations

    Given interface Cfg { port: number }, which three lines report an excess property error for host? (Choose three.)

    function make(): Cfg { return { port: 1, host: 'x' }; }          // 1
    const viaFn: () => Cfg = () => ({ port: 1, host: 'x' });         // 2
    const list: Cfg[] = [{ port: 1, host: 'x' }];                    // 3
    const nested: { cfg: Cfg } = { cfg: { port: 1, host: 'x' } };    // 4
    const extra = { host: 'x' };
    const spread: Cfg = { port: 1, ...extra };                       // 5

    Choose 3.

    • A

      Line 1

    • B

      Line 2

    • C

      Line 3

    • D

      Line 4

    • E

      Line 5

    Show the answer and explanation

    Answer: A and C and D

    Freshness follows the literal wherever it is directly checked against a declared type: return statements of annotated functions, array elements and nested properties. Two common gaps: an arrow function assigned to a function type infers its own return type first, so line 2 compiles (annotate (): Cfg => to get the check), and spread properties are never treated as excess.

    Why the other options are wrong

    • B. The arrow's return type is inferred from the literal, then the function types are compared, so no excess check runs.

    • E. Properties that arrive through a spread are not checked for excess.

  3. Question 3Narrowing, Functions and Classes

    Shape has three members (circle, square, triangle). What does the compiler report?

    function area(s: Shape): number {
      switch (s.kind) {
        case "circle": return s.r;
        case "square": return s.s;
        default:
          s satisfies never;
          throw new Error("unreachable");
      }
    }
    • A

      Nothing, because satisfies only checks object literals

    • B

      An error saying that the function lacks an ending return statement

    • C

      TS1360: type { kind: "triangle"; b: number; } does not satisfy the expected type never

    • D

      A warning that s is unused in the default branch, which noUnusedLocals reports

    Show the answer and explanation

    Answer: C

    expr satisfies never (TypeScript 4.9+) is a compact exhaustiveness check that needs no helper variable. When every case is handled it compiles; when one is missing, TypeScript names the leftover type. Keep the throw so the function still has a defined result at runtime.

    Why the other options are wrong

    • A. satisfies works on any expression, including identifiers.

    • B. The throw ends that path, so every path returns or throws.

    • D. This is a type error, not an unused-variable report.

  4. Question 4Compiler, Declarations and Modules

    This global declaration file worked for months. After someone added the first line, window.appVersion now fails with TS2339 everywhere. Why, and what is the fix?

    // globals.d.ts
    import type { Foo } from "./foo";
    
    interface Window {
      appVersion: string;
      foo: Foo;
    }
    • A

      Type-only imports are not allowed in declaration files; move the Foo import into each file that uses it

    • B

      The interface needs export now, so change it to export interface Window so that the merge happens

    • C

      The import made the file a module, so Window is now module-local; wrap it in declare global { … }

    • D

      The DOM lib is no longer loaded once any declaration file imports a module, so add "dom" to lib again

    Show the answer and explanation

    Answer: C

    Any top-level import or export turns a file into a module, and declarations in a module are scoped to it. The interface Window above therefore declares a new, unrelated local type instead of merging with the global Window. The fix keeps the import and moves the augmentation into declare global { interface Window { … } }, which is only legal inside a module.

    Why the other options are wrong

    • A. Imports are allowed in .d.ts files. The problem is what the import does to the file's scope.

    • B. Exporting it creates a module-local interface named Window; it still would not merge with the global one.

    • D. The DOM lib is still loaded; TS2339 says Window exists but lacks the property.

  5. Question 5Applied TypeScript

    Which statements about satisfies are true? (Choose two.)

    Choose 2.

    • A

      It reports an error if the expression doesn't conform to the type, such as a misspelled key

    • B

      The variable keeps the expression's own inferred type rather than the checked type

    • C

      Like as, it lets you claim a type the value doesn't actually have

    • D

      It adds a runtime validation step that throws if the shape is wrong

    • E

      It always keeps string literals, even where the target type is plain string

    Show the answer and explanation

    Answer: A and B

    expr satisfies T checks expr against T (with excess-property checks) but leaves the variable typed as expr's own inferred type. It doesn't make a value conform the way as pretends to, and it doesn't freeze literals by itself. Combine it with as const (as const satisfies T) when you need exact literal values.

    Why the other options are wrong

    • C. satisfies is a check, not an assertion. A non-conforming value is an error.

    • D. Like every type operator, it is erased during compilation.

    • E. Values contextually typed by string still widen to string; add as const to keep literals.

  6. Question 6Generics and Type-Level Programming

    How many members does the union Combo have?

    type Combo = `${'a' | 'b'}-${'x' | 'y' | 'z'}`;
    • A

      5, one per union member across both placeholders

    • B

      6, the cross product of 2 × 3 combinations

    • C

      1, because it becomes the single literal 'a|b-x|y|z'

    • D

      2, one for each member of the first placeholder only

    Show the answer and explanation

    Answer: B

    Placeholders in a template literal type each distribute, so the result is the cross product: 2 × 3 = 6 literals ('a-x' | 'a-y' | 'a-z' | 'b-x' | 'b-y' | 'b-z'). With large unions this grows fast, and TypeScript errors once a union passes 100,000 members.

    Why the other options are wrong

    • A. Members are not just listed; every combination of the two placeholders is produced.

    • C. Unions are expanded into separate literals, not joined into one string.

    • D. Each placeholder distributes, so both contribute to the count.

  7. Question 7Type System Foundations

    With noUncheckedIndexedAccess: true, what are the types of first, val, s and t0?

    declare const arr: string[];
    declare const dict: Record<string, number>;
    declare const tup: [string, number];
    
    const first = arr[0];
    const val = dict['k'];
    for (const s of arr) { /* ... */ }
    const t0 = tup[0];
    • A

      string | undefined, number | undefined, string | undefined, string | undefined

    • B

      string | undefined, number | undefined, string, string

    • C

      string, number | undefined, string, string

    • D

      string | undefined, number, string, string

    Show the answer and explanation

    Answer: B

    noUncheckedIndexedAccess adds | undefined to reads through index signatures, including array element access, because the key might not exist. Values produced by iteration cannot be missing, so s stays string, and a tuple's declared position tup[0] is known to exist, so it stays string. The flag is not part of strict; you enable it separately.

    Why the other options are wrong

    • A. Iteration and known tuple positions are not affected by the flag.

    • C. Array element reads are index reads too, so arr[0] also gets | undefined.

    • D. Record<string, number> has a string index signature, so it is affected too.

  8. Question 8Narrowing, Functions and Classes

    Given class P1 { f(x: string) { return x; } }, which three subclasses compile under strict? (Choose three.)

    Choose 3.

    • A

      class P3 extends P1 { f(x: "a") { return x; } }

    • B

      class P4 extends P1 { f(x: string | number) { return String(x); } }

    • C

      class P5 extends P1 { f() { return "none"; } }

    • D

      class P6 extends P1 { f(x: string, y: string) { return x + y; } }

    • E

      class P7 extends P1 { f(x: string) { return 1; } }

    Show the answer and explanation

    Answer: A and B and C

    An override must be assignable to the base member. Returning a different type and requiring more parameters both fail with TS2416. Taking fewer or wider parameters is safe. A narrower parameter ("a") is unsound but still accepted, because methods are compared bivariantly even under strictFunctionTypes. Only function-typed properties get the strict check.

    Why the other options are wrong

    • D. A second required parameter would break callers that pass one argument (TS2416).

    • E. The return type number is not assignable to string (TS2416).

  9. Question 9Compiler, Declarations and Modules

    This file is in a project with "allowJs": true, "checkJs": true. Which two statements are true? (Choose two.)

    // math.js
    /** @param {number} a @param {number} b */
    export function add(a, b) { return a + b; }
    add(1, "2");
    /** @type {string} */
    export let label = 5;

    Choose 2.

    • A

      add(1, "2") is reported, because the JSDoc @param types are enforced

    • B

      let label = 5 is reported, because @type {string} declares the variable as a string

    • C

      Nothing is reported, because type errors are never shown for .js files

    • D

      The file must be renamed to .ts before JSDoc types are read

    • E

      // @ts-ignore and // @ts-expect-error don't work in .js files

    Show the answer and explanation

    Answer: A and B

    allowJs lets .js files join the program, and checkJs type-checks them using inference plus JSDoc (@param, @type, @typedef, @template). This is a common first step in migration: you get errors in existing JavaScript before renaming a single file. A // @ts-check comment enables checking for one file at a time.

    Why the other options are wrong

    • C. That is true with allowJs alone; checkJs turns checking on.

    • D. JSDoc types are read in .js files; that is the point of checkJs.

    • E. Both directives work in checked JavaScript files.

  10. Question 10Applied TypeScript

    An API client is typed from a route map:

    type Routes = {
      "/users": { res: User[] };
      "/users/:id": { res: User };
    };
    declare function get<P extends keyof Routes>(path: P): Promise<Routes[P]["res"]>;

    What type does get("/users") have, and what happens with get("/posts")?

    • A

      Promise<User | User[]>, and get("/posts") returns Promise<never>

    • B

      Promise<User[]>, and get("/posts") is a compile error

    • C

      Promise<User[]>, and get("/posts") compiles with Promise<unknown>

    • D

      Promise<Routes["/users"]>, and get("/posts") is a compile error

    Show the answer and explanation

    Answer: B

    A route map turns path strings into a typed contract: the inferred literal P selects the right response type, and unknown paths fail to compile. Generating such maps from an OpenAPI spec or sharing them between client and server is how tools like tRPC and typed fetch wrappers work.

    Why the other options are wrong

    • A. P is inferred as the literal "/users", so indexing picks exactly that route's response.

    • C. An invalid key isn't widened to unknown; the extends keyof Routes constraint rejects it.

    • D. The return type indexes one level deeper, into ["res"], giving User[].

Practise all 479 TS questions

Start with the free 15-question diagnostic. It shows where to focus, and your results carry over if you sign up.

Go to TS