Narrowing Flashcards

1
Q

What is a typeof type guard and how is it used?

A

typeof type guards are used to narrow down the type of a variable based on JavaScript’s typeof operator.

function printAll(strs: string | string[] | null) {
    if (typeof strs === "string") {
        console.log(strs.toUpperCase());  // type is string
    } else if (typeof strs === "object") {
        if (strs) {  // checking for null
            strs.forEach(s => console.log(s));  // type is string[]
        }
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How does truthiness narrowing work in TypeScript?

A

Truthiness narrowing uses JavaScript’s truthy/falsy values to narrow down types.

function printName(name: string | null | undefined) {
    if (name) {
        // Here, name is definitely string
        console.log(name.toUpperCase());
    } else {
        // Here, name is null | undefined
        console.log("Name not provided");
    }
}

// Works with arrays too
function multiplyAll(values: number[] | null) {
    if (!values) {
        return 1;
    }
    return values.reduce((acc, x) => acc * x, 1);
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

How does equality narrowing work in TypeScript?

A

Equality narrowing narrows types based on comparisons with specific values.

function example(x: string | number, y: string | boolean) {
    if (x === y) {
        // x and y can only be string here
        console.log(x.toUpperCase());
        console.log(y.toUpperCase());
    } else {
        console.log(x);  // string | number
        console.log(y);  // string | boolean
    }
}

// Checking for null
function printValue(value: string | null) {
    if (value !== null) {
        console.log(value.toUpperCase());  // type is string
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

How does the ‘in’ operator help with type narrowing?

A

The ‘in’ operator checks if a property exists on an object and can be used for type narrowing.

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(animal: Fish | Bird) {
    if ("swim" in animal) {
        // animal is Fish
        animal.swim();
    } else {
        // animal is Bird
        animal.fly();
    }
}

// Complex example
interface A { a: string }
interface B { b: string }
function doSomething(x: A | B) {
    if ("a" in x) {
        console.log(x.a);  // x is A
    } else {
        console.log(x.b);  // x is B
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

How does instanceof narrowing work?

A

instanceof checks if a value is an instance of a class and narrows the type accordingly.

class StringContainer {
    constructor(public value: string) {}
    run() { console.log(this.value.toUpperCase()); }
}

class NumberContainer {
    constructor(public value: number) {}
    run() { console.log(this.value.toFixed(2)); }
}

function process(x: StringContainer | NumberContainer) {
    if (x instanceof StringContainer) {
        x.value.toUpperCase();  // x.value is string
    } else {
        x.value.toFixed(2);     // x.value is number
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

How does TypeScript narrow types through assignments?

A

TypeScript tracks assignments and narrows types based on assigned values.

let x: string | number;

x = "hello";     // x is string
console.log(x.toUpperCase());

x = 42;          // x is number
console.log(x.toFixed(2));

// With unions
let value: string | number | boolean;
value = "hello"; // value is string
value = 42;      // value is number
value = true;    // value is boolean
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

How does TypeScript analyze control flow to narrow types?

A

TypeScript follows code execution paths to determine type information.

function example(value: string | number | boolean) {
    let x = value;

    if (typeof x === "string") {
        x.toUpperCase();
        return;
    }

    // TypeScript knows x is number | boolean here
    if (typeof x === "number") {
        x.toFixed(2);
        return;
    }

    // TypeScript knows x is boolean here
    x.valueOf();
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

What are type predicates and how are they used?

A

Type predicates are functions that return a boolean and help TypeScript narrow types.

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined;
}

let pet: Fish | Bird = getRandomPet();

if (isFish(pet)) {
    pet.swim();  // TypeScript knows pet is Fish
} else {
    pet.fly();   // TypeScript knows pet is Bird
}

// More complex example
interface Cat { meow(): void }
interface Dog { bark(): void }

function isCat(animal: Cat | Dog): animal is Cat {
    return 'meow' in animal;
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What are assertion functions and how do they work?

A

Assertion functions are functions that throw an error if a condition is not met, helping TypeScript narrow types.

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new Error(msg);
    }
}

function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new Error("Not a string!");
    }
}

let value: unknown = "hello";
assertIsString(value);
console.log(value.toUpperCase());  // TypeScript knows value is string
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What are discriminated unions and how are they used?

A

Discriminated unions are unions that share a common field used to discriminate between them.

interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
    switch (shape.kind) {
        case "circle":
            return Math.PI * shape.radius ** 2;  // TypeScript knows shape is Circle
        case "square":
            return shape.sideLength ** 2;        // TypeScript knows shape is Square
    }
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What is the never type and how is it used?

A

never represents values that never occur, useful for exhaustive checking.

function error(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

// Using never to ensure exhaustive handling
type Shapes = Circle | Square;
function assertNever(x: never): never {
    throw new Error("Unexpected object: " + x);
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

How does exhaustiveness checking work in TypeScript?

A

Exhaustiveness checking ensures all possible cases are handled in code.

```<code>
type Direction = "north" | "south" | "east" | "west";</code>

function move(direction: Direction) {
switch (direction) {
case “north”:
return 1;
case “south”:
return -1;
case “east”:
return 2;
case “west”:
return -2;
default:
// This line ensures exhaustiveness
const exhaustiveCheck: never = direction;
return exhaustiveCheck;
}
}
~~~

How well did you know this?
1
Not at all
2
3
4
5
Perfectly