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.

Leave a Comment

Your email address will not be published. Required fields are marked *