W TypeScripcie `switch` ma sens wtedy, gdy wybierasz jedną z kilku znanych opcji i chcesz, żeby kod był czytelniejszy niż długi łańcuch `if...else`. Dobrze użyta konstrukcja potrafi uprościć logikę, ale źle użyta szybko wprowadza błędy z `break`, przypadkowym przejściem do kolejnego `case` i niedomkniętymi wariantami typu. Poniżej pokazuję, jak pisać taki kod praktycznie, bez sztucznej teorii i bez pułapek, które wciąż pojawiają się nawet w dojrzałych projektach.
Najkrótsza droga do poprawnego użycia switcha w TypeScripcie
- `switch` najlepiej sprawdza się przy porównywaniu jednej wartości z kilkoma stałymi wariantami.
- Dopasowanie w JavaScripcie działa przez porównanie ścisłe, więc nie licz na automatyczne rzutowanie typów.
- `break` chroni przed przejściem do kolejnego `case`, a `return` lub `throw` także kończą wykonanie bloku.
- W TypeScripcie bardzo dobrze działa wzorzec z unią typów i kontrolą wyczerpującą przez `never`.
- Przy `let` i `const` w `case` warto stosować dodatkowe klamry, bo same etykiety `case` nie tworzą osobnego zasięgu.
- Jeśli warunki nie są prostym dopasowaniem wartości, często lepszy będzie `if...else` albo mapa obiektów.
Jak działa switch i kiedy ma przewagę nad if else
Podstawowa idea jest prosta: bierzesz jedną wartość, sprawdzasz ją kolejno przeciwko kilku `case` i wykonujesz pasujący blok. MDN opisuje to wprost: dopasowanie odbywa się przez porównanie ścisłe, więc `1` nie jest tym samym co `"1"`, a `true` nie zadziała jak dowolny „prawdziwy” warunek. To ważne, bo wiele osób traktuje `switch` jak wygodniejszą wersję `if...else`, a to nie do końca to samo.Ja najczęściej sięgam po `switch`, gdy mam jeden stabilny punkt decyzji: status zamówienia, typ akcji, nazwę widoku, rodzaj komunikatu albo rozgałęzienie po unii typów. Jeśli warunki są zależne od zakresów, złożonych porównań albo kilku różnych zmiennych, `if...else` zwykle pozostaje czytelniejszy. `switch` wygrywa tam, gdzie logika jest płaska i opiera się na jawnych wariantach, a nie na obliczeniach.
Przykład praktyczny jest prosty: jeśli obsługujesz statusy `pending`, `paid`, `shipped` i `cancelled`, `switch` pokazuje od razu pełną listę możliwych dróg. W takim układzie kod czyta się szybciej niż serię porównań rozrzuconych po kilku liniach. To prowadzi do składni, która wygląda banalnie, ale ma kilka detali wartych dopracowania.
Składnia, która oszczędza błędów
W samym TypeScripcie składnia jest taka sama jak w JavaScripcie, bo kompilator nie wymyśla własnego `switch`. Różnica zaczyna się dopiero na etapie typów i kontroli błędów. W praktyce najbezpieczniej jest pisać konstrukcję w prostym, przewidywalnym układzie:
type OrderStatus = "pending" | "paid" | "shipped" | "cancelled";
function getStatusLabel(status: OrderStatus): string {
switch (status) {
case "pending":
return "Oczekuje na płatność";
case "paid":
return "Opłacone";
case "shipped":
return "Wysłane";
case "cancelled":
return "Anulowane";
default:
return "Nieznany status";
}
}
Najważniejsze są tu trzy rzeczy. Po pierwsze, każdy `case` kończy się `return`, więc nie ma ryzyka przypadkowego przejścia dalej. Po drugie, `default` istnieje nawet wtedy, gdy formalnie nie jest konieczny, bo ułatwia czytanie i daje bezpieczny punkt awaryjny. Po trzecie, w TypeScripcie taki kod dobrze współpracuje z literalnymi typami, czyli wartościami w stylu dokładnie `"paid"` zamiast ogólnego `string`.
Warto też pamiętać o zasięgu. Same etykiety `case` nie tworzą osobnego zakresu dla zmiennych, więc jeśli wewnątrz kilku gałęzi deklarujesz `const` albo `let`, zamknij daną gałąź w bloku. W przeciwnym razie można dostać błąd o ponownej deklaracji identyfikatora, mimo że na pierwszy rzut oka każdy `case` wygląda jak osobny fragment kodu. To drobiazg, ale właśnie takie drobiazgi najczęściej psują dobrze rozpoczęty refaktor.
Jeśli chcesz uniknąć podobnych wpadek, następna sekcja pokazuje, jak pisać `case` tak, żeby kompilator pomagał, a nie tylko „przepuszczał” kod dalej.
Jak wykorzystać switch z unią typów i kontrolą wyczerpującą
Tu TypeScript daje realną przewagę nad czystym JavaScriptem. Dokumentacja TypeScript pokazuje wzorzec z `never`, który pozwala wymusić pełną obsługę wszystkich wariantów. To szczególnie przydatne, gdy pracujesz na unii typów i chcesz mieć pewność, że po dodaniu nowego wariantu kompilator od razu wskaże brakującą gałąź.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number }
| { kind: "triangle"; sideLength: number };
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
case "triangle":
return (shape.sideLength * shape.sideLength) / 2;
default: {
const exhaustiveCheck: never = shape;
return exhaustiveCheck;
}
}
}
Ten wzorzec ma dużą wartość praktyczną. Jeśli później dodasz np. `rectangle`, a zapomnisz o nim w `switch`, TypeScript zgłosi błąd w miejscu, w którym naprawdę trzeba poprawić logikę. To lepsze niż ciche „przepuszczenie” nowego przypadku do `default`, bo wtedy brak obsługi wychodzi dopiero w produkcji.
Ja traktuję to jako jedną z sensowniejszych rzeczy, które TypeScript wnosi do zwykłego `switch`: nie tylko porządkuje kod, ale też pilnuje kompletności decyzji. Z tego miejsca łatwo już przejść do najczęstszych błędów, bo właśnie przy takich konstrukcjach pojawiają się najczęściej.

