val, var, and lazy val: Understanding Immutability

Introduction

One of the most important concepts to understand in Scala is the distinction between mutable and immutable data. Unlike many programming languages where variables are mutable by default, Scala encourages immutability as the preferred approach.

In this lesson, we'll explore the three ways to declare values in Scala: val, var, and lazy val. Understanding when and how to use each one is crucial for writing idiomatic Scala code.

val: Immutable Values

The val keyword creates an immutable value – once assigned, it cannot be changed. Think of it as a constant.

val name = "Alice"
val age = 25
val pi = 3.14159

// This would cause a compilation error:
// name = "Bob"  // Error: reassignment to val

Type Inference with val

Scala's type inference is powerful. You can explicitly specify types, but it's often unnecessary:

// Explicit type annotation
val message: String = "Hello, World!"
val count: Int = 42
val price: Double = 19.99

// Type inference (preferred when obvious)
val message = "Hello, World!"  // String inferred
val count = 42                 // Int inferred
val price = 19.99             // Double inferred

Immutable Collections

Even collections can be immutable with val:

val numbers = List(1, 2, 3, 4, 5)
val fruits = Set("apple", "banana", "orange")
val scores = Map("Alice" -> 95, "Bob" -> 87)

// You cannot reassign the collection itself:
// numbers = List(6, 7, 8)  // Error!

// But you can create new collections:
val moreNumbers = numbers :+ 6  // Creates new list [1, 2, 3, 4, 5, 6]
val moreFruits = fruits + "grape"  // Creates new set

Benefits of Immutability

  1. Thread Safety: Immutable values can be safely shared between threads
  2. Easier Reasoning: You know the value won't change unexpectedly
  3. Functional Programming: Enables powerful functional programming techniques
  4. Debugging: Fewer surprises when debugging code

var: Mutable Variables

The var keyword creates a mutable variable that can be reassigned:

var counter = 0
var name = "Initial"
var isActive = true

// These are all valid:
counter = counter + 1  // or counter += 1
name = "Updated"
isActive = false

println(s"Counter: $counter, Name: $name, Active: $isActive")
// Output: Counter: 1, Name: Updated, Active: false

When to Use var

Use var sparingly and only when you truly need mutability:

// Example: Accumulating a result in a loop
var sum = 0
for (i <- 1 to 10) {
  sum += i
}
println(s"Sum: $sum")  // Sum: 55

// Example: Reading user input until a condition is met
import scala.io.StdIn

var input = ""
while (input != "quit") {
  print("Enter command (or 'quit' to exit): ")
  input = StdIn.readLine()
  if (input != "quit") {
    println(s"You entered: $input")
  }
}

Functional Alternatives to var

Often, you can replace var with functional approaches:

// Instead of using var in a loop:
var sum = 0
for (i <- 1 to 10) {
  sum += i
}

// Use functional methods:
val sum = (1 to 10).sum

// Instead of building a list with var:
var result = List.empty[Int]
for (i <- 1 to 5) {
  result = result :+ (i * 2)
}

// Use map:
val result = (1 to 5).map(_ * 2).toList

lazy val: Deferred Evaluation

The lazy val keyword creates a value that is computed only when first accessed. This is called "lazy evaluation" or "call-by-need."

lazy val expensiveComputation = {
  println("Computing...")
  Thread.sleep(1000)  // Simulate expensive operation
  42
}

println("Before accessing lazy val")
println(s"Value: $expensiveComputation")  // "Computing..." printed here
println(s"Value again: $expensiveComputation")  // No "Computing..." this time