Card type

TSConfig: noUncheckedIndexedAccess

1 card
  1. 01
    const users: string[] = [];
    const firstUser = users[0];
    firstUser.toUpperCase();
    

    With strict: true and nothing else, does this compile?

    • A

      Yes, because firstUser is a const and cannot be undefined

    • B

      No: users[0] is string | undefined under strict

    • C

      No: reading index 0 of an empty array literal is a constant error

    • D

      Yes: users[0] is typed string, and only the separate noUncheckedIndexedAccess flag adds undefined to indexed reads

Everyday Types

4 cards
  1. 02
    declare function request(url: string, method: "GET" | "POST"): void;
    
    const method = "GET";
    let m = method;
    request("/users", m);
    

    Does the call compile under strict?

    • A

      Yes: m is initialised from a "GET" literal, so it keeps that literal type

    • B

      No: m is declared with let, so its type widens to string, which is not assignable to "GET" | "POST"

    • C

      Yes, but only because "GET" and string are the same type at runtime

    • D

      No: string literal types can only be used with const declarations, so let m is itself an error

  2. 03
    declare function request(url: string, method: "GET" | "POST"): void;
    
    const req = { url: "/users", method: "GET" };
    request(req.url, req.method);
    

    This does not compile. Which single change fixes it without adding a type annotation?

    • A

      Add as const after the object literal so method is typed "GET" rather than string

    • B

      Replace const req with let req, since let allows narrower property types

    • C

      Wrap the call as request(req.url, req.method!) to assert the value is present

    • D

      Write method: "GET" as string so the property has an explicit type

  3. 04

    Which of these lines is a compile error?

    const a = "hello" as unknown as number;   // 1
    const b = "hello" as number;              // 2
    const c = document.getElementById("x") as HTMLCanvasElement; // 3
    
    • A

      None: type assertions are never checked, they only silence the compiler

    • B

      Line 3: getElementById returns HTMLElement | null, which cannot be asserted to a subtype

    • C

      Line 1: a value cannot be asserted twice in one expression

    • D

      Line 2: string and number do not sufficiently overlap, so the assertion is rejected

  4. 05
    function handle(value: unknown) {
      value.toFixed(2);            // A
      const n = value as number;   // B
      if (typeof value === "string") value.toUpperCase(); // C
    }
    

    Which statements compile?

    • A

      B and C: an assertion or a typeof check is needed before any member access

    • B

      A, B and C: unknown behaves like any inside a function body

    • C

      Only C: unknown permits nothing until it is narrowed

    • D

      Only B: typeof narrowing does not apply to unknown

Narrowing

6 cards
  1. 06
    function firstOf(list: string[] | null | string) {
      if (typeof list === "object") {
        return list[0];
      }
    }
    

    What does the compiler say about list[0]?

    • A

      Error: typeof cannot narrow a union that contains an array type

    • B

      Nothing: typeof list === "object" narrows list to string[]

    • C

      Error: list is possibly null, because typeof null is "object" and TypeScript knows it

    • D

      Nothing, but list[0] is typed string | null instead of string

  2. 07
    function label(count: number | undefined) {
      if (count) {
        return `${count} items`;
      }
      return "none";
    }
    

    The types are correct and it compiles. What is wrong?

    • A

      count inside the block is still number | undefined, so the template literal is unsafe

    • B

      Nothing: truthiness is the recommended way to narrow away undefined

    • C

      label(0) returns "none", because 0 is falsy and the check narrows away more than undefined

    • D

      The function's return type is string | undefined, because one branch is unguarded

  3. 08
    type Shape = { kind: "circle"; r: number } | { kind: "square"; side: number };
    
    function area(s: Shape): number {
      switch (s.kind) {
        case "circle": return Math.PI * s.r ** 2;
        case "square": return s.side ** 2;
        default: {
          const unreachable: never = s;
          return unreachable;
        }
      }
    }
    

    A teammate adds { kind: "triangle"; base: number; height: number } to Shape and nothing else. What happens?

    • A

      A compile error at the switch, because a switch on a union must be exhaustive

    • B

      A compile error on the Shape declaration, because members must be added to every switch first

    • C

      A compile error at const unreachable: never = s, because s is now the triangle type there

    • D

      Nothing until runtime, where area returns undefined for a triangle

  4. 09
    function same(x: string | number, y: string | boolean) {
      if (x === y) {
        x; y;
      }
    }
    

    Inside the if, what are the types of x and y?

    • A

      Both are string: strict equality can only hold when both sides share a type

    • B

      x is string | number and y is string | boolean: === does not narrow

    • C

      x is string | number and y is string: only the right-hand side narrows

    • D

      Both are string | number | boolean: the union of everything either could be

  5. 10
    function isFish(pet: Fish | Bird): pet is Fish {
      return "fly" in pet;        // wrong: this is true for a Bird
    }
    
    if (isFish(pet)) pet.swim();
    

    What does the compiler report?

    • A

      An error: in checks cannot be used in a function with a type predicate

    • B

      A warning that "fly" in pet narrows to Bird, contradicting the return type

    • C

      Nothing: a type predicate is trusted as written, and the bug surfaces only at runtime

    • D

      An error: the predicate pet is Fish does not match what the function body checks

  6. 11
    function reset(box: { value: string | null }) { box.value = null; }
    
    function use(box: { value: string | null }) {
      if (box.value !== null) {
        reset(box);
        return box.value.length;
      }
    }
    

    Does this compile, and is it safe?

    • A

      It compiles but throws at runtime: narrowing survives the call, though reset set the value to null

    • B

      It does not compile: property paths like box.value are never narrowed, only local variables are

    • C

      It compiles and is safe: reset receives a copy, so box.value cannot change

    • D

      It does not compile: a function call invalidates the narrowing of box.value

