AlgoMaster Logo

Switch Statement

Last Updated: May 22, 2026

High Priority
8 min read

A switch picks one branch out of many based on a value. You could write the same logic with a chain of if/else if, but once you have more than two or three branches, that chain gets noisy and easy to break. Go's switch is built to be the cleaner option, and it makes a few choices that differ from C, Java, and most other languages with a similar keyword.

The Basic Shape

A switch evaluates an expression once and compares its value against each case from top to bottom. The first match runs, and then the switch ends. If nothing matches, default runs (if you wrote one).

There are no parentheses around status (Go's syntax never needs them on control statements). There are no break statements at the end of each case. And default doesn't have to be last, though it almost always is by convention because that's where readers expect to find it.

Compared to a chain of if status == "PENDING" { ... } else if status == "SHIPPED" { ... }, the switch version reads more like a table: "when status is X, do Y". The eye scans down the first column. With long if/else if chains, you have to read the comparison each time to be sure what's being checked.

No Fallthrough by Default

This is the single biggest difference between Go's switch and the one you find in C, C++, Java, or JavaScript. In Go, each case is self-contained. When the body finishes, the switch ends. There's no need to write break at the end of every case to stop the next one from running.

In C-family languages, the opposite is true. A case falls through to the next one unless you explicitly stop it with break. Forgetting that break is a famous source of bugs.

Only the matching case runs. Go could have copied C's behavior, but the designers decided the safer default was the opposite. Most of the time you want one case to run, not two or three by accident.

Multi-Value Cases

A single case can list more than one value, separated by commas. Any of them matches.

This is how you group statuses that should be treated the same way. The body still only runs once, even if you list ten values in the case.

Without multi-value cases you'd write something like this, which gets repetitive fast:

The comma form keeps related branches together without copy-paste. It also makes it easier to spot at a glance which statuses belong to which group.

Explicit fallthrough

Sometimes you really do want one case to fall through into the next. Go makes you say so with the fallthrough keyword. It has to be the last statement in the case, and it jumps unconditionally into the next case body (skipping the next case's condition check entirely).

tier is "GOLD", so the GOLD case matches and prints "Free standard shipping". Then fallthrough jumps into the SILVER body without checking whether the tier is silver, prints the discount line, falls through again into BRONZE, and prints the coupon line. The chain stops there because BRONZE doesn't end with fallthrough.

A few things about fallthrough:

  • It only moves to the next case in source order. You can't fall through to an arbitrary case.
  • It skips the next case's condition. The next body always runs, even if its value wouldn't match.
  • It has to be the last statement in the case. The compiler rejects it anywhere else.
  • It's rare in Go code. Most of the time multi-value cases are what you actually want.

The tier example above is one of the few times where fallthrough reads cleanly: a higher tier gets every benefit of the lower tiers, layered on top.

Expressionless Switch

You can leave the switch expression off entirely. When you do that, Go treats every case as a boolean condition and runs the first one that's true. This turns the switch into a clean replacement for a long if/else if chain.

There's no value after the switch keyword, just an open brace. Each case carries its own condition. The first true condition wins, the rest are skipped. cartTotal is 145, so the >= 100 case matches (the >= 200 case fails first), and the discount is 0.10. The >= 50 case would also be true, but switches stop at the first match.

Compare that to the if/else if version:

Both work. The expressionless switch wins when you have more than two or three branches because the cases line up visually and you don't repeat else if over and over. Many Go codebases use this pattern for tiered logic: pricing tiers, log levels, HTTP status ranges, retry backoff windows.

The order of cases matters. Switches always evaluate top to bottom, so put the more specific or higher-priority conditions first. If we flipped the order above and put cartTotal >= 50 at the top, a cart worth $300 would still match >= 50 first and get a 5% discount instead of 20%.

The diagram walks through the evaluation order. Each diamond is a case condition. The switch starts at the top and stops the moment one returns true. Anything below that point is never even checked, which is why ordering from most specific to least specific matters.

Switch With an Init Statement

Just like if, a switch can declare a variable in its header. The variable is scoped to the switch and lives only as long as the switch runs.

The switch c := normalize(category); c line does two things. First it calls normalize and binds the result to c. Then it uses c as the switch expression. The variable c only exists inside the switch, so it can't leak into surrounding code. This is the same pattern you've seen on if, and the motivation is identical: keep helper values scoped tightly to where they're used.

You can also use the init statement for setup work, like computing a derived value or grabbing a current time:

The switch has an init (hour := time.Now().Hour()) but no expression after the semicolon, so it acts as an expressionless switch where each case is a boolean condition. hour is scoped to the switch and disappears after it ends. The exact output depends on the current time, but for a 3 PM run it would print Afternoon order, ships today..

Cases Don't Have to Be Constants

In C and similar languages, case values must be compile-time constants. Go doesn't have that rule. A case can be any expression of a type comparable to the switch expression. That includes function calls, variables, and computed values.

The thresholds are regular variables, not constants. The switch evaluates each case at runtime against the current values. This is exactly what makes the expressionless switch so useful for thresholds, scoring, and routing. If thresholds came from configuration or a database, this still works.

You can do the same thing in an expression switch:

The cases compare against variables (openStatuses, closedStatuses). This isn't a common pattern because constants usually read more clearly, but Go allows it.

The only requirement is that each case value is comparable to the switch expression's type using ==. Strings, numbers, booleans, pointers, channels, interfaces, and arrays of comparable types all work. Slices, maps, and functions don't, because Go doesn't define equality on them (except against nil).

Order of Evaluation

Cases are checked top to bottom. As soon as one matches, its body runs and the switch ends. This is true for both expression switches and expressionless switches.

orderAge is 5. The switch checks each case in order: > 30 fails, > 7 fails, > 0 succeeds, so that body runs. The default doesn't run because something already matched.

If you reverse the order, you change the result. With case orderAge > 0 first, every positive orderAge would print "Recent order" and the more specific cases above would never trigger. With expression switches it's less of a problem because values are usually distinct strings or numbers, but with expressionless switches and overlapping ranges, ordering is the whole game.

When to Pick Switch Over If/Else

There's no hard rule, but a few patterns favor switch:

Use caseBetter choice
Comparing one value against many possible valuesswitch x { case ... }
Long if/else if chain of unrelated conditionsswitch { case cond1: ... }
Picking a tier based on a numeric thresholdExpressionless switch with ordered cases
Two or three branches with the same conditionif/else
Branches that share complex predicatesif/else if (less repetition)

The clearest signal is the shape of the conditions. If you find yourself writing the same comparison over and over (if x == "A" { ... } else if x == "B" { ... }), use switch. If your conditions are unrelated, mixing types and operations, if reads more honestly.

Here's a side-by-side using the order status example again:

The switch version has one comparison column (status vs. each case), so the eye picks out the structure right away. The if/else version forces you to re-read status == three times.

Order Lifecycle as a Switch

Order status is one of the most common places you'll reach for a switch in an e-commerce system. The state diagram below shows the transitions a typical order goes through, and the switch that follows reacts to whichever state the order is currently in.

An order starts in PENDING, moves to SHIPPED once payment clears, and ends in either DELIVERED or CANCELLED. A switch is a natural fit for handling each state, because there's a fixed list of known values and each one needs a different response.

Notice the default case at the end. It catches values the program doesn't know how to handle: a typo, a new status that hasn't been added yet, a value from a third-party system. Without default, the unknown status would silently do nothing, and that's almost always worse than a clear log line. A good rule of thumb: include a default whenever the set of possible values isn't fully under your control.

Combining the Pieces

The features above (multi-value cases, expressionless form, init statement, default) compose freely. The following example pulls several of them together: a function that decides what discount to apply based on both the customer's tier and the cart total.

The outer switch is an expression switch on tier. The PLATINUM and GOLD cases share the same logic, so they're grouped. Inside that case, an expressionless switch picks the percentage based on the cart total. SILVER and BRONZE use simple ifs. The unknown "DIAMOND" tier hits default and gets no discount.