Pattern Matching (case/in)

Reviewed & published by Brayan K

Pattern matching with case/in is a Ruby 3 feature that deconstructs arrays and hashes by shape and binds their inner values to variables in a single, readable expression.

Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

You'll learn array and hash patterns, variable binding, guards, the find pattern, and the one-line => and in forms.

What You'll Learn in This Lesson

1️⃣ Array & Hash Patterns with Binding

A case/in branch matches the structure of a value and binds its parts to new variables. Array patterns match position and length; hash patterns match by key. Add an if guard to refine a match further. A trailing bare key like name: binds the value of :name.

# case/in is Ruby 3's structural pattern matching.
# It deconstructs data and binds variables in one move.

status = [200, "OK"]

case status
in [200, message]              # match a 2-element array starting with 200
  puts "Success: #{message}"
in [code, _] if code >= 500    # guard: only if code is 500+
  puts "Server error #{code}"
in [code, message]
  puts "Other: #{code} #{message}"
end

# Hash patterns match by key and bind the values:
user = { name: "Ada", role: :admin }

case user
in { role: :admin, name: }     # bind :name, require role == :admin
  puts "#{name} is an admin"
in { name: }
  puts "#{name} is a regular user"
end

2️⃣ Find Patterns, Types & Alternatives

The find pattern [*, x, *] locates a single element anywhere in an array. You can match by class (Integer => x binds only if the value is an Integer), and combine choices with |. These compose to express surprisingly rich conditions concisely.

# Find pattern: match a sub-sequence inside an array.
log = ["start", "load", "ERROR: disk", "save", "done"]

case log
in [*, /ERROR/ => problem, *]   # find an element matching the regex
  puts "Found problem: #{problem}"
else
  puts "No errors"
end

# Type patterns: match the class, then deconstruct
point = { x: 3, y: 7 }
case point
in { x: Integer => x, y: Integer => y }
  puts "Point at (#{x}, #{y})"
end

# Alternative patterns with |
case "warn"
in "info" | "debug"
  puts "low priority"
in "warn" | "error"
  puts "needs attention"
end

3️⃣ One-Line Patterns: => and in

For quick work you don't need a full case. The rightward-assignment form value => pattern destructures and binds, raising if it doesn't match. The boolean form value in pattern returns true or false — handy in conditions.

# One-line patterns.
# The => form is a "rightward assignment" that MUST match (or raises).
config = { host: "localhost", port: 3000 }
config => { host:, port: }
puts "#{host}:#{port}"

# The in form returns true/false — great for a quick boolean test.
puts(({ name: "Ada" } in { name: String }))   # true
puts(([1, 2] in [Integer, Integer]))          # true
puts(([1, "x"] in [Integer, Integer]))        # false

Your turn. A shape hash is matched on its :type. Fill in the ___ so the square branch binds the :side value.

# 🎯 YOUR TURN — replace each ___ then run with: ruby shape.rb
shape = { type: :circle, radius: 10 }

case shape
in { type: :circle, radius: }     # bind radius from the hash
  puts "Circle area ~ #{(3.14 * radius * radius).round(1)}"
in { type: :square, ___ }         # 👉 bind the :side value here
  puts "Square area = #{side * side}"
end

# ✅ Expected output:
#    Circle area ~ 314.0

Mini-Challenge: Event Router

Write a method that routes UI events by their shape using case/in. This is a common real-world use of pattern matching — dispatching on the structure of incoming data. Run with ruby events.rb.

# 🎯 MINI-CHALLENGE: an event router.
# Save as events.rb and run with: ruby events.rb
#
# Write a method describe(event) that uses case/in to handle:
#   { type: "click", x:, y: }  -> "click at (x, y)"
#   { type: "key", key: }      -> "key press: <key>"
#   { type: }                  -> "unknown event: <type>"
# Then call it with three different event hashes.
#
# ✅ Example output:
#    click at (5, 9)
#    key press: Enter
#    unknown event: scroll

# your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Pattern Matching

