Scala Inheritance
Inheritance lets a class reuse and extend the behavior of another class. The child class (subclass) inherits all non-private members of the parent class (superclass) and can add new members or override existing ones. Scala uses the extends keyword.
Basic Inheritance
class Animal(val name: String):
def breathe(): Unit = println(s"$name breathes air")
def describe(): String = s"I am $name"
class Dog(name: String, val breed: String) extends Animal(name):
def bark(): Unit = println(s"$name says: Woof!")
override def describe(): String = s"I am $name, a $breed dog"
class Cat(name: String) extends Animal(name):
def meow(): Unit = println(s"$name says: Meow!")
val dog = Dog("Bruno", "Labrador")
val cat = Cat("Luna")
dog.breathe() // Bruno breathes air (inherited)
dog.bark() // Bruno says: Woof! (own method)
println(dog.describe()) // I am Bruno, a Labrador dog (overridden)
cat.breathe() // Luna breathes air
cat.meow() // Luna says: Meow!
Animal
/ \
Dog Cat
breathe() breathe() ← inherited
bark() meow() ← own
describe() describe() ← Dog overrides, Cat inherits
The override Keyword
Scala requires the override keyword when redefining a method from the parent. This is a safety feature — if you misspell the method name, the compiler catches it instead of silently creating a new method:
class Shape(val color: String):
def area(): Double = 0.0
def describe(): String = s"A $color shape"
class Rectangle(color: String, val width: Double, val height: Double)
extends Shape(color):
override def area(): Double = width * height
override def describe(): String =
s"A $color rectangle ${width}×${height}, area=${area()}"
val r = Rectangle("red", 4.0, 5.0)
println(r.describe()) // A red rectangle 4.0×5.0, area=20.0
println(r.area()) // 20.0
Calling the Parent with super
class Vehicle(val brand: String):
def info(): String = s"Brand: $brand"
class Car(brand: String, val model: String) extends Vehicle(brand):
override def info(): String = super.info() + s", Model: $model"
class ElectricCar(brand: String, model: String, val range: Int)
extends Car(brand, model):
override def info(): String = super.info() + s", Range: ${range}km"
val tesla = ElectricCar("Tesla", "Model S", 600)
println(tesla.info())
// Brand: Tesla, Model S, Range: 600km
ElectricCar.info()
calls super.info() → Car.info()
calls super.info() → Vehicle.info()
"Brand: Tesla"
+ ", Model: Model S"
+ ", Range: 600km"
= "Brand: Tesla, Model: Model S, Range: 600km"
Polymorphism
A variable of the parent type can hold any subtype instance. When you call a method on it, Scala runs the actual subtype's version at runtime:
class Animal(val name: String):
def sound(): String = "..."
class Dog(name: String) extends Animal(name):
override def sound(): String = "Woof"
class Cat(name: String) extends Animal(name):
override def sound(): String = "Meow"
class Cow(name: String) extends Animal(name):
override def sound(): String = "Moo"
val animals: List[Animal] = List(Dog("Rex"), Cat("Luna"), Cow("Bessie"))
animals.foreach(a => println(s"${a.name}: ${a.sound()}"))
// Rex: Woof
// Luna: Meow
// Bessie: Moo
final — Preventing Override
class Base:
final def safeMethod(): String = "Cannot be overridden"
def normalMethod(): String = "Can be overridden"
class Child extends Base:
// override def safeMethod() = "..." // Error: method is final
override def normalMethod(): String = "Overridden in Child"
Use final on methods whose behavior must never change in subclasses — for example, security-critical operations or algorithm steps that subclasses must not alter.
Inheritance with Abstract Members
abstract class Report(val title: String):
def generate(): String // abstract — no body
def header(): String = s"=== $title ===" // concrete
def footer(): String = "=== End of Report ===" // concrete
def fullReport(): String =
s"${header()}\n${generate()}\n${footer()}"
class SalesReport(title: String, val total: Double)
extends Report(title):
override def generate(): String = f"Total Sales: ₹$total%,.2f"
class InventoryReport(title: String, val items: Int)
extends Report(title):
override def generate(): String = s"Items in Stock: $items"
val sales = SalesReport("Q4 Sales", 1250000.0)
println(sales.fullReport())
// === Q4 Sales ===
// Total Sales: ₹12,50,000.00
// === End of Report ===
Constructor Parameters in Inheritance
class Person(val name: String, val age: Int)
// Subclass must pass parent constructor arguments
class Employee(name: String, age: Int, val company: String)
extends Person(name, age):
def intro(): String = s"$name, $age, works at $company"
class Manager(name: String, age: Int, company: String, val team: Int)
extends Employee(name, age, company):
override def intro(): String =
super.intro() + s", manages $team people"
val mgr = Manager("Sunita", 42, "TechCorp", 12)
println(mgr.intro())
// Sunita, 42, works at TechCorp, manages 12 people
println(mgr.name) // Sunita (from Person)
println(mgr.company) // TechCorp (from Employee)
isInstanceOf and Type Checking
val a: Animal = Dog("Max")
println(a.isInstanceOf[Dog]) // true
println(a.isInstanceOf[Cat]) // false
println(a.isInstanceOf[Animal]) // true
// Safe cast
val dog = a.asInstanceOf[Dog]
dog.bark() // Max says: Woof!
// Safer: use pattern matching
a match
case d: Dog => println(s"${d.name} is a dog")
case c: Cat => println(s"${c.name} is a cat")
case _ => println("Unknown animal")
Inheritance vs Composition
Inheritance (is-a) Composition (has-a)
──────────────────────────── ───────────────────────────────
Dog extends Animal Car has an Engine
Dog IS an Animal Car HAS an Engine
Use when: Use when:
- Clear is-a relationship - Has-a relationship
- Sharing base behavior - Behavior can vary independently
- Polymorphic dispatch needed - Avoid tight coupling
Prefer composition over deep inheritance hierarchies. More than 2–3 levels of inheritance typically signals a design problem. Traits (mixins) offer a flatter alternative for sharing behavior across unrelated classes.
