When we come to Scala and see the sealed keyword, we often wonder "why". After all, having all subclasses defined in one or more files shouldn't be a big deal. For us, programmers, it's not but for the compiler, it has an importance. In this post, I will try to show the sealed class use cases.
Data Engineering Design Patterns

Looking for a book that defines and solves most common data engineering problems? I'm currently writing
one on that topic and the first chapters are already available in 👉
Early Release on the O'Reilly platform
I also help solve your data engineering problems 👉 contact@waitingforcode.com 📩
In the first section I will shortly explain the sealed classes in order to leave much more place to the use cases. I will cover each of them in a separate section.
Definition
The sealed is a Scala keyword used to control the places where given trait or class can be extended. More concretely, the subclasses and the implementations can be defined only in the same source file as the sealed trait or class.
Despite their simple definition, sealed types are used for some specific goals. And I will list them with examples in the next sections. Obviously, having all the classes in a single file can negatively impact the code readability. And that's the reason why it's good to see first whether the advantages of sealing don't break the balance with the readability.
Type safety
The first advantage of the sealed types is related to the type safety. If you carefully analyze all compilation warnings, you've probably already seen warning: match may not be exhaustive. Just to recall, they happen when your match cases don't cover all possibilities.
But what is the link with the sealed types? The mystery comes from the definition of the constraint. Since all the types must be defined in a single file, the compiler automatically knows all possibilities and can use them in every pattern matching executed against the sealed type. Let's take an example:
package sealedtypes sealed trait Letter {} class A extends Letter class B extends Letter class C extends Letter
If we try to extend Letter trait in a different file, the compiler will end up with "illegal inheritance from sealed trait Letter" message. And if we write the code like this one:
val letter: Letter = new B() letter match { case _: sealedtypes.A => "a" }
The compiler will print:
Warning:(7, 3) match may not be exhaustive. It would fail on the following input: C() letter match {
You can later remove the sealed keyword and recompile the code in order to see that the warning will disappear.
Algebraic Data Types
In simple terms, the Algebraic Data Types (I will refer to them with the ADT abbreviation) symbolize the data model with a closed set of possible values sharing the same interface. Probably the 2 most popular ADTs you'll meet are product and sum types. The best example illustrating the former one is a class composed of other types. On the other side, the sum type doesn't combine different types into one common type. Rather than that, it enumerates the list of the same types in a container, exactly as Java's enum does.
Since all the implementations of sealed must be defined in the same file as the declaration, they become a good candidate to be implemented as the sum type:
sealed trait Letter object Letter { class A() extends Letter {} class B() extends Letter {} class C() extends Letter {} }
Enum alternative
Enum drawbacks was already addressed in the Enums in Scala post. Just to recall quickly, one of the negative points was the inability to extend the enums behavior. Thanks to the sealed types, as I partially illustrated this in the previous section, it's possible to use more dynamic enumerations:
sealed trait Letter { def letter: String def isVowel: Boolean } object Letter { case object A extends Letter { override def letter: String = "A" override def isVowel: Boolean = true } case object B extends Letter { override def letter: String = "B" override def isVowel: Boolean = false } case object C extends Letter { override def letter: String = "C" override def isVowel: Boolean = false } }
Despite their apparent simplicity, it's often hard to find the use cases of the sealed types. I hope I illustrated some of them in this post. From the examples, you can deduce that you can freely use sealed types when you're dealing with a finite set of possibilities. It's true and this use case is a good alternative to the classical Scala enums. However, you should always be aware of the eventual impact on the code readability.
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