At first glance Scala's pattern matching looks similar to Java's switch statement. But it's only the first impression because after analyzing the differences we end up with some smarter idea.
Data Engineering Design Patterns

Looking for a book that defines and solves most common data engineering problems? I wrote
one on that topic! You can read it online
on the O'Reilly platform,
or get a print copy on Amazon.
I also help solve your data engineering problems 👉 contact@waitingforcode.com 📩
In this another post of One Scala Feature per week series we'll focus on the feature called pattern matching. In the first section we'll discover its definition. In the next section we will see its use cases. Finally, in the last part we will read about some reflex we often have in the first months of working with Scala.
Pattern matching definition
Pattern matching is a mechanism letting us to check some value against one or more defined pattern. The similarity with switch statement comes mainly from the grammar that looks like:
matchedValue match { case SomeTypeToMatch => "xxx" case SomeOtherTypeToMatch => "yyy" case _ => "default value" }As you can see, pattern matching is built of the matched value (matchedValue) and one or more cases against which the value is tested (case ... => "..."). It's a simple version of pattern matching. More advanced one uses the system of pattern guards that help to limit the conditions under which given case matches. The following snippet shows the use of guards used to detect if given number is even or odd, or if matched value is not an Int:
it("should apply with guards") { // Please note it's only for illustration purpose // Obviously, we could avoid an IllegalArgumentException by simply // transforming Any argument to an Int def getNumberLabel(nr: Any): String = { nr match { case number: Int if number % 2 == 0 => "even" case number: Int if number % 2 != 0 => "odd" case _ => throw new IllegalArgumentException("Test can be made only on integers") } } val labelFor2 = getNumberLabel(2) labelFor2 shouldEqual "even" val labelFor3 = getNumberLabel(3) labelFor3 shouldEqual "odd" val error = intercept[IllegalArgumentException] { getNumberLabel("text") } error.getMessage shouldEqual "Test can be made only on integers" }
The above code snippet shows the use of guards that in fact are simple if statements applied on matched elements.
Pattern matching use cases
Pattern matching with guards looks like a switch statement after all. However it's not the case since it can do a lot more stuff, as matching only types, recursively match a nested data structure and so on. All interesting use cases, found in already quoted in the post about "Pattern matching in Scala", are listed below:
- matching nested structures, aka deep matches - pattern matching can be used to make recursive calls on nested data structures to find, for instance, the first matching node (e.g. graph) or the root of analyzed structure (e.g. tree). But since it involves recursive calls, remember to put a break condition. An example of such use is presented below:
it("should apply to deep matches") { // Use null instead of Optional for brevity case class Person(name: String, parent: Person = null) val peopleHierarchy = Person("A", Person("B", Person("C", Person("D", Person("E", null) ) ) ) ) def getRootParentName(person: Person): String = { val parentName = person match { case Person(_, parent) if parent != null => { getRootParentName(parent) } case Person(name, parent) if parent == null => s"${name}" case _ => throw new IllegalMonitorStateException("Neither children nor parent was found") } parentName } val parentName = getRootParentName(peopleHierarchy) parentName shouldEqual "E" }
- type patterns - pattern matching can be used to replace isInstanceOf type checks:
it("should apply to type patterns") { case class A() case class B() case class C() def testType(someObject: Any): String = { someObject match { case _: A => "class#A" case _: B => "class#B" case _: C => "class#C" case _ => "unknown class" } } val matchedClassName = testType(B()) matchedClassName shouldEqual "class#B" }
- sequence patterns - pattern matching is able to match against simple types but also against the values of a sequence where its head is separated from the tail by :: sign:
it("should apply to sequence patterns") { val letters = Seq("a", "b", "c", "d") val sequenceHead = letters match { case firstLetter :: _ => firstLetter case _ => "unknown" } sequenceHead shouldEqual "a" }
- exhaustiveness checks - Scala compiler is able to detect the cases not covered by the pattern matching clause. A great example of that is the situation when we match against an optional value and consider only the case of defined value:
def testOptional(someObject: Option[Int]): Int = { someObject match { case Some(nr) => nr } }
Above code produces a compilation warning:Warning:(82, 7) match may not be exhaustive. it would fail on the following input: None someObject match {
- variable bindings - in addition to type tests we can also extract values of matched type (to get more information see the post about Scala extractors):
it("should use variable bindings in matched clause") { case class Address(street: String, city: String, country: String) val testedAddress = Address("street#1", "city#1", "country#1") val city = testedAddress match { case Address(_, cityFromAddress, _) => cityFromAddress case _ => "" } city shouldEqual "city#1" }
Similar case is present in comprehensions, as here for a triple:it("should be used for comprehensions") { val testedAddress = ("street#1", "city#1", "country#1") val (_, city, _) = testedAddress city shouldEqual "city#1" }
- partial functions - pattern matching is also used in shortened partial functions versions:
it("should be used as a partial function") { val letters = Seq("z", "a", "b", "c") val aLetter = letters.collectFirst { case letter if letter == "a" => "A" } aLetter shouldEqual Some("A") }
Risk of overuse
But the risk of so powerful pattern matching is its overuse. It's especially visible in the code of programmers using this mechanic for the first time. In such situation almost all if-else conditions are expressed as pattern matching cases. Is it bad ? Yes and not. For many pattern matching is more readable than if-else statement. For the others its use should be only limited to specific cases (e.g. extracting components from objects) and not to make conditionals checks on the same type values (after all guards contain an if too).
A good point helping to make a decision concerns code complexity. In the compiled bytecode, a simple if-else statement is much shorter than pattern matching even applied on only 2 branches:
def getOneOrAnotherIf(value: Int): Boolean = { if (value%2 == 0) true else false } def getOneOrAnotherPatternMatching(value: Int): Boolean = { value match { case nr if nr%2 == 0 => true case _ => false } }
That in bytecode gives:
public boolean getOneOrAnotherIf(int); Code: 0: iload_1 1: iconst_2 2: irem 3: iconst_0 4: if_icmpne 11 7: iconst_1 8: goto 12 11: iconst_0 12: ireturn public boolean getOneOrAnotherPatternMatching(int); Code: 0: iload_1 1: istore_2 2: iload_2 3: lookupswitch { // 0 default: 12 } 12: iload_2 13: iconst_2 14: irem 15: iconst_0 16: if_icmpne 23 19: iconst_1 20: goto 24 23: iconst_0 24: ireturn
This section is not here to give you the final answer but only to emphasize the risk of pattern matching overuse - especially at the beginning. With the practice we should acquire the reflex about the cases when if-else is more adequate than pattern matching and inversely.
The introduction talked about similarities witch Java's switch case, at least at first approach. But as proven throughout all 3 sections, it's only superficial. Java's switch statement applies on specific values whereas Scala's pattern matching has much more use cases. They all were shown in the second section and among the most important ones we should list 3 kinds of matches: deep, type and sequence ones. Combined together they can help to build really powerful methods extracting some values of matched object.
Consulting

With nearly 16 years of experience, including 8 as data engineer, I offer expert consulting to design and optimize scalable data solutions.
As an O’Reilly author, Data+AI Summit speaker, and blogger, I bring cutting-edge insights to modernize infrastructure, build robust pipelines, and
drive data-driven decision-making. Let's transform your data challenges into opportunities—reach out to elevate your data engineering game today!
👉 contact@waitingforcode.com
đź”— past projects