More on Functions

4 cards
  1. 12
    type Callback = () => void;
    
    const a: Callback = () => 42;          // 1
    function b(): void { return 42; }      // 2
    const c: Callback = function () { return "done"; }; // 3
    

    Which lines are errors?

    • A

      Lines 1 and 3: arrow and function expressions are checked more strictly than declarations

    • B

      Only line 2: a void return type in a function type is contextual and ignores what is returned, while a literal void declaration forbids returning a value

    • C

      None: void means the return value is discarded, so any value is allowed everywhere

    • D

      Lines 1, 2 and 3: a void function may not return a value

  2. 13
    function len(s: string): number;
    function len(arr: any[]): number;
    function len(x: any) { return x.length; }
    
    len(Math.random() > 0.5 ? "hello" : [0]);
    

    What happens?

    • A

      It compiles: the implementation signature accepts any, so a union argument is fine

    • B

      It compiles: the compiler tries each overload and unions their return types

    • C

      Error: no overload matches string | number[], because each call must pick one overload signature

    • D

      Error: overloads must have a shared return type, and these do not

  3. 14
    function minimumLength<T extends { length: number }>(obj: T, min: number): T {
      if (obj.length >= min) return obj;
      return { length: min };
    }
    

    Why is the second return an error?

    • A

      The function needs an explicit type argument at each call before T can be returned

    • B

      { length: min } satisfies the constraint but is not T: T could be a narrower type such as string, and callers get back whatever T was

    • C

      length is a read-only property of the constraint, so it cannot be assigned in a literal

    • D

      An object literal cannot be returned from a generic function

  4. 15
    const args = [8, 5];
    const angle = Math.atan2(...args);
    

    Why does this fail to compile?

    • A

      Spread syntax is only allowed with rest parameters, and atan2 declares two named parameters

    • B

      args is inferred as number[], whose length is unknown, so spreading it does not prove two arguments are passed

    • C

      Math.atan2 expects a tuple, and arrays are never assignable to tuples

    • D

      Array literals are read-only by default and cannot be spread into a call

Object Types

4 cards
  1. 16
    interface Person { name: string; age: number }
    interface ReadonlyPerson { readonly name: string; readonly age: number }
    
    const writable: Person = { name: "Ann", age: 40 };
    const frozen: ReadonlyPerson = writable;
    writable.age++;
    console.log(frozen.age);
    

    What is the result?

    • A

      Runtime error: objects typed readonly are frozen with Object.freeze

    • B

      Compile error: a Person is not assignable to ReadonlyPerson because the properties differ in mutability

    • C

      It compiles and prints 41: readonly only stops writes through frozen, and assignability ignores the modifier

    • D

      It compiles and prints 40: assigning to a readonly type copies the object

  2. 17
    interface Options { color?: string; width?: number }
    declare function paint(o: Options): void;
    
    const opts = { colour: "red", width: 10 };
    paint(opts);                                // 1
    paint({ colour: "red", width: 10 });        // 2
    

    Which call is an error?

    • A

      Both: colour is not a property of Options

    • B

      Neither: all properties of Options are optional, so any object qualifies

    • C

      Only call 2: excess property checking applies to object literals passed directly, not to a variable that happens to have extra properties

    • D

      Only call 1: a variable's inferred type must match Options exactly, while a literal is contextually typed

  3. 18
    interface Scores {
      [subject: string]: number;
      average: number;
      name: string;
    }
    

    What does the compiler say?

    • A

      Nothing: named properties are checked separately from the index signature

    • B

      Error on name: every property must be assignable to the string index signature's type, and string is not number

    • C

      Error: an interface may not mix an index signature with named properties

    • D

      Error on average: a property may not have the same type as the index signature

  4. 19
    interface A { x: string }
    interface B { x: number }
    
    type C = A & B;                 // 1
    interface D extends A, B {}     // 2
    

    Which declaration is an error?

    • A

      Both: x has conflicting types in A and B

    • B

      Neither: in both, x becomes string | number

    • C

      Only declaration 1: intersections require every property to agree, while extends takes the last one

    • D

      Only declaration 2: extends requires compatible members, while an intersection silently gives x the type never