PatternExample
Arrayin [a, b, c]
Hashin { name:, age: }
Guardin [n] if n > 0
Typein Integer => n
Findin [*, x, *]
Alternativesin 1 | 2 | 3
One-line bindval => pattern

🎉 Lesson Complete!

Practice quiz

Which keyword introduces structural pattern matching in Ruby 3?

  • case/when
  • match/with
  • case/in
  • switch/case

Answer: case/in. case/in is Ruby 3's structural pattern matching; case/when does value matching with ===.

What happens if no in pattern matches and there is no else clause?

  • It raises NoMatchingPatternError
  • It returns nil
  • It returns false
  • It skips silently

Answer: It raises NoMatchingPatternError. case/in with no matching branch and no else raises NoMatchingPatternError.

In a hash pattern, what does a bare key like name: do?

  • Requires name to equal a string
  • Deletes the key
  • Compares with ===
  • Binds the value of :name to a variable

Answer: Binds the value of :name to a variable. A bare key binds the value of that key to a new local variable of the same name.

What does the find pattern [*, x, *] do?

  • Matches only 3-element arrays
  • Finds a single element anywhere in the array
  • Reverses the array
  • Matches an empty array

Answer: Finds a single element anywhere in the array. The find pattern with two splats locates one element anywhere inside the array.

What does ({ name: "Ada" } in { name: String }) return?

  • true
  • false
  • nil
  • an error

Answer: true. The one-line in form returns true when the value matches the pattern.

What does ([1, "x"] in [Integer, Integer]) return?

  • true
  • nil
  • false
  • an error

Answer: false. "x" is not an Integer, so the structural match fails and in returns false.

What does the one-line form value => pattern do when it does NOT match?

  • Returns false
  • Raises NoMatchingPatternError
  • Returns nil
  • Returns the value

Answer: Raises NoMatchingPatternError. The => rightward-assignment form must match or it raises NoMatchingPatternError.

How do you combine alternative patterns in a single in branch?

  • With &&
  • With or
  • With commas
  • With the | operator

Answer: With the | operator. Alternatives are combined with |, as in: in "warn" | "error".

What does Integer => x do in a pattern?

  • Always binds x
  • Binds x only if the value is an Integer
  • Converts x to an Integer
  • Compares x with ==

Answer: Binds x only if the value is an Integer. A type pattern matches the class first, binding x only when the value is an Integer.

Which methods let your own objects support pattern matching?

  • to_a and to_h
  • match and scan
  • deconstruct and deconstruct_keys
  • each and map

Answer: deconstruct and deconstruct_keys. deconstruct enables array patterns and deconstruct_keys enables hash patterns.

Continue this course

Frequently asked questions

How is case/in different from case/when?

case/when tests each branch with the === operator and is about value matching — 'is this value in this range / of this class?'. case/in is structural pattern matching: it deconstructs arrays and hashes, binds inner values to variables, applies guards, and matches by shape. Use when for simple value dispatch and in when you want to pull apart nested data and bind pieces of it at the same time.

What happens if no in pattern matches?

Unlike case/when, a case/in with no matching branch and no else clause raises a NoMatchingPatternError. This is deliberate — pattern matching is often used where you expect data in a known shape, so an unmatched case usually signals a bug or unexpected input. Add an in _ (which matches anything) or an else clause when you want a safe fallback.

What does the find pattern [*, x, *] do?

The find pattern searches for a single element anywhere inside an array. [*, /ERROR/ => problem, *] means 'some elements, then one matching /ERROR/ which we bind to problem, then some more elements'. It's ideal for locating an item in a list without knowing its position. Only one find pattern (with two splats) is allowed per array pattern.

Can I use pattern matching on my own objects?

Yes. If your object defines deconstruct (returning an array) it can be matched with array patterns, and if it defines deconstruct_keys (returning a hash) it can be matched with hash patterns. Struct and Data objects implement both automatically, which is why pattern matching pairs so naturally with them. This lets you match against domain objects, not just raw arrays and hashes.