Union and Intersection Types
New type features.
Union and Intersection Types is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
New Type Features
Scala 3 adds union types (A | B) and intersection types (A & B) to the type system. They let you describe values more precisely without inheritance hierarchies.
object Main:
def main(args: Array[String]): Unit =
println("Union: A | B, Intersection: A & B")Union Types Basics
A union type A | B represents a value that is either an A or a B. It is a set-theoretic OR over types.
object Main:
def show(x: Int | String): String = x match
case i: Int => s"int $i"
case s: String => s"str $s"
def main(args: Array[String]): Unit =
println(show(42))
println(show("hi"))Unions Need No Common Supertype
Unlike Scala 2 Either, union types do not require wrapping. Any two types can be unioned without sharing a parent class.
object Main:
def parse(input: Int | Boolean): String =
input match
case n: Int => s"number $n"
case b: Boolean => s"flag $b"
def main(args: Array[String]): Unit =
println(parse(7))
println(parse(true))Unions for Error Modeling
Union types are handy for lightweight error results without a wrapper type. Combined with enums or singletons, they read cleanly.
case object NotFound
object Main:
def lookup(id: Int): String | NotFound.type =
if id == 1 then "Alice" else NotFound
def main(args: Array[String]): Unit =
println(lookup(1))
println(lookup(2))Intersection Types Basics
An intersection type A & B represents a value that is both A and B. It is a set-theoretic AND, often used to combine traits.
trait Named:
def name: String
trait Aged:
def age: Int
object Main:
def describe(p: Named & Aged): String =
s"${p.name} is ${p.age}"
def main(args: Array[String]): Unit =
val person = new Named with Aged:
def name = "Bob"
def age = 40
println(describe(person))Intersection Is Commutative
Order does not matter for intersection types: A & B is the same type as B & A. This differs from Scala 2 compound types A with B, which were order-sensitive in some member resolutions.
trait Readable:
def read: String
trait Writable:
def write(s: String): Unit
object Main:
def use(r: Readable & Writable): Unit =
r.write("x")
println(r.read)
def main(args: Array[String]): Unit =
val rw = new Readable with Writable:
def read = "data"
def write(s: String) = println(s"wrote $s")
use(rw)Combining Multiple Traits
You can intersect more than two types. The result requires all listed capabilities.
trait A: def a: Int
trait B: def b: Int
trait C: def c: Int
object Main:
def sum(x: A & B & C): Int = x.a + x.b + x.c
def main(args: Array[String]): Unit =
val v = new A with B with C:
def a = 1
def b = 2
def c = 3
println(sum(v))Type Aliases for Unions
Long union types can be named with a type alias to keep signatures readable.
type Json = Int | String | Boolean
object Main:
def render(j: Json): String = j match
case i: Int => i.toString
case s: String => s"\"$s\""
case b: Boolean => b.toString
def main(args: Array[String]): Unit =
println(render("hi"))
println(render(true))Exhaustive Matching on Unions
The compiler knows the members of a union, so matching can be checked for completeness. Handling every alternative makes the match total.
type Shape = "circle" | "square"
object Main:
def sides(s: Shape): Int = s match
case "circle" => 0
case "square" => 4
def main(args: Array[String]): Unit =
println(sides("square"))Widening of Unions
When you assign a union value to an inferred variable, Scala keeps the union type. Be aware that some operations widen to the least upper bound, so annotate when you want to preserve the union.
object Main:
def pick(flag: Boolean): Int | String =
if flag then 1 else "one"
def main(args: Array[String]): Unit =
val x: Int | String = pick(false)
println(x)When to Use Each
Choose the right tool.
- Union: a value is one of several types (OR).
- Intersection: a value satisfies several traits at once (AND).
- Unions avoid wrapper types; intersections avoid deep inheritance.
trait Logger: def log(s: String): Unit
trait Clock: def now: Long
object Main:
def run(env: Logger & Clock): Unit =
env.log(s"time=${env.now}")
def main(args: Array[String]): Unit =
val env = new Logger with Clock:
def log(s: String) = println(s)
def now = 100L
run(env)Quick Check
Test your understanding of union and intersection types.
Recap
You learned Scala 3 union and intersection types.
A | B: value is A or B, no wrapper or common parent needed.A & B: value is both A and B; commutative.- Pattern match to narrow unions; combine traits with intersections.
- Use
typealiases to name long unions.
object Main:
def length(x: String | List[Int]): Int = x match
case s: String => s.length
case l: List[Int] => l.length
def main(args: Array[String]): Unit =
println(length("hello"))
println(length(List(1, 2, 3)))Frequently asked questions
Is the “Union and Intersection Types” lesson free?
Yes — the full text of “Union and Intersection Types” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO.
What will I learn in “Union and Intersection Types”?
New type features. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Scala for Backend Engineering & Functional Programming?
No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Union and Intersection Types” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?
Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Significant Indentation
- Enums in Scala 3
- Opaque Types
- Union and Intersection Types