Generics

1 card
  1. 20
    function first<T>(items: T[]): T {
      return items[0];
    }
    
    const x = first([]);
    

    What is the type of x under strict?

    • A

      undefined

    • B

      unknown

    • C

      any

    • D

      never

Keyof Type Operator

1 card
  1. 21
    type A = keyof { [n: number]: unknown };
    type B = keyof { [s: string]: unknown };
    

    What are A and B?

    • A

      Both are string: object keys are always strings in JavaScript

    • B

      A is number, B is string | number

    • C

      Both are string | number | symbol

    • D

      A is number, B is string

Typeof Type Operator

1 card
  1. 22
    function makeId() { return { id: 1, at: new Date() }; }
    
    type Id = typeof makeId();
    

    What does the compiler say?

    • A

      Id is () => { id: number; at: Date }

    • B

      Id is { id: number; at: Date }

    • C

      Error: functions with inferred return types cannot be used with typeof

    • D

      Error: typeof in a type position accepts an identifier or property path, not a call expression; use ReturnType<typeof makeId>

Indexed Access Types

1 card
  1. 23
    type Person = { name: string; age: number; alive: boolean };
    
    const key = "age";
    type Age = Person[key];
    

    What is wrong?

    • A

      key is a value, and only types may be used in an indexed access; write Person[typeof key] or use the literal "age" directly

    • B

      Nothing: Age is number

    • C

      Person[key] needs key declared with let, since const values are not visible to the type system

    • D

      Indexed access only accepts unions, so Person["age"] is the only valid form here

Conditional Types

3 cards
  1. 24
    type ToArray<T> = T extends any ? T[] : never;
    type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
    
    type A = ToArray<string | number>;
    type B = ToArrayNonDist<string | number>;
    

    What are A and B?

    • A

      Both are (string | number)[]

    • B

      A is string[] | number[], B is (string | number)[]

    • C

      A is (string | number)[], B is string[] | number[]

    • D

      Both are string[] | number[]

  2. 25
    type IsString<T> = T extends string ? true : false;
    
    type R = IsString<never>;
    

    What is R?

    • A

      false

    • B

      boolean

    • C

      true

    • D

      never

  3. 26
    declare function parse(input: string): number;
    declare function parse(input: number): string;
    
    type R = ReturnType<typeof parse>;
    

    What is R?

    • A

      number | string

    • B

      string: inference uses the last overload

    • C

      Error: ReturnType cannot be applied to an overloaded function

    • D

      number: inference uses the first overload

Mapped Types

2 cards
  1. 27
    type Loose = { readonly id?: string; readonly name?: string };
    
    type Tightened = { [K in keyof Loose]-?: Loose[K] };
    

    What is Tightened?

    • A

      { id: string; name: string }: mapping over keys produces plain properties

    • B

      { readonly id: string; readonly name: string }: -? removes optionality, and readonly is preserved because the mapping is homomorphic

    • C

      { readonly id?: string; readonly name?: string }: -? only applies to mapped types over string

    • D

      { id: string | undefined; name: string | undefined }: removing ? keeps undefined in the type

  2. 28
    type Circle = { kind: "circle"; radius: number };
    
    type Fields = { [K in keyof Circle as Exclude<K, "kind">]: Circle[K] };
    

    What is Fields?

    • A

      { radius: number; kind?: "circle" }: excluded keys become optional

    • B

      { kind: never; radius: number }: the excluded key remains with type never

    • C

      { radius: number }: remapping a key to never drops the property

    • D

      Error: the as clause must produce a string type for every key

