Instrukcja switch

Gdy jedna zmienna może przyjąć kilkanaście konkretnych wartości, drabinka else if staje się ścianą tekstu. switch zapisuje to samo krócej — a w połączeniu z typami literalnymi kompilator sam pilnuje, żebyś nie zapomniał o żadnym przypadku.

TypeScript switch / case break 45 min
CEL LEKCJI

Czego się dziś nauczysz

  • Zapiszesz wybór wielowariantowy instrukcją switch
  • Wyjaśnisz, co się dzieje, gdy zabraknie break
  • Połączysz kilka wartości w jeden przypadek
  • Ocenisz, kiedy switch jest lepszy od drabinki else if
  • Sprawisz, że kompilator zgłosi błąd, gdy pominiesz jakąś wartość typu
TEORIA

Składnia

schemat
switch (wyrażenie) {
  case wartość1:
    // instrukcje
    break;
  case wartość2:
    // instrukcje
    break;
  default:
    // gdy nic nie pasowało
}

Program oblicza wyrażenie w nawiasie i porównuje je z kolejnymi etykietami case — dokładnie tak, jakby użył operatora ===. Przy pierwszym trafieniu zaczyna wykonywać kod, a break wyprowadza go z całej instrukcji.

src/index.ts
let ocena: number = 4;

switch (ocena) {
  case 6:
    console.log("celujący");
    break;
  case 5:
    console.log("bardzo dobry");
    break;
  case 4:
    console.log("dobry");
    break;
  case 3:
    console.log("dostateczny");
    break;
  case 2:
    console.log("dopuszczający");
    break;
  case 1:
    console.log("niedostateczny");
    break;
  default:
    console.log("nieprawidłowa ocena");
}
wynik w konsoli
dobry

To samo w drabince else if zajęłoby tyle samo linii, ale każda z nich powtarzałaby nazwę zmiennej i operator porównania. switch mówi raz: „porównuję wartość”, a dalej wypisuje już same możliwości.

Blok default jest opcjonalny

Jeśli go pominiesz i nic nie pasuje, switch po prostu nic nie zrobi. Warto go jednak dopisywać — przechwytuje wartości, o których nie pomyślałeś, i jest dobrym miejscem na komunikat o błędzie.

Dlaczego dane są podane w ten sposób?

Przy const ocena = 4 kompilator zna dokładnie wartość 4 i może odrzucić pozostałe case jako niemożliwe. Zmienna liczbowa let oraz funkcje daneWejsciowe() symulują wynik pochodzący z zewnątrz i zachowują szerszy typ. Funkcje poznasz dokładnie w lekcji 11; tutaj funkcja po prostu zwraca wskazaną wartość.

Przy rozpoznawaniu pojedynczej cyfry użyj porównania znaków od „0” do „9”, zamiast Number, które akceptuje też białe znaki.

WAŻNE

Rola instrukcji break

switch nie kończy się po wykonaniu pasującego przypadku — wykonuje wszystko, co jest poniżej, aż napotka break. Bez niego dostajemy tak zwane przelatywanie (ang. fall-through):

zapomniany break
let ocena: number = 5;

switch (ocena) {
  case 6:
    console.log("celujący");
  case 5:
    console.log("bardzo dobry");
  case 4:
    console.log("dobry");
  default:
    console.log("koniec");
}
wynik w konsoli — prawdopodobnie nie o to chodziło
bardzo dobry
dobry
koniec

Najczęstszy błąd przy switch

Program wszedł w case 5 i poleciał dalej przez wszystkie kolejne bloki. Włącz w tsconfig.json opcję "noFallthroughCasesInSwitch": true, a kompilator zacznie zgłaszać takie sytuacje jako błąd.

Wyjątek: gdy blok kończy się instrukcją return (w funkcji, lekcja 11), break jest zbędny — return i tak opuszcza całą funkcję.

PRAKTYKA

Grupowanie przypadków

Przelatywanie bywa jednak przydatne — świadomie użyte pozwala obsłużyć kilka wartości tym samym kodem. Etykiety case stawia się wtedy jedna pod drugą, bez instrukcji pomiędzy nimi:

src/index.ts
type Dzien = "pon" | "wt" | "sr" | "czw" | "pt" | "sob" | "ndz";
function daneWejsciowe(): Dzien { return "sob"; }
const dzien = daneWejsciowe();

