Pattern matching

Scala · Essentials · 12 min

Pattern matching is Scala's main way to branch on the shape of data.

A match expression tests a value against patterns in order and evaluates the first matching case: x match { case pattern => result ... }. It's an expression, so it returns a value.

Pattern kinds:

  • Literals: case 0 =>, case "quit" =>.
  • Variables and the wildcard: case n => binds anything to n; case _ => matches anything without binding.
  • Types: case s: String => matches and casts in one step.
  • Constructors of case classes, nested freely: case Order(id, Customer(name, _)) =>.
  • Tuples and sequences: case (a, b) =>, case List(first, rest*) =>, case head :: tail =>.
  • Alternatives case 1 | 2 => and binders case p @ Point(0, _) =>.

A guard adds a condition: case n if n < 0 =>. If no case matches, a MatchError is thrown at runtime, which is why matching on a sealed hierarchy or enum is preferred: the compiler then warns when a case is missing.

Variable patterns start with a lowercase letter. To compare against an existing value instead of binding a new name, use backticks (` case expected => `) or an uppercase constant.

Patterns also work in val definitions (val (q, r) = (7 / 2, 7 % 2)), in for generators and in lambdas passed to collect.

Example

case class Customer(name: String, vip: Boolean)
case class Order(id: Int, customer: Customer, total: Double)

def describe(x: Any): String = x match
  case 0                         => "zero"
  case n: Int if n < 0           => s"negative int $n"
  case n: Int                    => s"int $n"
  case s: String                 => s"string of length ${s.length}"
  case (a, b)                    => s"pair $a and $b"
  case List(first, rest*)        => s"list starting $first, ${rest.size} more"
  case Order(id, Customer(name, true), _) => s"VIP order $id for $name"
  case _                         => "something else"

println(describe(-4))                         // negative int -4
println(describe("hey"))                      // string of length 3
println(describe((1, "a")))                   // pair 1 and a
println(describe(List(9, 8, 7)))              // list starting 9, 2 more
println(describe(Order(7, Customer("Ana", true), 99.0)))  // VIP order 7 for Ana
println(describe(3.5))                        // something else

val (q, r) = (17 / 5, 17 % 5)
println(s"$q remainder $r")                   // 3 remainder 2

Remember

  • Cases are tried top to bottom; the first match wins
  • Patterns destructure case classes, tuples and sequences, with guards
  • Lowercase names bind; use backticks to compare to an existing value
  • Unmatched values throw MatchError

Watch out for

  • Writing case expected => meaning the variable expected: it creates a new binding that matches everything.

Check yourself

  1. val limit = 10; 5 match { case limit => "hit"; case _ => "miss" } returns?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.scala-lang.org/scala3/book/control-structures.html#match-expressions