skip to content

In Go, what does a `switch` with no expression after the keyword compare its cases against?

level: middleimportance: should knowfreq 55%

answer

  1. the tag has a hidden default value
  2. each case must evaluate to a boolean
  3. it replaces a familiar chain of ifs
  4. think of it as switch true

basics

~10 s

It compares against the boolean value true, so every case is a boolean expression and the first one that is true runs. This expressionless form is Go's idiomatic replacement for a long if/else-if chain.

solid answer

~40 s

Writing `switch {` is exactly `switch true {`: the implicit tag is the untyped constant `true`, so every case expression must be boolean, and the first clause that evaluates to true runs. Clauses are evaluated in source order, which makes ordering significant when conditions overlap — `case n < 100:` written before `case n < 10:` makes the second unreachable. Go programmers reach for this instead of a long if/else-if ladder because the clauses line up visually and `default` gives the else branch a name. It takes the same optional init statement as any switch: `switch tok := next(); {` runs the assignment first and scopes `tok` to the switch — tag, every case expression, and every body — so a temporary used only for dispatch never leaks into the enclosing function.

code

go · 9 lines
go
switch tok := lex.next(); {
case tok.Kind == kindEOF:
	return nil, io.EOF
case tok.Kind == kindComment:
	return nil, nil
default:
	return parse(tok)
}
// tok is not in scope after the closing brace

go deeper

for a junior

Recall the shape: nothing after the switch keyword, then cases holding boolean tests. Reading it as switch true and picking the first true clause is enough at this level.

for a middle

Explain that the implicit tag is the constant true, that every case expression must be boolean, and that the first true clause wins so overlapping conditions must be ordered deliberately.

for a senior

Show judgment about readability: when this form beats an if/else ladder, when a lookup table beats both, and how the init statement keeps a dispatch temporary scoped to the switch.

for a principal

Set the house style. Decide whether multi-way dispatch in your codebase is written as an expressionless switch, a table, or small named predicates, and record the reason so reviewers stop relitigating it.

## Two shapes, one statement Go's expression switch comes in two shapes. With a tag, `switch v { case a: ... }` compares `v` for equality against each case. Without one, `switch { case cond: ... }` has an implicit tag of the untyped boolean constant `true`, and each case is compared against it — which is just another way of saying **each case must be a boolean expression, and the first true one wins**. That equivalence is worth stating literally in an interview: `switch {` and `switch true {` are the same statement, and the second form compiles and behaves identically. It explains everything else about the form. ## Why it exists Go has no ternary operator and no `elif` keyword, so a multi-way decision written with `if` becomes a ladder: ``` if n < 0 { return "negative" } else if n == 0 { return "zero" } else { return "positive" } ``` The expressionless switch says the same thing with the conditions in a column, no `else if` noise, and an explicitly named final branch: ``` switch { case n < 0: return "negative" case n == 0: return "zero" default: return "positive" } ``` This is idiomatic Go and appears throughout the standard library. It is a readability choice, not a performance one: for arbitrary boolean predicates the compiler emits the same sequence of tests either way. Optimisations such as binary search or a jump table apply to a *tagged* switch with many constant cases, not to a chain of predicates. ## Order is part of the meaning Because the first true clause wins and later clauses are never evaluated, the order of overlapping conditions is load-bearing. Ranges must be written from narrowest to widest, or from one end of the range to the other: ``` switch { case score >= 90: grade = "A" case score >= 80: grade = "B" case score >= 70: grade = "C" } ``` Swapping any two of those lines silently changes the result for a whole band of inputs, and nothing in the toolchain flags it. A reordering bug of this kind is one of the few places where an expressionless switch is *less* safe than a set of independent `if` statements with early returns, and it is worth a comment when the bands are not obviously ordered. ## The init statement Every switch may carry a simple statement before the tag, separated by a semicolon, and the expressionless form keeps it — you simply leave the tag empty: ``` switch tok := lex.next(); { case tok.Kind == kindEOF: return nil case tok.IsKeyword(): return parseKeyword(tok) } ``` The scope of `tok` is the switch statement: the tag expression, all case expressions, and all case bodies — and nothing outside. This is the same construct as `if`'s init statement, and it exists so a value needed only for dispatch does not linger in the enclosing function where a later reader has to work out whether it is still live. ## When not to use it Two cases argue for something else. If every clause is an equality test against the same value, a **tagged** switch says so more directly, lets the compiler reject duplicate constants, and permits the dense-constant optimisations. `switch { case k == a: ... case k == b: ... }` is a tagged switch written the long way. If the clauses map values to values rather than to behaviour — a code to a message, a kind to a name — a package-level `map` or array literal is usually clearer than either switch form, and it has the practical advantage that a test can enumerate its keys, which no switch permits. ## Summary No expression means an implicit `true` tag; case expressions must be boolean; the first true clause runs and the rest are never evaluated; ordering overlapping conditions is a correctness concern; the optional init statement scopes a temporary to the switch alone.

  • How does the optional init statement in a switch header behave?
    `switch x := f(); x {` runs `x := f()` first and scopes `x` to the switch statement — the tag, every case expression and every case body — and nowhere else. The same works with no tag: `switch tok := next(); {`. It is the same construct as `if`'s init statement, and it exists so a temporary used only for dispatch does not leak into the surrounding function.
  • Is an expressionless switch any faster than the equivalent if/else-if chain?
    No. With arbitrary boolean conditions the compiler generates the same sequence of tests, so the choice is about readability. Optimisations such as binary search or a jump table apply to a tagged switch with many constant cases, not to a chain of predicates, so there is nothing to gain by rewriting one form as the other for speed.
  • When is an expressionless switch the wrong tool?
    When every clause is an equality test on one value — a tagged `switch v {` says that more directly and lets the compiler reject duplicate constants. And when the branches map values to values rather than to behaviour, a package-level map or array literal is usually clearer than either switch, with the practical bonus that a test can enumerate its keys.

saying these in an interview costs you the question

  • Says the implicit tag is nil or the zero value
  • Thinks clause order does not affect the result
  • Believes case expressions may be non-boolean here
  • Claims a default clause is mandatory in this form
  • Argues it is faster than an if/else-if chain