Scala Match Expression
The match expression is one of Scala's most powerful features. It works like a sophisticated switch statement that can match values, types, data structures, and conditions — all in one clean syntax. Pattern matching makes complex branching logic readable and safe.
Basic Match Syntax
val day = "Monday"
val message = day match
case "Monday" => "Start of the work week"
case "Friday" => "Almost the weekend"
case "Saturday" => "Day off!"
case "Sunday" => "Day off!"
case _ => "A regular workday"
println(message) // Start of the work week
value match
case pattern1 => result1
case pattern2 => result2
case _ => default result ← wildcard, matches anything
The underscore _ is the catch-all pattern. It matches any value not caught by the earlier cases. Every match should include a wildcard unless you have covered every possible value (which sealed traits enforce).
Matching Numbers
def classify(n: Int): String =
n match
case 0 => "zero"
case 1 | 2 | 3 => "small" // OR pattern with |
case n if n < 0 => s"negative: $n" // guard condition
case n if n > 100 => "large"
case n => s"medium: $n" // binding the value
println(classify(0)) // zero
println(classify(2)) // small
println(classify(-7)) // negative: -7
println(classify(250)) // large
println(classify(42)) // medium: 42
Guard Conditions (if clauses)
Add a guard — an if condition inside a case — to filter matches further:
def describeTemp(celsius: Double): String =
celsius match
case t if t < 0 => "Freezing"
case t if t < 15 => "Cold"
case t if t < 25 => "Comfortable"
case t if t < 35 => "Warm"
case _ => "Very hot"
println(describeTemp(-5)) // Freezing
println(describeTemp(22)) // Comfortable
println(describeTemp(40)) // Very hot
Matching on Types
You can match a value against different types — useful when working with Any or mixed-type collections:
def describe(value: Any): String =
value match
case i: Int => s"Integer: $i"
case s: String => s"String of length ${s.length}: $s"
case d: Double => f"Double: $d%.2f"
case b: Boolean => s"Boolean: $b"
case _ => "Unknown type"
println(describe(42)) // Integer: 42
println(describe("Scala")) // String of length 5: Scala
println(describe(3.14)) // Double: 3.14
println(describe(true)) // Boolean: true
println(describe(List(1))) // Unknown type
Matching Case Classes
Case classes are built for pattern matching. The compiler generates an unapply method that extracts fields automatically:
case class Circle(radius: Double)
case class Rectangle(width: Double, height: Double)
case class Triangle(base: Double, height: Double)
sealed trait Shape
case class ShapeCircle(c: Circle) extends Shape
case class ShapeRect(r: Rectangle) extends Shape
case class ShapeTriangle(t: Triangle) extends Shape
def area(shape: Shape): Double =
shape match
case ShapeCircle(Circle(r)) => Math.PI * r * r
case ShapeRect(Rectangle(w, h)) => w * h
case ShapeTriangle(Triangle(b, h)) => 0.5 * b * h
Match Is an Expression, Not a Statement
Unlike switch statements in Java, Scala's match returns a value. You assign the result directly to a val:
val code = 404
val httpMessage: String = code match
case 200 => "OK"
case 201 => "Created"
case 400 => "Bad Request"
case 404 => "Not Found"
case 500 => "Internal Server Error"
case _ => "Unknown status"
println(httpMessage) // Not Found
Match with Tuples
Match can destructure tuples — multiple values at once:
def quadrant(x: Int, y: Int): String =
(x, y) match
case (0, 0) => "Origin"
case (px, 0) => s"X-axis at $px"
case (0, py) => s"Y-axis at $py"
case (px, py) if px > 0 && py > 0 => "Quadrant I"
case (px, py) if px < 0 && py > 0 => "Quadrant II"
case (px, py) if px < 0 && py < 0 => "Quadrant III"
case _ => "Quadrant IV"
println(quadrant(3, 4)) // Quadrant I
println(quadrant(-2, 5)) // Quadrant II
println(quadrant(0, 0)) // Origin
+Y
│ Quadrant II │ Quadrant I
──────┼─────────────────────── +X
│ Quadrant III│ Quadrant IV
-Y
Matching Lists
You can match on the structure of a list:
def sumList(numbers: List[Int]): Int =
numbers match
case Nil => 0 // empty list
case head :: Nil => head // single element
case head :: tail => head + sumList(tail) // head + rest
println(sumList(List())) // 0
println(sumList(List(5))) // 5
println(sumList(List(1, 2, 3))) // 6
List(1, 2, 3) matches head :: tail
head = 1
tail = List(2, 3)
1 + sumList(List(2, 3))
2 + sumList(List(3))
3 + sumList(Nil)
0
= 6
Exhaustiveness Checking with Sealed Traits
When you match on a sealed trait (where all subtypes are defined in the same file), the compiler warns you if you miss a case:
sealed trait TrafficLight
case object Red extends TrafficLight
case object Yellow extends TrafficLight
case object Green extends TrafficLight
def instruction(light: TrafficLight): String =
light match
case Red => "Stop"
case Yellow => "Caution"
case Green => "Go"
// If you remove 'Green', the compiler gives a warning
This compile-time exhaustiveness check prevents entire categories of bugs. You can never forget a case — the compiler tells you immediately.
Nested Patterns
case class Address(city: String, country: String)
case class Person(name: String, address: Address)
def greetCity(person: Person): String =
person match
case Person(name, Address("Mumbai", "India")) => s"Welcome, $name! Aamchi Mumbai!"
case Person(name, Address(city, "India")) => s"Hello, $name from $city!"
case Person(name, Address(city, country)) => s"Hi, $name from $city, $country"
val p1 = Person("Rohan", Address("Mumbai", "India"))
val p2 = Person("Ananya", Address("Delhi", "India"))
val p3 = Person("Carlos", Address("Madrid", "Spain"))
println(greetCity(p1)) // Welcome, Rohan! Aamchi Mumbai!
println(greetCity(p2)) // Hello, Ananya from Delhi!
println(greetCity(p3)) // Hi, Carlos from Madrid, Spain
Match vs if-else
Use match when... Use if-else when...
─────────────────────────────── ─────────────────────────────
Matching multiple specific values Two branches (true/false)
Extracting data from case classes Simple boolean condition
Type-based dispatch Range checks on one variable
Working with sealed traits The condition is complex
The match expression keeps logic organized and prevents the deep nesting that chains of if-else if-else if create. As a rule: whenever you find yourself writing more than two else if branches, consider switching to a match.
