Introduction
Pattern matching, introduced in Ruby 2.7 and progressively stabilized from Ruby 3.0 through 3.2, brings powerful destructuring and conditional logic to the language. It lets you match complex data structures — arrays, hashes, nested objects — in a single expressive statement. This lesson covers the case/in syntax, array and hash patterns, guard clauses, and how pattern matching reduces boilerplate in data-heavy code.
Key Concepts
case/in: The pattern matching syntax, distinct fromcase/when. Eachinbranch matches a structural pattern.- Array Pattern: Matches array elements positionally, with support for rest (
*) and wildcard (_) operators. - Hash Pattern: Matches hash keys and optionally binds their values to local variables.
- Guard Clause: An
ifcondition appended to a pattern for additional filtering. - Pin Operator (
^): Matches against the value of an existing variable rather than binding a new one.
Real World Context
Pattern matching shines when processing API responses, webhook payloads, or any structured data. Instead of writing nested if statements to check response[:status] and dig into response[:data], you write a single case/in that destructures and validates the shape in one step. For example, a payment webhook handler can match { event: "charge.succeeded", data: { amount: Integer => amt } } and immediately have the amount bound to a variable.
Deep Dive
Pattern matching provides powerful destructuring that replaces verbose conditional logic.
Basic Case/In Syntax
The case/in syntax matches structural patterns against data:
rubycase json in { status: "ok", data: [first, *rest] } puts "First item: #{first}" in { status: "error", message: msg } puts "Error: #{msg}" else puts "Unknown format" end
Each in branch is tried in order. The first matching pattern wins. Variables like first, rest, and msg are bound automatically.
Array Patterns
Array patterns match elements by position and support rest and wildcard operators:
rubycase [1, 2, 3] in [a, b, c] puts a, b, c # 1, 2, 3 in [first, *rest] puts first, rest # 1, [2, 3] in [_, second, _] puts second # 2 (underscores ignore values) end
The *rest captures remaining elements into an array. The _ wildcard matches any single value without binding it to a variable.
Hash Patterns
Hash patterns match on keys and can bind values or enforce types:
rubyuser = { name: "Alice", age: 30, city: "NYC" } case user in { name: String => n, age: 18.. } puts "Adult: #{n}" in { name: String => n, age: ...18 } puts "Minor: #{n}" end
The String => n syntax checks that the value is a String and binds it to n. The 18.. is a range pattern matching any value 18 or greater. Hash patterns are lenient by default — extra keys in the hash are ignored.
Guard Clauses
You can add an if condition to any pattern for more precise matching:
rubycase number in n if n.positive? "positive" in n if n.negative? "negative" in 0 "zero" end
The guard is evaluated after the pattern matches. If the guard fails, Ruby moves to the next in branch.
Common Pitfalls
- Confusing
case/inwithcase/when—case/whenuses===for comparison, whilecase/inuses structural pattern matching. Mixingwhenandinin the samecasestatement is a syntax error. - Forgetting that hash patterns are lenient —
in { name: }matches any hash that contains a:namekey, even if it has many other keys. If you need exact matching, usein **nilto reject extra keys:in { name:, **nil }.
Best Practices
- Use pattern matching for structured data — API responses, config hashes, and parsed JSON are ideal candidates. Avoid pattern matching for simple equality checks where
case/whenis clearer. - Combine with Data classes for clean domain modeling —
Data.defineobjects work naturally with pattern matching, giving you type-safe destructuring of value objects.
Summary
case/inprovides structural pattern matching with automatic variable binding.- Array patterns use positional matching with
*restand_wildcards; hash patterns match on keys. - Guard clauses (
if) add conditional logic on top of structural matches.
Code Examples
response = { status: "ok", data: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }] }
case response
in { status: "ok", data: [first, *] }
puts "First: #{first[:name]}" # => "First: Alice"
in { status: "error", message: msg }
puts "Error: #{msg}"
end