Template Literal Types

1 card
  1. 29
    type Axis = "x" | "y";
    type Bound = "min" | "max";
    
    type Key = `${Bound}${Capitalize<Axis>}`;
    

    What is Key?

    • A

      `${string}${string}`

    • B

      "minX" | "maxY"

    • C

      "minX" | "minY" | "maxX" | "maxY"

    • D

      "minx" | "maxy"

Classes

4 cards
  1. 30
    class Cat { private name = "cat"; }
    class Dog { private name = "dog"; }
    
    const pet: Cat = new Dog();
    

    Why is the assignment rejected, even though the shapes match?

    • A

      Types with private (or protected) members are only compatible when the member comes from the same declaration, so two classes each declaring their own private name are not interchangeable

    • B

      Classes are always nominal in TypeScript, so no two distinct classes are ever assignable

    • C

      private members are erased, so Cat and Dog are both {} and {} is not assignable to a class

    • D

      Property initialisers make the members readonly, and readonly properties block assignment between classes

  2. 31
    interface Checkable { check(name: string): boolean }
    
    class NameChecker implements Checkable {
      check(s) {
        return s.toLowerCase() === "ok";
      }
    }
    

    Under strict, what happens?

    • A

      It compiles, and s is string because of contextual typing from the interface

    • B

      It compiles: implements gives the method its parameter types from the interface

    • C

      Error: a method implementing an interface must repeat the interface's return type

    • D

      Error: s implicitly has an any type, because implements only checks the class afterwards and does not type its members

  3. 32
    class Base {
      name = "base";
      constructor() { console.log("My name is " + this.name); }
    }
    class Derived extends Base {
      name = "derived";
    }
    
    new Derived();
    

    What is printed?

    • A

      My name is base

    • B

      A compile error: name may not be redeclared in a subclass

    • C

      My name is derived

    • D

      My name is undefined

  4. 33
    class Counter {
      count = 0;
      increment() { this.count++; }
    }
    
    const c = new Counter();
    const tick = c.increment;
    tick();
    

    What do the compiler and the runtime do?

    • A

      Compile error: a method cannot be passed as a callback without bind

    • B

      Compile error under noImplicitThis, because this inside increment is untyped

    • C

      It compiles and works: methods keep their this when detached from the instance

    • D

      It compiles, and at runtime this is undefined inside increment, so this.count++ throws a TypeError

Type Compatibility

2 cards
  1. 34
    let one = (a: number) => 0;
    let two = (a: number, s: string) => 0;
    
    two = one;   // 1
    one = two;   // 2
    

    Which assignment is an error?

    • A

      Assignment 1: one takes fewer parameters than two declares

    • B

      Neither: parameter count is not part of a function's type

    • C

      Both: function types must have identical parameter lists

    • D

      Assignment 2: two requires a second argument that callers of one will never pass

  2. 35
    type User = { id: number; name: string };
    const u: User = { id: 1, name: "Ann" };
    
    for (const k of Object.keys(u)) {
      u[k];
    }
    

    Why is u[k] an error under strict, and is it a compiler mistake?

    • A

      It is a mistake: Object.keys should return (keyof User)[] and the definition is simply out of date

    • B

      for...of cannot iterate an array of literal types, so k widens to string

    • C

      Indexing an object with a variable key is always an error unless the object has an index signature

    • D

      Object.keys returns string[] deliberately: a value of type User may have extra properties at runtime, so its keys are not known to be only "id" | "name"

Utility Types

2 cards
  1. 36
    type User = { id: number; name: string };
    
    type A = Pick<User, "nickname">;
    type B = Omit<User, "nickname">;
    

    What does the compiler say?

    • A

      A is an error, B compiles as User unchanged: Pick constrains its keys to keyof T, Omit accepts any string

    • B

      Both are errors: "nickname" is not a key of User

    • C

      Both compile: neither utility checks its keys against T

    • D

      B is an error, A compiles as {}

  2. 37
    type Config = Readonly<{ name: string; tags: string[] }>;
    
    declare const cfg: Config;
    cfg.name = "x";        // 1
    cfg.tags.push("x");    // 2
    

    Which line is an error?

    • A

      Only line 2: arrays inside a Readonly object become readonly string[]

    • B

      Both: Readonly freezes the whole value

    • C

      Only line 1: Readonly makes the properties of one level read-only, and tags is still a mutable string[]

    • D

      Neither: Readonly is a documentation hint with no effect on assignments

