Control Flow in TypeScript
Core branching and looping constructs, plus TypeScript’s control‑flow based type analysis.
Basics: if/else, switch, loops
const n = 3;
if (n > 0) {
console.log("positive");
} else if (n === 0) {
console.log("zero");
} else {
console.log("negative");
}
const day = "Mon" as const;
switch (day) {
case "Mon":
case "Tue":
console.log("weekday");
break;
default:
console.log("other");
}
for (const x of [1, 2, 3]) console.log(x); // values
for (const i in { a: 1, b: 2 }) console.log(i); // keys (string)
Prefer for..of for arrays; for..in iterates keys (strings), not values.
Control‑flow narrowing
TypeScript refines types based on checks.
function len(x: string | string[] | null) {
if (!x) return 0; // narrows to string | string[]
if (typeof x === "string") return x.length;
return x.length; // string[]
}
class A { a = 1 }
class B { b = 2 }
function f(v: A | B) {
if (v instanceof A) return v.a; // narrows to A
return v.b; // B
}
function hasId(x: unknown): x is { id: number } {
return typeof (x as any)?.id === "number";
}
Discriminated unions and exhaustive checks
Use a common literal tag field to discriminate.
type Shape =
| { kind: "circle"; r: number }
| { kind: "rect"; w: number; h: number };
function area(s: Shape): number {
switch (s.kind) {
case "circle": return Math.PI * s.r ** 2;
case "rect": return s.w * s.h;
default: {
const _exhaustive: never = s; // compile error if a case is missing
return _exhaustive;
}
}
}
Truthiness and nullish values
if (x)treats"", 0, NaN, null, undefinedas falsy- Prefer
??(nullish coalescing) over||to avoid treating0/""as missing
const port = Number(process.env.PORT) ?? 3000;
Try/catch typing
Enable useUnknownInCatchVariables to force safe handling.
try {
risky();
} catch (e: unknown) {
if (e instanceof Error) console.error(e.message);
}
Summary
- Use narrowing (
typeof/instanceof/in) and discriminated unions - Enforce exhaustiveness with a
nevercheck - Prefer
??for defaults; usefor..ofover arrays