Jak pisać bezpieczne case’y i nie wpaść w klasyczne pułapki
- Nie zapominaj o `break` tam, gdzie ma nastąpić zakończenie gałęzi. Bez niego kod przejdzie do następnego `case`, a to bywa trudne do zauważenia w dużych blokach.
- Włącz `noFallthroughCasesInSwitch`. To ustawienie w `tsconfig` raportuje przypadki, w których niechciany fall-through mógłby przejść niezauważony.
- Nie zakładaj, że `case` tworzy własny zakres. Jeśli używasz `const` lub `let`, dodaj klamry wokół zawartości gałęzi.
- Nie mieszaj zbyt wielu odpowiedzialności w jednym `switch`. Gdy blok robi trzy różne rzeczy, zwykle jest już za ciężki.
- Uważaj na przypadkowe porównania typów. `switch` nie zrobi za Ciebie konwersji wartości, więc liczby i napisy trzeba trzymać konsekwentnie.
- Wykorzystuj celowy fall-through tylko wtedy, gdy naprawdę upraszcza kod. Dwa lub trzy `case` wykonujące ten sam fragment to dobry przykład, ale złożona sekwencja działań bywa już zbyt mało czytelna.
W praktyce większość problemów sprowadza się do jednego: ktoś pisze `switch` tak, jakby był z natury prosty i „sam się obroni”. Nie obroni się. Pomaga tu tylko konsekwencja, jasne reguły i krótki blok odpowiedzialności dla jednej decyzji. To prowadzi naturalnie do pytania, kiedy w ogóle nie warto używać `switch`.
Kiedy switch przegrywa z if else albo mapą obiektów
Nie każdy przypadek zasługuje na `switch`. Jeśli warunek zależy od zakresu liczb, daty, kilku pól naraz albo wyrażeń logicznych typu „jeśli A i B, ale nie C”, `if...else` będzie po prostu lepszy. Z kolei przy bardzo prostym mapowaniu klucz-wynik często wygrywa obiekt lub `Map`, bo usuwa zbędny ceremonialny kod.
| Sytuacja | Lepszy wybór | Dlaczego |
|---|---|---|
| Jedna wartość, kilka stałych wariantów | `switch` | Najczytelniej pokazuje zamkniętą listę opcji. |
| Warunki zakresowe i złożone porównania | `if...else` | Łatwiej opisać logikę niż sztucznie rozbijać ją na `case`. |
| Proste mapowanie klucza na wynik | Obiekt lub `Map` | Mniej kodu, mniej szans na pomyłkę, łatwiejsza rozbudowa danych. |
| Obsługa zamkniętej unii typów | `switch` z `never` | Kompletność obsługi może być sprawdzana przez kompilator. |
| Warunki zależne od wielu zmiennych | `if...else` albo funkcje pomocnicze | `switch` szybko traci wtedy przejrzystość. |
Jest jeszcze wzorzec `switch (true)`, czasem spotykany w starszym kodzie. Technicznie działa, ale w nowych projektach traktuję go ostrożnie, bo maskuje prostą logikę porównań pod konstrukcją, która wygląda na bardziej formalną, niż jest w rzeczywistości. Jeśli musisz dopisywać komentarz, żeby wytłumaczyć, czemu `switch` sprawdza warunki logiczne, to zwykle sygnał, że lepiej wrócić do `if...else`. Z tego miejsca zostaje już tylko jedna rzecz: sensowny wzorzec pracy, który możesz wdrożyć od razu.
Wzorzec, który najlepiej sprawdza się w codziennym kodzie
Jeśli miałbym zostawić jeden praktyczny schemat, to wyglądałby tak: wybierz `switch` tylko wtedy, gdy porównujesz jedną wartość z zamkniętym zestawem wariantów; trzymaj każdy `case` krótko; kończ go `return`, `break` albo `throw`; a przy TypeScripcie dodaj kontrolę wyczerpującą przez `never`, jeśli pracujesz na unii typów. To daje kod, który jest jednocześnie czytelny, bezpieczny i odporny na późniejsze dopisywanie nowych przypadków.
W codziennej pracy najbardziej doceniam nie samą składnię, tylko to, że dobrze napisany `switch` ogranicza chaos w logice decyzyjnej. Widzisz od razu, jakie są obsługiwane warianty, gdzie kończy się gałąź i czy kompilator zauważy brak nowego przypadku. Jeśli trzymasz się tych zasad, `switch/case` w TypeScripcie przestaje być tylko prostą konstrukcją składniową, a zaczyna być naprawdę solidnym narzędziem do porządkowania decyzji w kodzie.