AnyCompiler

Type Narrowing

When a value's type is a union — like string | number — TypeScript won't let you use operations specific to just one of those types until you've proven which one it actually is. Narrowing is how you prove it, and TypeScript's checker actually tracks the proof.

function describe(value: string | number): string {
  if (typeof value === "string") {
    return `String of length ${value.length}`;
  }
  return `Number: ${value.toFixed(2)}`;
}

console.log(describe("hello"));
console.log(describe(3.14159));
Output
String of length 5
Number: 3.14

Inside the if (typeof value === "string") block, TypeScript treats value as a string — value.length is valid there but would be a compile error outside that block, where value could still be a number.

in — checking whether a property exists

The in operator checks whether an object has a given property, and TypeScript uses that check to narrow between two different object shapes.

interface Cat {
  meow(): string;
}

interface Dog {
  bark(): string;
}

function speak(animal: Cat | Dog): string {
  if ("meow" in animal) {
    return animal.meow();
  }
  return animal.bark();
}

const cat: Cat = { meow: () => "Meow!" };
const dog: Dog = { bark: () => "Woof!" };

console.log(speak(cat));
console.log(speak(dog));
Output
Meow!
Woof!

Next: modules and the standard library — organizing code across files, and a few utility types that build on everything covered so far.

Practice exercise

Write a function count(value: string | number[]): string that returns `string: ${value.length}` if value is a string (checked with typeof), or `array: ${value.length}` otherwise. Print count("hello") and count([1, 2, 3]).