Enums

2 cards
  1. 38
    enum Direction { Up = "UP", Down = "DOWN" }
    
    declare function move(d: Direction): void;
    
    move("UP");
    move(Direction.Up);
    

    What happens to move("UP")?

    • A

      Error: "UP" is not assignable to Direction; string enum members are distinct types that only the enum's own members satisfy

    • B

      It compiles: a string enum member is just its string value

    • C

      Error: string enums cannot be used as parameter types

    • D

      It compiles, but only if the enum is declared const enum

  2. 39
    const enum Level { Low, High }
    
    const names = Object.keys(Level);
    

    Why is this an error?

    • A

      const enum members are read-only, so reflecting over them is disallowed

    • B

      A const enum is fully inlined at each use and emits no runtime object, so there is nothing to reference as a value

    • C

      Level has no string values, and Object.keys requires a string enum

    • D

      Object.keys does not accept enums of any kind

TSConfig: exactOptionalPropertyTypes

1 card
  1. 40
    interface Settings { theme?: "dark" | "light" }
    
    const s: Settings = { theme: undefined };
    

    When is this an error?

    • A

      Only with exactOptionalPropertyTypes enabled, which distinguishes an absent property from one explicitly set to undefined

    • B

      Never: an optional property always accepts undefined

    • C

      Always under strict, because undefined is not a member of the union

    • D

      Only without strictNullChecks, when undefined is not part of any type

TSConfig: useUnknownInCatchVariables

1 card
  1. 41
    try {
      risky();
    } catch (e) {
      console.log(e.message);
    }
    

    Under strict, why is e.message an error?

    • A

      e is unknown under strict, because anything can be thrown; narrow with instanceof Error before reading properties

    • B

      e is Error, and message is a protected property

    • C

      It is not an error: e is any in every configuration

    • D

      catch variables are never, since exceptions are not part of the type system

TSConfig: strictFunctionTypes

1 card
  1. 42
    interface ViaMethod   { handle(x: string | number): void }
    interface ViaProperty { handle: (x: string | number) => void }
    
    const a: ViaMethod   = { handle(x: string) {} };
    const b: ViaProperty = { handle: (x: string) => {} };
    

    Under strict, which assignment is rejected?

    • A

      Both: a handler for string cannot accept string | number

    • B

      Only b: strictFunctionTypes checks function-typed properties contravariantly, while method syntax is deliberately left bivariant

    • C

      Only a: method declarations are checked more strictly than arrow functions

    • D

      Neither: parameter types are compared bivariantly in every position

TypeScript 4.9 Release Notes: satisfies

1 card
  1. 43
    type RGB = [number, number, number];
    type Palette = Record<"red" | "green", string | RGB>;
    
    const annotated: Palette = { red: [255, 0, 0], green: "#00ff00" };
    const satisfied = { red: [255, 0, 0], green: "#00ff00" } satisfies Palette;
    
    annotated.green.toUpperCase();   // 1
    satisfied.green.toUpperCase();   // 2
    

    Which line compiles?

    • A

      Both: satisfies and a type annotation are two spellings of the same check

    • B

      Neither: green is string | RGB in both, and RGB has no toUpperCase

    • C

      Only line 2: satisfies validates the literal against Palette but keeps the inferred type, so green is known to be a string

    • D

      Only line 1: satisfies widens each property to the checked type

Declaration Merging

1 card
  1. 44
    interface Box { height: number }
    interface Box { width: number }
    
    type Size = { height: number };
    type Size = { width: number };
    

    What does the compiler say?

    • A

      Both pairs are errors: a name may only be declared once

    • B

      The interfaces merge into { height: number; width: number }; the second type Size is a duplicate identifier error

    • C

      Both pairs merge: interfaces and type aliases with the same name combine their members

    • D

      The type aliases merge; the second interface Box is an error because interfaces are closed

Modules: Theory

1 card
  1. 45

    Two files in a project, each with no import or export statement:

    // a.ts
    const config = { debug: true };
    // b.ts
    const config = { debug: false };
    

    What happens?

    • A

      Error Cannot redeclare block-scoped variable 'config': a file with no import or export is a script, and every script shares one global scope

    • B

      Nothing: each file is its own module, so the two config variables are unrelated

    • C

      b.ts wins: later files shadow earlier ones in compilation order

    • D

      Error only if isolatedModules is on; otherwise the files are compiled separately

End of deck · 45 cards

Save