A TypeScript switch statement is JavaScript's switch with type checking. It compares a value against each case with ===, runs the matching branch, and stops at break or return. TypeScript narrows the switched value inside each case and can check that every possible value is handled.
Stacked labels (case "sat": case "sun":) share one branch. return ends the function, so no break is needed after it.
Syntax
switch (expression) {
case value1:
// runs when expression === value1
break;
case value2:
case value3:
// runs for value2 or value3
break;
default:
// runs when nothing else matched
}
- Matching uses strict equality,
===:case 1does not match the string"1". - Without
break,returnorthrow, execution continues into the next case (fallthrough). defaultis optional and can sit anywhere, though last is conventional.- A
casevalue TypeScript can prove never matches is error TS2678. For a parameter typed"a" | "b",case "c":reportsType '"c"' is not comparable to type '"a" | "b"'., which catches typos in case labels.
Narrowing Inside Each Case
In each case, TypeScript knows which value matched and narrows the type. This is most useful with a discriminated union: switch on the shared tag property, and each case sees the matching variant with its own properties.
shape.radius compiles only in the "circle" case. Outside it, shape might be a rectangle, which has no radius. There is no default, and the function still type-checks as returning number, because TypeScript sees that the three cases cover every kind. More patterns built on this are on the discriminated unions page.
Exhaustive Switch with never
The function above stops compiling if a fourth shape is added without a case (TS2366, a missing return). That only works when the function returns a value. For a guarantee that also works in void code and gives a clearer message, add a default that assigns the value to never:
index.ts(14,19): error TS2322: Type '{ kind: "triangle"; base: number; height: number; }' is not assignable to type 'never'.
After the handled cases, the only type left for shape is the triangle variant, and it cannot be assigned to never. The error names exactly what is missing. Add case "triangle": return (shape.base * shape.height) / 2; and the default sees never, so the block compiles and prints 9. The throw still guards against bad data at runtime, such as a kind from JSON that the types did not anticipate.
Many codebases wrap the check in a helper:
The same technique works for enums: switch on the enum value and pass it to assertNever in the default.
switch (true) for Ranges and Conditions
switch compares values, so ranges need a trick: switch on true and write a condition in each case. Since TypeScript 5.3, those conditions narrow types just like if statements do.
Cases are tested in order, so put the most specific first. Whether this reads better than if / else if is a matter of taste; the behavior is the same.
Fallthrough and break
A case with code but no break, return or throw falls through into the next case. That is almost always a bug. The compiler option noFallthroughCasesInSwitch (not part of strict) turns it into error TS7029, Fallthrough case in switch., while still allowing stacked empty labels.
Deliberate fallthrough like this works, but with noFallthroughCasesInSwitch on it must be rewritten, for example with separate if checks. That is usually clearer anyway.
Variables Inside Cases
The whole switch body is one block, so a const declared in one case is visible (and a redeclaration is an error) in the others. Wrap a case in braces to give it its own scope:
Without the braces, the second const unit is compile error TS2451, Cannot redeclare block-scoped variable 'unit'.
switch vs Object Lookup
When each case only maps a value to another value, an object typed with Record is shorter, and TypeScript checks that every key is present:
Leaving out a key is a compile error, which gives the same exhaustiveness as a never check. Keep switch for cases that run different logic, narrow union variants, or return early.
Frequently Asked Questions
How do you write a switch statement in TypeScript?
Exactly as in JavaScript: switch (value) { case "a": ...; break; default: ... }. Cases are compared with ===. TypeScript adds checks: a case value that can never match the switched type is an error (TS2678), and inside each case the switched variable is narrowed to that case.
How do I make a switch exhaustive in TypeScript?
Add a default that assigns the value to a variable of type never: default: { const unreachable: never = value; throw new Error(...) }. When every member of the union is handled, the value is never there and it compiles. When one is missing, the compiler reports the missing member (TS2322).
How do I handle multiple cases with the same code in a TypeScript switch?
Stack the labels with no code between them: case "sat": case "sun": return "weekend";. Empty cases fall through to the next one. The noFallthroughCasesInSwitch option only reports cases that have code and no break or return, so stacked labels stay allowed.
Does switch (true) narrow types in TypeScript?
Yes, since TypeScript 5.3. In switch (true) { case typeof x === "string": ... }, x is narrowed to string inside that case, just as it would be in an if. It is a readable alternative to a chain of if/else if with range or type checks.