Scala Traits
A trait is Scala's primary tool for defining reusable behavior. Traits are similar to interfaces in Java, but more powerful — they can contain both abstract methods (declarations without bodies) and concrete methods (with full implementations). A class can mix in multiple traits, which solves the classic limitation of single inheritance.
Defining a Trait
trait Greetable:
def greet(): String // abstract — no body
trait Farewell:
def sayBye(): String = "Goodbye!" // concrete — has a body
Implementing a Trait
class EnglishSpeaker extends Greetable:
def greet(): String = "Hello!"
class SpanishSpeaker extends Greetable:
def greet(): String = "¡Hola!"
val e = new EnglishSpeaker()
val s = new SpanishSpeaker()
println(e.greet()) // Hello!
println(s.greet()) // ¡Hola!
Mixing in Multiple Traits
A class can extend one class and mix in multiple traits using with:
trait Swimmable:
def swim(): String = "I can swim"
trait Flyable:
def fly(): String = "I can fly"
trait Runnable:
def run(): String = "I can run"
class Duck extends Swimmable with Flyable with Runnable:
def describe(): String =
s"${swim()}, ${fly()}, ${run()}"
class Penguin extends Swimmable with Runnable:
override def swim(): String = "I swim very fast"
def describe(): String = s"${swim()}, ${run()}"
val duck = new Duck()
println(duck.describe())
// I can swim, I can fly, I can run
val penguin = new Penguin()
println(penguin.describe())
// I swim very fast, I can run
Trait Mixing Diagram
Swimmable Flyable Runnable
│ │ │
└────────────┴────┬────┘
│
Duck
(mixes in all three)
Traits with Abstract and Concrete Members
trait Logger:
def prefix: String // abstract — subclass must define this
// Concrete methods — use the abstract 'prefix'
def info(msg: String): Unit = println(s"[$prefix INFO] $msg")
def warn(msg: String): Unit = println(s"[$prefix WARN] $msg")
def error(msg: String): Unit = println(s"[$prefix ERROR] $msg")
class AppLogger extends Logger:
val prefix = "MyApp"
class TestLogger extends Logger:
val prefix = "Test"
val app = new AppLogger()
app.info("Server started") // [MyApp INFO] Server started
app.warn("Low memory") // [MyApp WARN] Low memory
app.error("Disk full") // [MyApp ERROR] Disk full
Trait as Interface (Polymorphism)
Use a trait as a type to write code that works with any class implementing that trait:
trait Shape:
def area(): Double
def perimeter(): Double
def describe(): String =
f"Area: ${area()}%.2f, Perimeter: ${perimeter()}%.2f"
class Circle(radius: Double) extends Shape:
def area(): Double = Math.PI * radius * radius
def perimeter(): Double = 2 * Math.PI * radius
class Square(side: Double) extends Shape:
def area(): Double = side * side
def perimeter(): Double = 4 * side
// Works with any Shape
def printShapeInfo(shape: Shape): Unit =
println(shape.describe())
val shapes: List[Shape] = List(new Circle(5), new Square(4))
shapes.foreach(printShapeInfo)
// Area: 78.54, Perimeter: 31.42
// Area: 16.00, Perimeter: 16.00
Trait with State (Fields)
Traits can define fields — both abstract and concrete:
trait Timestamped:
val createdAt: Long = System.currentTimeMillis()
def ageInSeconds: Long =
(System.currentTimeMillis() - createdAt) / 1000
class Message(val text: String) extends Timestamped
val msg = new Message("Hello")
Thread.sleep(2000)
println(s"Message age: ${msg.ageInSeconds} seconds") // ~2
Overriding Trait Methods
trait Printer:
def print(value: String): Unit = println(value)
class FancyPrinter extends Printer:
override def print(value: String): Unit =
println(s"*** $value ***")
class QuietPrinter extends Printer:
override def print(value: String): Unit =
() // do nothing (silent)
val normal = new Printer {}
val fancy = new FancyPrinter()
val quiet = new QuietPrinter()
normal.print("Hello") // Hello
fancy.print("Hello") // *** Hello ***
quiet.print("Hello") // (nothing)
Trait Linearization (Method Resolution Order)
When multiple traits define a method with the same name, Scala uses linearization — a deterministic order — to decide which implementation runs. The rule is: classes and traits are ordered right-to-left, with the class itself first.
trait A:
def hello: String = "A"
trait B extends A:
override def hello: String = "B"
trait C extends A:
override def hello: String = "C"
class D extends A with B with C
val d = new D()
println(d.hello) // C (C is the rightmost trait)
Linearization order for D: D → C → B → A
First override found wins: C.hello = "C"
Calling super in Traits
trait Validator:
def validate(input: String): Boolean = input.nonEmpty
trait LengthValidator extends Validator:
override def validate(input: String): Boolean =
super.validate(input) && input.length >= 3
trait LetterValidator extends Validator:
override def validate(input: String): Boolean =
super.validate(input) && input.forall(_.isLetter)
class StrictValidator extends LengthValidator with LetterValidator
val v = new StrictValidator()
println(v.validate("Hi")) // false (length < 3)
println(v.validate("Hello")) // true
println(v.validate("Hi2")) // false (contains digit)
Each super.validate call passes the check up the chain, building a pipeline of validations. This is the Stackable Trait Pattern — a powerful way to compose behavior.
Sealed Traits
A sealed trait restricts which classes can extend it — only classes in the same file. This lets the compiler verify exhaustive pattern matching:
sealed trait PaymentStatus
case object Pending extends PaymentStatus
case object Success extends PaymentStatus
case object Failed extends PaymentStatus
case object Refunded extends PaymentStatus
def handle(status: PaymentStatus): String =
status match
case Pending => "Processing your payment..."
case Success => "Payment successful!"
case Failed => "Payment failed. Please try again."
case Refunded => "Amount refunded."
// If any case is missing, compiler warns you
Traits vs Abstract Classes
Feature Trait Abstract Class
────────────────────── ────────────── ──────────────────
Multiple inheritance? Yes (many traits) No (one class only)
Constructor params? No (Scala 2) Yes
Yes (Scala 3)
Contains state? Yes Yes
Stackable? Yes No
Preferred for... Behavior mixins Base with constructor
Use traits when you want to define reusable behavior that multiple unrelated classes can adopt. Use abstract classes when you need a common base with constructor parameters and do not need multiple inheritance.
