Pattern Matching in Ruby 3
Fast recall
Quick revision
Core answer
case expression uses in branches, such as in { type: "paid", order_id: }, to both verify the keys and capture order_id. Array patterns can separate a first value from the rest, guards add a condition, and the pin operator ^ compares with an existing variable instead of replacing it. A case/in without a matching branch or else raises NoMatchingPatternError, so use a fallback when unknown input is valid.Whether you know pattern matching (Ruby 3.0+) uses `case/in` to destructure + bind on shape (array/hash/type/range).
Using `when` instead of `in` (case/in, not case/when), or forgetting `else` (NoMatchingPatternError if no match).
Pattern matching (Ruby 3): case/in destructures + binds on shape (array/hash/type/range). `=>` binds. No match -> NoMatchingPatternError (use else).
Interview-ready guide
Interview answer
Ruby pattern matching is a way to test structured data and name selected parts in the same operation. It is written with case/in, standalone rightward assignment using =>, or the boolean in expression. Unlike a chain of key lookups, the pattern describes the shape the program expects.
Each in branch is tried in order. Literal, class, range, array, and hash patterns can be nested. A variable in a pattern normally captures a value, so an existing variable must be pinned with ^ when it should act as a comparison value. An if guard can check an extra rule after the structural match succeeds.
For example, an event handler can match { type: "paid", order_id: Integer => id }. That branch runs only when the event has the expected type and an integer order ID, and id is immediately available to the body. A second branch can handle { type: "failed", reason: }, while else records an unsupported event.
Array and hash patterns have different boundaries. [first, second] does not match a three-item array unless a rest pattern is included. { name: } can match a hash with additional symbol keys; { name:, **nil } requires no extras. Ruby hash patterns work with symbol keys, which matters after parsing JSON with string keys.
I use pattern matching when shape is central to the decision, such as events, command results, or nested API data. Ordinary accessors or simple conditionals remain clearer for one field. If unknown shapes are possible, an else branch makes that boundary explicit instead of allowing NoMatchingPatternError to escape accidentally.
Pattern Matching in Ruby 3
Direct Answer
Ruby pattern matching checks the shape of structured data and can bind matched parts to local variables. A case expression uses in branches, such as in { type: "paid", order_id: }, to both verify the keys and capture order_id. Array patterns can separate a first value from the rest, guards add a condition, and the pin operator ^ compares with an existing variable instead of replacing it. A case/in without a matching branch or else raises NoMatchingPatternError, so use a fallback when unknown input is valid.
Deep Dive & Architecture
A pattern has two jobs: it rejects values with the wrong structure and exposes useful pieces from values that fit. Ruby first takes the expression after case, then tests the in branches from top to bottom. The first successful branch supplies its captured local variables to that branch body. when and in are different branch systems and cannot be mixed inside one case expression.
Value patterns rely on ===, so a class checks type, a range checks membership, and a literal checks its expected value. Variable names behave differently: in code assigns the matched value to code; it does not compare with a previous local variable of that name. Writing in ^expected_code changes the role to comparison. Guards such as if amount.positive? belong after the structural part and may use values just captured by the pattern.
Collection rules prevent subtle assumptions. An array pattern describes all positions unless * or *rest absorbs remaining elements. A hash pattern normally asks only for listed symbol keys and ignores other keys. Adding **nil states that no unknown keys are allowed. JSON parsers commonly produce string-keyed hashes, so code must symbolize keys deliberately or match another representation before using a hash pattern.
Standalone forms express two different intentions. payload => { user: { id: } } is an assertion plus unpacking operation and raises when the contract is broken. payload in { cached: true } produces true or false and is useful in a predicate. A multi-branch case/in is better when several valid shapes lead to different behaviour. Omitting both a successful branch and else makes the case exhaustive by raising NoMatchingPatternError.
User-defined objects can participate without being converted manually. Array-style matching asks an object for deconstruct; hash-style matching asks for deconstruct_keys. That is useful for domain value objects, but the returned structure becomes part of the object's public matching contract. It should expose stable meaning rather than private storage details.
Pattern matching improves code only when the visual shape helps the reader. Deep patterns repeated across handlers can become another form of coupling to external payloads. At an integration boundary, validate and normalize an incoming event once, then let the rest of the application work with a small domain object. This separates unreliable transport shape from dependable business behaviour.
Code Example
event = { type: 'paid', order_id: 42, amount: 1_500 }
message = case event
in { type: 'paid', order_id: Integer => id, amount: 1.. => cents }
"capture order #{id} for #{cents} cents"
in { type: 'failed', order_id:, reason: }
"order #{order_id} failed: #{reason}"
else
'unsupported event'
end
puts message
expected_id = 42
matches_expected = event in { order_id: ^expected_id }
puts matches_expected # true; pin compares
Common Mistakes & Pitfalls
Using when instead of in (case/in, not case/when), or forgetting else (NoMatchingPatternError if no match).
Check your understanding
Answer these without looking back at the article.
- Why does a bare variable bind while a pinned variable compares?
- How do array and hash patterns differ when the value contains extra items?
- When should an event handler include an `else` branch?
Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.