Narrowing Flashcards
What is a typeof type guard and how is it used?
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 does truthiness narrowing work in TypeScript?
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 does equality narrowing work in TypeScript?
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 does the ‘in’ operator help with type narrowing?
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 does instanceof narrowing work?
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 does TypeScript narrow types through assignments?
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 does TypeScript analyze control flow to narrow types?
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(); }
What are type predicates and how are they used?
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; }
What are assertion functions and how do they work?
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
What are discriminated unions and how are they used?
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 } }
What is the never type and how is it used?
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 does exhaustiveness checking work in TypeScript?
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;
}
}
~~~