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.

Leave a Comment

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