switch (dzien) {
  case "sob":
  case "ndz":
    console.log("Weekend — szkoła zamknięta");
    break;
  case "pon":
  case "wt":
  case "sr":
  case "czw":
    console.log("Dzień nauki, zajęcia do 15:20");
    break;
  case "pt":
    console.log("Dzień nauki, zajęcia do 13:30");
    break;
}

Drugi typowy przykład — liczba dni w miesiącu:

src/index.ts
let miesiac: number = 2;
const rok = 2026;
let dni: number;

switch (miesiac) {
  case 1: case 3: case 5: case 7: case 8: case 10: case 12:
    dni = 31;
    break;
  case 4: case 6: case 9: case 11:
    dni = 30;
    break;
  case 2:
    dni = (rok % 4 === 0 && rok % 100 !== 0) || rok % 400 === 0 ? 29 : 28;
    break;
  default:
    dni = 0;
    console.log("Nieprawidłowy numer miesiąca");
}

console.log(`Miesiąc ${miesiac}/${rok} ma ${dni} dni`);
DECYZJA

switch czy if?

SytuacjaWybierz
Jedna zmienna, kilka konkretnych wartości (status, kod, litera, dzień)switch
Przedziały liczbowe (>= 90, >= 75…)if / else if
Warunki złożone z && i || na wielu zmiennychif
Sprawdzenie pojedynczego warunku logicznegoif
Zmienna typu unii literałów albo enumswitch

switch nie obsługuje przedziałów

Zapis case procent >= 90: nie zadziała tak, jak myślisz — po case musi stać wartość do porównania przez ===, a nie warunek. Do progów używaj drabinki else if z poprzedniej lekcji.

TYPESCRIPT

Kontrola kompletności

Tu switch pokazuje przewagę, której zwykły JavaScript nie ma. Gdy zmienna jest typu unii literałów, kompilator wie, ile możliwości istnieje — i potrafi sprawdzić, czy obsłużyłeś wszystkie:

src/index.ts
type Status = "nowe" | "wydane" | "anulowane";

function daneWejsciowe(): Status { return "wydane"; }
const status = daneWejsciowe();
let opis: string;

switch (status) {
  case "nowe":
    opis = "Oczekuje na realizację";
    break;
  case "wydane":
    opis = "Zamówienie odebrane";
    break;
  case "anulowane":
    opis = "Zamówienie anulowane";
    break;
}

console.log(opis);   // OK — kompilator widzi, że każda możliwość ma przypisanie

Usuń teraz ostatni przypadek i skompiluj ponownie. Dostaniesz:

komunikat kompilatora
error TS2454: Variable 'opis' is used before being assigned.

Kompilator wykrył, że istnieje wartość ("anulowane"), dla której zmienna nie dostaje nic. To ogromna pomoc przy rozbudowie programu: gdy dopiszesz do typu Status nową wartość, kompilator sam wskaże wszystkie miejsca w projekcie, które trzeba uzupełnić.

Wzorzec z never

Gdy w switch nie przypisujesz zmiennej, tylko wykonujesz działania, powyższa kontrola nie zadziała. Stosuje się wtedy sztuczkę ze specjalnym typem never, który oznacza „wartość, która nie może wystąpić”:

src/index.ts
type Status = "nowe" | "wydane" | "anulowane";
function daneWejsciowe(): Status { return "nowe"; }
const status = daneWejsciowe();

switch (status) {
  case "nowe":
    console.log("Przygotuj zamówienie");
    break;
  case "wydane":
    console.log("Zamknij zamówienie");
    break;
  case "anulowane":
    console.log("Zwróć towar do magazynu");
    break;
  default:
    // jeśli obsłużyliśmy wszystko, tutaj typ status to "never"
    const niemozliwe: never = status;
    console.log(`Nieobsłużony status: ${niemozliwe}`);
}

Po co to komu

Dopisz do typu Status czwartą wartość, na przykład "opoznione". Linia z never natychmiast zapali się na czerwono — bo do default może teraz trafić realna wartość. Kompilator przypomina ci o niedokończonej robocie. Typ never omówimy dokładniej w lekcji 17.

PRZYKŁAD Z OMÓWIENIEM

Kalkulator konsolowy

Klasyczne zadanie egzaminacyjne: dwie liczby, znak działania, wynik. Program obsługuje też przypadki brzegowe i konwersję jednostek.

src/index.ts
type Dzialanie = "+" | "-" | "*" | "/" | "%" | "^";

// ── dane wejściowe ──
const a = 17;
let b: number = 5;
function daneWejsciowe(): Dzialanie { return "/"; }
const operacja = daneWejsciowe();

