pencils ready ✎

TypeScript MCQs multiple-choice questions with answers & explanations

All 28 TypeScript quiz questions on one page. Pick an answer in your head, then open Show answer to check it and read why. Want a score and a timer? Take them as a quiz instead.

  1. 1.

    With strict on, which line fails to compile?

    easy
    let a: any = 'hello';
    let u: unknown = 'hello';
    
    a.toUpperCase(); // A
    u.toUpperCase(); // B
    if (typeof u === 'string') u.toUpperCase(); // C
    1. AOnly A
    2. BOnly B
    3. CB and C
    4. DNone of them
    Show answer

    Answer: B (Only B)

    any switches checking off, so A compiles (and would crash at runtime if a were a number). unknown accepts any value but allows no operations until you narrow it, so B fails with 'u' is of type 'unknown'. The typeof check in C narrows u to string, so it compiles.

  2. 2.

    What does the compiler do with these declarations?

    easy
    interface User { name: string }
    interface User { age: number }
    
    type Point = { x: number };
    type Point = { y: number };
    1. ABoth pairs merge
    2. BBoth pairs are Duplicate identifier errors
    3. CUser merges to have both name and age; Point is a Duplicate identifier error
    4. DUser is a Duplicate identifier error; Point merges to have both x and y
    Show answer

    Answer: C (User merges to have both name and age; Point is a Duplicate identifier error)

    Interfaces are open: declarations with the same name in the same scope merge their members, which is how libraries let you augment types like Window. Type aliases are closed, so declaring Point twice is error TS2300, Duplicate identifier. To combine type aliases, use an intersection.

  3. 3.

    What is ColorName?

    easy
    const colors = { red: '#f00', green: '#0f0' };
    type ColorName = keyof typeof colors;
    1. A'red' | 'green'
    2. Bstring
    3. C'#f00' | '#0f0'
    4. D{ red: string; green: string }
    Show answer

    Answer: A ('red' | 'green')

    typeof colors turns the value into its type, { red: string; green: string }, and keyof takes the union of its keys. For the values you would write (typeof colors)[keyof typeof colors], which is just string here unless you add as const.

  4. 4.

    Given type User = { id: number; name: string; email: string }, which type is exactly { id: number; name: string }?

    easy
    1. AOmit<User, 'id' | 'name'>
    2. BPartial<User>
    3. CPick<User, 'id' | 'name'>
    4. DExtract<User, 'id' | 'name'>
    Show answer

    Answer: C (Pick<User, 'id' | 'name'>)

    Pick<T, K> keeps only the listed keys. Omit<User, 'id' | 'name'> does the opposite and leaves { email: string }. Partial keeps every key but makes them optional, and Extract filters union members, so here it produces never.

  5. 5.

    What types does TypeScript infer for a, b and c.greeting?

    easy
    let a = 'hello';
    const b = 'hello';
    const c = { greeting: 'hello' };
    1. A'hello', 'hello', 'hello'
    2. Bstring, string, string
    3. Cstring, 'hello', 'hello'
    4. Dstring, 'hello', string
    Show answer

    Answer: D (string, 'hello', string)

    A const primitive can never change, so it keeps the literal type 'hello'. A let can be reassigned, so it widens to string. Object properties are mutable too, so c.greeting widens to string even though c is const. Use as const to keep the literals.

  6. 6.

    What is Exclude<'a' | 'b' | 'c', 'a' | 'c'>?

    easy
    1. A'a' | 'c'
    2. B'b'
    3. Cnever
    4. D'a' | 'b' | 'c'
    Show answer

    Answer: B ('b')

    Exclude<T, U> removes every member of the union T that is assignable to U. It is the distributive conditional type T extends U ? never : T applied to each member, and the resulting nevers vanish from the union, leaving 'b'. Extract keeps the matching members instead.

  7. 7.

    Which call is a compile error?

    easy
    function total(nums: readonly number[]) {
      return nums.length;
    }
    function append(nums: number[]) {
      nums.push(0);
    }
    const mutable = [1, 2];
    const frozen: readonly number[] = [1, 2];
    total(mutable); // 1
    total(frozen);  // 2
    append(frozen); // 3
    1. AOnly 1
    2. BOnly 3
    3. C2 and 3
    4. DNone of them
    Show answer

    Answer: B (Only 3)

    A mutable number[] can go where a readonly number[] is expected, because the function only promises not to mutate it. The reverse is unsafe: append could push into an array the caller declared read-only, so call 3 errors. Typing parameters as readonly lets a function accept both kinds.

  8. 8.

    What is Role?

    mid
    const roles = ['admin', 'editor'] as const;
    type Role = (typeof roles)[number];
    1. Astring
    2. Bstring[]
    3. Creadonly ['admin', 'editor']
    4. D'admin' | 'editor'
    Show answer

    Answer: D ('admin' | 'editor')

    as const turns the array into a readonly tuple of literal types, and indexing it with number gives the union of its element types. Without as const, roles would be string[] and Role just string. The pattern gives you a runtime list and a matching type from one source.

  9. 9.

    Why does items.length fail to compile under strict?

    mid
    function count(items: string[] | null) {
      if (typeof items === 'object') {
        return items.length;
      }
      return 0;
    }
    1. Atypeof cannot narrow arrays
    2. Btypeof null is 'object', so items is still string[] | null inside the if
    3. Clength does not exist on string[]
    4. DIt compiles fine
    Show answer

    Answer: B (typeof null is 'object', so items is still string[] | null inside the if)

    Because typeof null === 'object' at runtime, TypeScript keeps null in the narrowed type and reports 'items' is possibly 'null'. Use if (items), items !== null or Array.isArray(items) instead.

  10. 10.

    The triangle variant was just added to Shape. Where does the compiler report an error?

    mid
    type Shape =
      | { kind: 'circle'; r: number }
      | { kind: 'square'; size: number }
      | { kind: 'triangle'; base: number; height: number };
    
    function area(s: Shape): number {
      switch (s.kind) {
        case 'circle': return Math.PI * s.r ** 2;
        case 'square': return s.size ** 2;
        default: {
          const unreachable: never = s;
          return unreachable;
        }
      }
    }
    1. ANowhere; area returns undefined for triangles at runtime
    2. BOn const unreachable: never = s
    3. COn switch (s.kind)
    4. DOn the return type, because not all code paths return a value
    Show answer

    Answer: B (On const unreachable: never = s)

    In the default branch, s is narrowed to whatever the cases did not handle. Before the change that was never; now it is the triangle type, which is not assignable to never, so the assignment errors. This exhaustiveness check turns a forgotten variant into a compile error.

  11. 11.

    Which assignment is a compile error?

    mid
    interface Options { verbose: boolean }
    
    const a: Options = { verbose: true, silent: true }; // 1
    const extra = { verbose: true, silent: true };
    const b: Options = extra;                           // 2
    1. ABoth
    2. BNeither
    3. COnly 2
    4. DOnly 1
    Show answer

    Answer: D (Only 1)

    Excess property checking applies only to fresh object literals assigned directly to a typed target, because an unknown key there is most likely a typo. extra is not fresh, and under structural typing it has everything Options needs, so assigning it is fine.

  12. 12.

    Which line fails to compile?

    mid
    type Config = Record<string, string | number>;
    
    const annotated: Config = { port: 3000 };
    const checked = { port: 3000 } satisfies Config;
    
    annotated.port.toFixed(); // A
    checked.port.toFixed();   // B
    1. AOnly B
    2. BOnly A
    3. CBoth
    4. DNeither
    Show answer

    Answer: B (Only A)

    An annotation widens the variable to Config, so annotated.port is string | number, which has no toFixed. satisfies checks the object against Config but keeps its inferred type, so checked.port is still number.

  13. 13.

    What does this log?

    mid
    enum Direction { Up, Down }
    console.log(Direction[0], Object.keys(Direction).length);
    1. AUp 2
    2. Bundefined 2
    3. CUp 4
    4. D0 4
    Show answer

    Answer: C (Up 4)

    Numeric enums compile to an object with a reverse mapping, { 0: 'Up', 1: 'Down', Up: 0, Down: 1 }. So Direction[0] is 'Up' and the object has 4 keys. String enum members get no reverse mapping.

  14. 14.

    What is the inferred type of age?

    mid
    function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
      return obj[key];
    }
    const user = { name: 'Ada', age: 36 };
    const age = getProp(user, 'age');
    1. Anumber
    2. Bstring | number
    3. Cunknown
    4. Dany
    Show answer

    Answer: A (number)

    K is inferred as the literal 'age', and the return type T[K] is an indexed access type, so it resolves to number. Returning T[keyof T] instead would give string | number. The constraint also makes getProp(user, 'email') a compile error.

  15. 15.

    With strict enabled, which assignment is an error?

    mid
    class Animal { name = '' }
    class Dog extends Animal { bark() {} }
    
    let handleAnimal = (a: Animal) => {};
    let handleDog = (d: Dog) => {};
    
    handleDog = handleAnimal; // 1
    handleAnimal = handleDog; // 2
    1. ANeither
    2. BOnly 1
    3. COnly 2
    4. DBoth
    Show answer

    Answer: C (Only 2)

    Under strictFunctionTypes, function parameters are contravariant. A function that accepts any Animal can safely stand in for one that only ever receives Dogs, so 1 is fine. A Dog handler used as an Animal handler could be called with a Cat and try to bark(), so 2 errors.

  16. 16.

    What does TypeScript say about parse(value)?

    mid
    function parse(input: string): number;
    function parse(input: number): string;
    function parse(input: string | number): string | number {
      return typeof input === 'string' ? Number(input) : String(input);
    }
    const value = Math.random() > 0.5 ? 'a' : 1;
    const x = parse(value);
    1. AError: no overload matches, because string | number fits neither signature
    2. Bx is string | number
    3. Cx is number
    4. Dx is any
    Show answer

    Answer: A (Error: no overload matches, because string | number fits neither signature)

    Callers see only the overload signatures, never the implementation signature, and each overload is tried on its own. A string | number argument matches neither, so the call fails with No overload matches this call. Add a third overload for the union, or use one signature (or a generic with a conditional return type) instead.

  17. 17.

    Which declaration is a compile error?

    mid
    type A = { name?: string };
    type B = { name: string | undefined };
    
    const a: A = {};
    const b: B = {};
    1. ANeither
    2. BOnly a
    3. CBoth
    4. DOnly b
    Show answer

    Answer: D (Only b)

    name?: means the key may be missing entirely. name: string | undefined means the key must be present, even if its value is undefined, so {} is missing name. With exactOptionalPropertyTypes, name?: string additionally stops you from assigning an explicit undefined.

  18. 18.

    With strict on, which lines are compile errors?

    mid
    const a: {} = 42;       // 1
    const b: object = 42;   // 2
    const c: Object = 'hi'; // 3
    const d: {} = null;     // 4
    1. A2 and 4
    2. BOnly 2
    3. C1, 2 and 4
    4. DAll four
    Show answer

    Answer: A (2 and 4)

    {} means any non-nullish value, primitives included, so 1 compiles. object means non-primitive, so 2 errors. Object behaves much like {} and accepts 'hi'. Under strictNullChecks, null is not assignable to {}, so 4 errors. Use object or Record<string, unknown> when you really mean an object.

  19. 19.

    With strict: true and no other flags, what is the type of s?

    mid
    const scores: Record<string, number> = { ada: 10 };
    const s = scores.grace;
    1. Anumber
    2. Bnumber | undefined
    3. Cundefined
    4. Dunknown
    Show answer

    Answer: A (number)

    strict does not include noUncheckedIndexedAccess. Without that flag, reading any key of a Record<string, number> is typed number, even though s is undefined at runtime. Turning the flag on makes index reads number | undefined, which forces a check.

  20. 20.

    How many members does the union ClassName have?

    mid
    type Size = 'sm' | 'lg';
    type Color = 'red' | 'blue';
    type ClassName = `${Size}-${Color}`;
    1. A1, because it widens to string
    2. B2
    3. C8
    4. D4
    Show answer

    Answer: D (4)

    Template literal types distribute over the unions in every placeholder and produce the cross product: 'sm-red' | 'sm-blue' | 'lg-red' | 'lg-blue'. This grows quickly with more placeholders, and TypeScript refuses to build unions that get too large.

  21. 21.

    Compiled with target: es2022, what does this log at runtime?

    mid
    class Account {
      private pin = 1234;
      #secret = 'x';
    }
    const acc = new Account();
    console.log((acc as any).pin, Object.keys(acc));
    1. Aundefined []
    2. B1234 ['pin', '#secret']
    3. C1234 ['pin']
    4. DIt throws a TypeError
    Show answer

    Answer: C (1234 ['pin'])

    private is only a compile-time check. The emitted JavaScript has an ordinary pin field, so casting to any reads it and Object.keys lists it. #secret is a native private field enforced by the engine: it never appears in Object.keys and cannot be read from outside the class at all.

  22. 22.

    What is Result?

    hard
    type ToArray<T> = T extends unknown ? T[] : never;
    type Result = ToArray<string | number>;
    1. A(string | number)[]
    2. Bnever
    3. Cunknown[]
    4. Dstring[] | number[]
    Show answer

    Answer: D (string[] | number[])

    A conditional type whose checked type is a naked type parameter distributes over unions: it is evaluated as ToArray<string> | ToArray<number>, giving string[] | number[]. To get (string | number)[], turn distribution off by wrapping both sides in tuples: [T] extends [unknown] ? T[] : never.

  23. 23.

    Under strict, which line is a compile error?

    hard
    class Animal { name = '' }
    class Dog extends Animal { bark() {} }
    
    interface WithMethod { run(a: Animal): void }
    interface WithProp { run: (a: Animal) => void }
    
    const dogOnly = (d: Dog) => d.bark();
    const m: WithMethod = { run: dogOnly }; // 1
    const p: WithProp = { run: dogOnly };   // 2
    1. AOnly 1
    2. BOnly 2
    3. CBoth
    4. DNeither
    Show answer

    Answer: B (Only 2)

    strictFunctionTypes only applies to function-type syntax like run: (a: Animal) => void. Parameters declared with method shorthand are still checked bivariantly, a deliberate exception that keeps types like Array<T> covariant. So 1 is accepted (unsoundly) and 2 errors. Prefer property syntax when you want strict checking.

  24. 24.

    What is Result?

    hard
    type Mutable<T> = { -readonly [K in keyof T]-?: T[K] };
    type Frozen = { readonly id: number; readonly note?: string };
    type Result = Mutable<Frozen>;
    1. A{ readonly id: number; readonly note?: string }
    2. B{ id: number; note: string }
    3. C{ id: number; note?: string }
    4. D{ id: number; note: string | undefined }
    Show answer

    Answer: B ({ id: number; note: string })

    -readonly strips the readonly modifier and -? strips optionality from every key. Removing ? also removes the undefined that optionality added, so note becomes plain string, the same thing the built-in Required<T> does.

  25. 25.

    What are A and B?

    hard
    type Unwrap<T> = T extends Promise<infer U> ? U : T;
    type A = Unwrap<Promise<string[]>>;
    type B = Unwrap<number>;
    1. AA is string[], B is number
    2. BA is string[], B is never
    3. CA is Promise<string[]>, B is number
    4. DA is string, B is number
    Show answer

    Answer: A (A is string[], B is number)

    infer U captures whatever type sits inside Promise<...> when the match succeeds, so A is string[]; the array is not unwrapped. number does not match, so the false branch returns T itself. The built-in Awaited<T> does this recursively for nested promises and thenables.

  26. 26.

    In TypeScript 5.5 or later, what is the type of defined?

    hard
    const values = [1, undefined, 2, undefined];
    const defined = values.filter((v) => v !== undefined);
    1. A(number | undefined)[]
    2. Bundefined[]
    3. Cnumber[]
    4. Dany[]
    Show answer

    Answer: C (number[])

    Since TypeScript 5.5 the compiler infers type predicates from simple function bodies, so (v) => v !== undefined is treated as (v) => v is number and the narrowing overload of filter applies. Before 5.5 the result was (number | undefined)[] and you needed an explicit (v): v is number => ... guard.

  27. 27.

    Which line is a compile error?

    hard
    type Callback = () => void;
    const cb: Callback = () => 42; // 1
    
    function run(): void {
      return 42; // 2
    }
    1. ABoth
    2. BNeither
    3. COnly 1
    4. DOnly 2
    Show answer

    Answer: D (Only 2)

    A function type returning void means the caller will ignore the result, so a function that returns something can still be assigned to it; that is what lets you pass arr.push to forEach. A function declaration annotated : void must not return a value, so 2 errors.

  28. 28.

    What is Keys?

    hard
    type Cat = { name: string; meow(): void };
    type Dog = { name: string; bark(): void };
    type Keys = keyof (Cat | Dog);
    1. A'name'
    2. B'name' | 'meow' | 'bark'
    3. Cnever
    4. Dstring
    Show answer

    Answer: A ('name')

    A value of type Cat | Dog is only guaranteed to have the keys both members share, so keyof of a union gives the common keys: 'name'. Intersections work the other way round: keyof (Cat & Dog) is 'name' | 'meow' | 'bark'.

esc