In Ruby, how does a case/when expression decide which branch runs, and what value does the whole case return?
answer
- the when value is the receiver
- === not ==
- top to bottom, first truthy wins
- subject evaluated once
- no match and no else gives nil
basics
~20 sRuby evaluates the case subject once, then for each when value in order calls when_value === subject; the first truthy result selects that branch. The case returns that branch's last expression, the else branch if nothing matched, or nil without an else.
solid answer
~50 s`case subject` evaluates the subject once, then walks the `when` clauses top to bottom. For each value listed after `when` it calls `value === subject` — the `when` value is the receiver, so the *pattern* decides what matching means. The first truthy result runs that branch and skips everything after it, including remaining values on the same line. Several values can share a `when`, separated by commas, and a splatted array (`when *CODES`) expands into values. Like `if`, `case` is an expression: it returns the last expression of the branch that ran, the `else` branch when nothing matched, or `nil` when nothing matched and there is no `else`. Because the default `Object#===` behaves like `==`, plain literals compare by equality, while classes, ranges, regexps and procs override `===` to match by type, containment, pattern or predicate.
code
ruby · 12 linesdef classify(status)
case status
when 200, 204 then :ok
when 300..399 then :redirect
when 500.. then :server_error
end
end
p classify(204) # => :ok
p classify(302) # => :redirect
p classify(503) # => :server_error
p classify(404) # => nil (no match, no else)go deeper
Explain that each when value is tested with === against the subject, the first match wins, and the case returns that branch's value or nil when nothing matches and there is no else.
Stress that the when value is the receiver of ===, so its class decides matching; show several values per when, a splatted array, then for one-liners and lazy evaluation of later values.
Guard against the silent nil from an unmatched case in code paths that feed lookups or alerts, and choose between case, subjectless case and a hash lookup deliberately.
Treat case dispatch on types and ranges as part of the domain model: decide when a table-driven or polymorphic design should replace growing case statements across a codebase.
## The shape of a case/when ```ruby label = case status when 200, 204 then "ok" when 301, 302 then "redirect" when 500..599 then "server error" else "other" end ``` Ruby evaluates this in a fixed order: 1. Evaluate the **subject** (`status`) exactly once. 2. Take the first `when` clause and evaluate its first value. 3. Call **`value === subject`** — the `when` value is the receiver, the subject is the argument. 4. If the result is truthy, run that clause's body and **stop**. Otherwise try the next value on the same `when`, then the next `when`. 5. If no value matched, run the `else` body if there is one. The Ruby language reference describes it the same way: "the first `when` that matches is executed and all other matches are ignored", and conditions on one `when` are tried "in turn". ## Why === and why the receiver matters `===` is called the **case equality** operator, but it really means "does this pattern describe that value?". Its default, defined on `Kernel`, behaves like `==` (with an identity shortcut), so literal integers, strings and symbols compare by equality. Many core classes override it: | When value | Method called | Matches when | |---|---|---| | `200` (Integer) | `Integer#===` | `200 == subject` | | `"GET"` (String) | `String#===` | string equality | | `Integer` (a class) | `Module#===` | subject is an instance of `Integer` or a subclass | | `500..599` (Range) | `Range#===` | subject lies between begin and end | | `/timeout/i` (Regexp) | `Regexp#===` | the regexp finds a match in the subject | | `->(c) { c >= 500 }` | `Proc#===` | the proc, called with the subject, returns truthy | Because the **pattern is the receiver**, `when Integer` asks `Integer === subject` — "is the subject an Integer?". Reversing it (`subject === Integer`) would ask something entirely different, which is why `===` is not symmetric and why `case` always puts the `when` value on the left. ## Several values, splats and then - **Several values per `when`** are separated by commas and tried left to right: `when 301, 302, 307`. - **A splat** expands an array into values: `when *RETRYABLE_CODES`. Ruby's own test suite exercises `when *%w/. +/`. - **`then`** separates the values from a body on the same line: `when 404 then "not found"`. Without `then`, the body starts on the next line. - Values after the first match are **not evaluated**, so side effects in later `when` expressions may never happen. ## What case returns `case` is an expression, like `if`: - the value of the **last expression of the branch that ran**; - the value of the **`else` branch** when nothing matched; - **`nil`** when nothing matched and there is no `else`. ```ruby kind = case 418 when 200..299 then :success when 500..599 then :failure end kind # => nil ``` That `nil` is the same trap as an `if` without `else`: a monitoring bot that does `ALERTS.fetch(kind)` will get `KeyError`, and one that does `kind.to_s.upcase` will silently print an empty string. Add an `else` whenever `nil` is not a meaningful answer. ## Subjectless case `case` can also be written without a subject; each `when` then holds a boolean condition, and the first truthy one wins, like `if`/`elsif`: ```ruby case when latency_ms > 2_000 then :page when error_rate > 0.05 then :warn else :ok end ``` No `===` is involved here — each condition is simply evaluated for truthiness. RuboCop's `Style/EmptyCaseCondition` (enabled by default) prefers `if`/`elsif` for this shape. ## Summary - The subject is evaluated once; `when` values are tried top to bottom, left to right. - Each test is `when_value === subject`, so the pattern's class defines matching. - The first truthy match wins; nothing after it is evaluated. - `case` returns the chosen branch's last value, the `else` value, or `nil`.
- Are the values in later when clauses evaluated if an earlier one matches?No. Ruby evaluates `when` values lazily, in order, and stops at the first truthy `===`. Values on the same `when` after the matching one, and all later clauses, are never evaluated, so any method calls or side effects in them do not run.
- How do you match a subject against every element of an array constant in one when?Splat it: `when *RETRYABLE_CODES` expands the array into separate values, each tried with `===`. Writing `when RETRYABLE_CODES` without the splat compares the whole array to the subject with `Array#==`, which is false for an Integer subject. A `Range` or `Set` constant also works without a splat, because their `===` tests membership.
Each when value is a doorman with his own rule, and the subject is the guest walking past: the class doorman checks the guest's kind, the range doorman checks the guest's number, the regexp doorman reads the name. The guest never judges the doorman; the doorman asks the question, which is why the when value is the receiver of ===.
saying these in an interview costs you the question
- case/when compares the subject with each when value using ==.
- Ruby calls subject === when_value, so the subject decides how matching works.
- Every when clause is evaluated and the last matching branch wins.
- A case with no matching when and no else raises an error.
- A subjectless case still calls === on each when condition.