// ── obliczenie ──
let wynik: number;
let opis: string;
let blad = false;

switch (operacja) {
  case "+":
    wynik = a + b;
    opis = "suma";
    break;
  case "-":
    wynik = a - b;
    opis = "różnica";
    break;
  case "*":
    wynik = a * b;
    opis = "iloczyn";
    break;
  case "/":
    if (b === 0) {
      wynik = 0;
      opis = "dzielenie przez zero";
      blad = true;
    } else {
      wynik = a / b;
      opis = "iloraz";
    }
    break;
  case "%":
    if (b === 0) {
      wynik = 0;
      opis = "reszta z dzielenia przez zero";
      blad = true;
    } else {
      wynik = a % b;
      opis = "reszta z dzielenia";
    }
    break;
  case "^":
    wynik = a ** b;
    opis = "potęga";
    break;
}

// ── wypisanie ──
console.log("═".repeat(38));
if (blad) {
  console.log(`BŁĄD: ${opis}`);
} else {
  console.log(`${a} ${operacja} ${b} = ${wynik}`);
  console.log(`Działanie: ${opis}`);

  // dodatkowe informacje o wyniku
  switch (true) {
    case wynik < 0:
      console.log("Wynik ujemny");
      break;
    case wynik === 0:
      console.log("Wynik zerowy");
      break;
    default:
      console.log("Wynik dodatni");
  }

  console.log(`Zaokrąglony: ${Math.round(wynik)}`);
  console.log(`Całkowity:   ${Number.isInteger(wynik) ? "tak" : "nie"}`);
}
console.log("═".repeat(38));
wynik w konsoli
══════════════════════════════════════
17 / 5 = 3.4
Działanie: iloraz
Wynik dodatni
Zaokrąglony: 3
Całkowity:   nie
══════════════════════════════════════

Omówienie

FragmentDlaczego tak
type Dzialanie = "+" | "-" | … Dzięki unii literałów kompilator zna wszystkie sześć możliwości i pilnuje, żeby każda miała swój case. Pominięcie jednego to natychmiastowy TS2454 przy wynik.
if wewnątrz case "/" W bloku case może stać dowolny kod, także instrukcja warunkowa. Tu sprawdzamy dzielnik, zanim wykonamy dzielenie.
switch (true) Sztuczka: skoro po case stoi wartość porównywana przez ===, to porównując z true możemy w etykietach umieścić warunki. Działa, ale używaj oszczędnie — zwykły if / else if jest tu czytelniejszy.
Number.isInteger(wynik) Sprawdzenie, czy liczba jest całkowita. Przydaje się, gdy wynik dzielenia trzeba wyświetlić inaczej niż wynik dodawania.

Sprawdź to sam

Zmień b na 0 i operacja na "/" — zobaczysz komunikat błędu zamiast Infinity. Potem dopisz do typu Dzialanie wartość "//" (dzielenie całkowite) i sprawdź, jak kompilator upomni się o brakujący przypadek.

CZĘSTE BŁĘDY

Na co uważać

KodProblem
brak break Program wykona też kolejne przypadki. Włącz noFallthroughCasesInSwitch.
case procent >= 90: Po case musi być wartość, nie warunek. Do przedziałów użyj else if.
case "5": przy zmiennej liczbowej Porównanie działa jak ===, więc tekst nigdy nie zrówna się z liczbą.
const x = …; w dwóch blokach case Wszystkie case dzielą jeden zasięg, więc powtórzona nazwa to błąd. Otocz zawartość przypadku własnymi klamrami: case 1: { … break; }
switch na typie boolean Dwie możliwości to zadanie dla if / else, nie dla switch.
brak default przy danych z zewnątrz Wartość spoza listy przejdzie bez śladu. Dopisz default z komunikatem.

Dwie rzeczy z przyszłości

W przykładach tej lekcji celowo używamy dwóch elementów, które poznasz później. function daneWejsciowe(): Dzien { … } to funkcja udająca dane od użytkownika — funkcjami zajmiemy się w lekcji 11. Zapis type Dzien = "pon" | "wt" | … to unia literałów, czyli typ dopuszczający tylko wymienione wartości; pełne omówienie w lekcji 15. Tutaj wystarczy je czytać, nie musisz ich samodzielnie projektować.

ZADANIA

Zadania

ZAD 1Przewidź wynik★☆☆
zagadka.ts
let x: number = 2;

switch (x) {
  case 1:
    console.log("jeden");
    break;
  case 2:
    console.log("dwa");
  case 3:
    console.log("trzy");
    break;
  case 4:
    console.log("cztery");
    break;
  default:
    console.log("inne");
}
pokaż rozwiązanie
dwa
trzy

