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.

Leave a Comment

Your email address will not be published. Required fields are marked *