Scala Immutability
Immutability means creating values that never change after assignment. In Scala, val bindings and case classes are immutable by default. When data cannot change, programs become easier to understand, test, and run concurrently. You never have to ask "what is this value right now?" — it is always the same value it started with.
The Problem with Mutable State
// Mutable — hard to reason about
var balance = 1000
def applyInterest() = balance = balance * 1.05
def applyFee() = balance = balance - 50
// What is balance here? Depends on call order, threading, etc.
applyInterest()
applyFee()
println(balance) // 1000 * 1.05 - 50 = 1000? or 950 * 1.05?
Immutable Alternative
// Immutable — always predictable
def applyInterest(b: Double): Double = b * 1.05
def applyFee(b: Double): Double = b - 50
val start = 1000.0
val afterInt = applyInterest(start) // 1050.0
val final1 = applyFee(afterInt) // 1000.0
val final2 = applyFee(applyInterest(start)) // always 1000.0
// Original start is unchanged — always 1000.0
val vs var
val name = "Scala" // immutable — cannot reassign
var count = 0 // mutable — can reassign
name = "Java" // Error: reassignment to val
count = 1 // fine
Immutable Collections
All default Scala collections — List, Map, Set, Vector — are immutable. Every "modification" creates a new collection and leaves the original intact:
val original = List(1, 2, 3)
val extended = original :+ 4 // new list; original unchanged
val doubled = original.map(_ * 2)
println(original) // List(1, 2, 3)
println(extended) // List(1, 2, 3, 4)
println(doubled) // List(2, 4, 6)
original: [1] → [2] → [3] → Nil (never changes)
extended: [1] → [2] → [3] → [4] → Nil (new list, shares [1],[2],[3])
Immutable Data Classes (Case Classes)
case class Address(street: String, city: String, pin: String)
case class Person(name: String, age: Int, address: Address)
val priya = Person("Priya", 28, Address("MG Road", "Bangalore", "560001"))
// Cannot modify — use copy to create a new version
val olderPriya = priya.copy(age = 29)
val movedPriya = priya.copy(address = priya.address.copy(city = "Pune", pin = "411001"))
println(priya.age) // 28 (original unchanged)
println(olderPriya.age) // 29
println(movedPriya.address.city) // Pune
Structural Sharing
Scala's immutable data structures are designed efficiently. When you create a "modified" version, unchanged parts are shared between old and new structures — not copied:
List(1, 2, 3) → [1] ─→ [2] ─→ [3] ─→ Nil
│
0 :: List(1,2,3) → [0] ─┘
(shares the rest — no copying)
Immutability in Concurrent Code
// Mutable shared state — dangerous in multiple threads
var sharedCounter = 0
// Thread A: sharedCounter += 1
// Thread B: sharedCounter += 1
// Result: could be 1 instead of 2 (race condition!)
// Immutable — safe to share across threads
val sharedConfig = Map("timeout" -> 30, "retries" -> 3)
// Any thread can read this — no locks needed
Practical: Building State Through Transformation
case class Cart(items: List[String], total: Double):
def addItem(item: String, price: Double): Cart =
copy(items = items :+ item, total = total + price)
def removeItem(item: String, price: Double): Cart =
copy(items = items.filterNot(_ == item), total = total - price)
def applyDiscount(percent: Double): Cart =
copy(total = total * (1 - percent / 100))
val empty = Cart(List(), 0.0)
val cart1 = empty.addItem("Keyboard", 1500.0)
val cart2 = cart1.addItem("Mouse", 800.0)
val cart3 = cart2.addItem("Monitor", 12000.0)
val cart4 = cart3.removeItem("Mouse", 800.0)
val cart5 = cart4.applyDiscount(10)
println(cart5.items) // List(Keyboard, Monitor)
println(cart5.total) // 12150.0 (13500 - 10%)
println(cart3.total) // 14300.0 (original cart3 unchanged)
When to Allow Mutation
Prefer immutability (use val) for: Allow mutation (use var) for:
────────────────────────────────── ─────────────────────────────
Business domain objects Performance-critical loops
Shared data structures Internal algorithm state
API results and responses External I/O buffers
Configuration Game loop variables
Data flowing through pipelines Builder patterns (temporarily)
Scala does not ban mutation — it just defaults to immutability and makes mutation explicit with var. This design pushes you toward safer choices while keeping an escape hatch when performance genuinely demands it.