Po case 2 brakuje break, więc program przelatuje do case 3 i dopiero tam się zatrzymuje. Do case 4 i default już nie dotrze.

ZAD 2Konwerter ocen słownych★☆☆

Napisz program, który zamienia ocenę słowną (typ literalny: "celujący", "bardzo dobry", …) na cyfrę i odwrotnie — dwie osobne instrukcje switch. Sprawdź, czy kompilator upomni się o brakujący przypadek, gdy usuniesz jedną z gałęzi.

ZAD 3Menu automatu★★☆

Zasymuluj menu automatu z napojami. Zmienna wybor typu "1" | "2" | "3" | "4" | "0" odpowiada pozycjom: kawa 4 zł, herbata 3 zł, czekolada 5 zł, woda 2 zł, anulowanie. Dla każdej pozycji wypisz nazwę i cenę, a dla anulowania — komunikat o zwrocie monet. Dodaj sprawdzenie, czy wrzucona kwota wystarcza, i wypisz resztę.

pokaż przykładowe rozwiązanie
type Wybor = "1" | "2" | "3" | "4" | "0";

function daneWejsciowe(): Wybor { return "3"; }
const wybor = daneWejsciowe();
const wrzucone = 4.5;

let nazwa: string;
let cena: number;

switch (wybor) {
  case "1": nazwa = "kawa";      cena = 4; break;
  case "2": nazwa = "herbata";   cena = 3; break;
  case "3": nazwa = "czekolada"; cena = 5; break;
  case "4": nazwa = "woda";      cena = 2; break;
  case "0": nazwa = "anulowano"; cena = 0; break;
}

if (wybor === "0") {
  console.log(`Anulowano. Zwrot: ${wrzucone.toFixed(2)} zł`);
} else if (wrzucone < cena) {
  console.log(`${nazwa}: ${cena.toFixed(2)} zł`);
  console.log(`Brakuje ${(cena - wrzucone).toFixed(2)} zł`);
} else {
  console.log(`Wydaję: ${nazwa}`);
  console.log(`Reszta: ${(wrzucone - cena).toFixed(2)} zł`);
}
ZAD 4Klasyfikacja znaku★★☆

Mając pojedynczy znak w zmiennej, ustal jego kategorię: samogłoska, spółgłoska, cyfra, znak interpunkcyjny (. , ! ?) lub inny. Użyj grupowania przypadków — wszystkie samogłoski w jednej grupie case. Pamiętaj o wielkich literach.

pokaż wskazówki

Sprowadź znak do małej litery przez const z = znak.toLowerCase();. Grupuj samogłoski w case (uwzględnij też ą, ę, ó dla polskich liter). Cyfrę ASCII sprawdź warunkiem z >= "0" && z <= "9".

ZAD 5Symulator biletomatu★★★

Napisz program wydający resztę monetami. Dane: kwota do wydania w groszach. Wypisz, ile sztuk każdego nominału trzeba wydać (5 zł, 2 zł, 1 zł, 50 gr, 20 gr, 10 gr, 5 gr, 2 gr, 1 gr), pomijając nominały o zerowej liczbie. Dodatkowo, na podstawie typu wyliczeniowego Kasa (Pelna, BrakDrobnych, Pusta), instrukcją switch wypisz komunikat o możliwości wydania reszty.

pokaż wskazówki

Nominały umieść w tablicy const nominaly = [500, 200, 100, 50, 20, 10, 5, 2, 1]; i przejdź ją pętlą for…of. Dla każdego licz Math.floor(reszta / nominal), a potem reszta = reszta % nominal;. To ten sam wzorzec, którego użyliśmy przy rozbijaniu czasu w lekcji 4 — tylko powtórzony dziewięć razy zamiast dwóch.

PODSUMOWANIE

Co trzeba zapamiętać

  • switch porównuje wartość operatorem === — nie obsługuje przedziałów.
  • Bez break program wykonuje także kolejne przypadki.
  • Etykiety case jedna pod drugą grupują wartości obsługiwane tym samym kodem.
  • default łapie wszystko, co nie pasowało — warto go dopisywać.
  • Sama unia nie wymusza pełnego switch. Kontrolę kompletności uzyskasz przez użycie zmiennej wymagającej przypisania w każdej gałęzi, jawny typ wyniku funkcji albo wzorzec never.
  • Progi liczbowe zostają domeną drabinki else if.