Start learning free

LESSON 83 OF 414 · CONDITIONAL STATEMENTS

Pattern Matching in Scala: match, Guards and Tuples

Lesson Read the full explanation

You already know how to branch with if / else if chains. Scala has a second branching tool that is usually the better one: match. It looks like switch from other languages, but it is stronger in two ways. First, match is an expression — the whole thing evaluates to a value, so you can bind its result directly with val. Second, each case holds a pattern, not just a single value: a constant, a captured variable with a condition attached (a guard), or the wildcard _ that matches anything.

val temp = 3
val advice = temp match {
    case t if t >= 30 => "stay in the shade"
    case t if t >= 10 => "perfect day"
    case _ => "bring a jacket"
}
println(advice)   // prints: bring a jacket

Each case reads case pattern => value. Cases are checked top to bottom and the first one that fits wins: its value becomes the value of the whole match. The guard case t if t >= 30 captures the number into t and matches only when the condition holds — Scala has no range pattern, so guards are how you say "30 or above".


The trap:

Unlike an if with no else, a match that fits no case does not quietly do nothing. Cover only scores of 80 and above, feed it a 55, and the program compiles without complaint — then crashes at runtime with an error like this:

scala.MatchError: 55 (of class java.lang.Integer)

The message tells you which value slipped through every case: 55. The fix is a wildcard case, written case _ => ..., which matches anything the earlier cases didn't. End every match over an open-ended value with one, and a MatchError can never reach your users.


Matching a tuple:

Patterns can also take data apart. A tuple like (0, 5) packs two values together; a tuple pattern unpacks it and tests both slots at once:

val point = (0, 5)
val location = point match {
    case (0, 0) => "origin"
    case (0, y) => s"on the y-axis at $y"
    case _ => "somewhere else"
}

(0, 0) matches one exact pair. (0, y) is the interesting one: the literal 0 must be equal, while the lowercase name y matches any second slot and captures it, so the right-hand side can use it. That double duty — test the shape, extract the parts — is why this feature is called pattern matching rather than just switching.


Assignment
Follow the Coding Tutorial to grade a score with guards, then locate a point with a tuple match:

  • Declare score as 87 and match it into grade: exactly 100 is "perfect", s with s >= 90 is an "A", s with s >= 80 is a "B", and anything else (_) is a "C". Print the grade.
  • Build point as the tuple (0, 5) and match it into location: (0, 0) is "origin", (0, y) is s"on the y-axis at $y", and _ is "somewhere else". Print the location.

The exact expected output is:

grade = B
location = on the y-axis at 5

Hint
A guard goes between the captured name and the arrow: case s if s >= 90 => "A". If the program dies with scala.MatchError, you're missing the case _ wildcard at the bottom.


STEP 1 OF 12
    Ctrl + Enter to check

    SCALA · CONDITIONAL STATEMENTS

    Pattern Matching in Scala, explained

    What pattern matching is

    You already know how to branch with if and else if. Scala has a second branching tool, match, that often reads better when one value is tested against many possibilities. It resembles switch in other languages, but it is stronger in two ways:

    • match is an expression. The whole thing evaluates to a value, so you can bind the result straight to a val.
    • Each case holds a pattern, not only a constant. A pattern can be a literal, a name that captures the value, a name with a condition attached, a tuple that takes data apart, or the wildcard _ that matches anything.
    val temp = 3
    val advice = temp match {
        case t if t >= 30 => "stay in the shade"
        case t if t >= 10 => "perfect day"
        case _ => "bring a jacket"
    }
    println(advice)   // bring a jacket
    

    Each case reads case pattern => value. Cases are tried from top to bottom, and the first one that fits wins. Its value becomes the value of the whole match. The part t if t >= 30 is a guard: the name t captures the number, and the case only matches when the condition after if is true. Scala has no range pattern, so guards are how you say "30 or above".

    The trap: a match with no matching case

    An if with no else quietly does nothing when its condition is false. A match does not. If no case fits, the program throws a scala.MatchError at run time:

    val n = 55
    val r = n match {
        case s if s >= 80 => "B"
    }
    println(r)
    
    Exception in thread "main" scala.MatchError: 55 (of class java.lang.Integer)
    

    The compiler accepts this code. The message names the value that slipped through every case, which is 55 here. The fix is a wildcard case, case _ => ..., which matches anything the earlier cases did not. End every match over an open-ended value, such as an Int, with one.

    Matching a tuple

    Patterns can take data apart. A tuple such as (0, 5) packs two values together, and a tuple pattern unpacks it and tests both slots at once:

    val point = (0, 5)
    val location = point match {
        case (0, 0) => "origin"
        case (0, y) => s"on the y-axis at $y"
        case _ => "somewhere else"
    }
    

    (0, 0) matches one exact pair. (0, y) is the interesting one: the literal 0 must be equal, while the lowercase name y matches any second value and captures it, so the right side can use it. A pattern both tests the shape and extracts the parts, which is why the feature is called pattern matching and not just switching.

    A few more forms are handy. Alternatives are separated with |, as in case "sat" | "sun" => "weekend". The string prefix s in s"... $y" inserts the value of y into the text.

    The lesson's exercise

    The exercise builds both examples step by step.

    1. Declare score as a val holding 87. The match needs a value to branch on.
    2. Open the match as an expression. Write val grade = score match { ... } as one declaration. No return and no assignment inside each branch is needed; whichever case runs becomes the value of grade.
    3. Fill in four cases. A constant case for 100 gives "perfect". Two guarded cases follow: s with s >= 90 gives "A", and s with s >= 80 gives "B". The wildcard gives "C". Order matters: if the >= 80 guard came before the >= 90 one, a 95 would be graded B.
    4. Print the grade with the interpolated string s"grade = $grade". The >= 80 guard catches 87, so the output is grade = B.
    5. Build point as the tuple (0, 5).
    6. Open the second match on point and bind it to val location, with the same shape as before.
    7. Add the three tuple cases: (0, 0) for "origin", (0, y) for s"on the y-axis at $y", and the wildcard for "somewhere else". The pair (0, 5) fails the first case and fits the second, with y bound to 5.
    8. Print the location with s"location = $location".
    val score = 87
    val grade = score match {
        case 100 => "perfect"
        case s if s >= 90 => "A"
        case s if s >= 80 => "B"
        case _ => "C"
    }
    println(s"grade = $grade")
    val point = (0, 5)
    val location = point match {
        case (0, 0) => "origin"
        case (0, y) => s"on the y-axis at $y"
        case _ => "somewhere else"
    }
    println(s"location = $location")
    

    The program prints:

    grade = B
    location = on the y-axis at 5
    

    Common mistakes

    • Leaving out case _. A value that matches nothing throws MatchError at run time.
    • Putting a broad case first. Cases are tried in order, and a guard such as s >= 80 would catch every score of 90 or more before the A case is reached.
    • Expecting a range pattern. There is none. Use a guard: case s if s >= 90.
    • Forgetting that lowercase names capture. In case (0, y), y is a new variable that matches anything, not a comparison with an existing variable called y.
    • Using = instead of => between a pattern and its result.

    Key takeaways

    • match is an expression: it gives a value that you can bind with val.
    • Cases run top to bottom, and the first matching case wins.
    • A guard (case s if s >= 90) adds a condition to a captured name.
    • A tuple pattern tests the shape and captures the parts at once.
    • Always end a match over an open-ended value with case _ to avoid MatchError.
    Keep going with ScalaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free