Screen 1 of 5

val vs var vs lazy val

Imagine three types of shipping labels. This is Scala's variable system — one of the most common interview questions.

KeywordMutable?When Evaluated?Metaphor
val❌ ImmutableImmediately📦 Sealed package — once packed, can't reopen
var✅ MutableImmediately📥 Open box — swap contents anytime
lazy val❌ ImmutableOn first access📨 Sealed package that's only built when someone asks for it
val name = "Alice" // name = "Bob" // ❌ ERROR! var score = 10 score = 20 // ✅ OK lazy val data = { println("Loading...") 42 } // "Loading..." only prints // when you first use 'data'
Create a permanent label "name" = Alice. Can never change.
If you try to change it, Scala refuses to compile.
Create a changeable score starting at 10.
Update score to 20 — totally fine with var.
Create a lazy value. The code block doesn't run yet!
Only when someone reads "data" does it execute and cache the result (42).
💡 When to use lazy val?
Use it for expensive computations (database connections, file loading) that you might not always need. It saves resources by computing only on demand.
Screen 2 of 5

Case Classes — Data in a Gift Box

Think of a case class like a pre-printed form. You define the fields once, and Scala fills in all the boring methods for you.

case class Person(name: String, age: Int) val p1 = Person("John", 30) val p2 = Person("John", 30) println(p1) // Person(John,30) println(p1 == p2) // true val p3 = p1.copy(age = 31) // Person(John,31)
Define a Person form with two fields: name and age.
Create John, age 30. No "new" keyword needed!
Create another identical John.
Printing shows nice output (auto toString).
Comparing works by values, not memory (auto equals).
Copy John but change age to 31. Original is unchanged (immutable!).

🎁 What you get free

toString, equals, hashCode, copy method, apply (no "new" needed), unapply (pattern matching support)

🎯 Interview tip

"When would you use a case class?" → For immutable data containers. DTOs, API responses, config objects, events.

Screen 3 of 5

Companion Objects — The Class's Best Friend

Imagine a phone and its charging dock. They share the same name, live together, and the dock provides utilities the phone can't do alone.

class Person(val name: String, val age: Int) object Person { def apply(name: String, age: Int): Person = new Person(name, age) def fromCSV(csv: String): Person = { val parts = csv.split(",") new Person(parts(0), parts(1).toInt) } } val p = Person("John", 25) val p2 = Person.fromCSV("Jane,28")
The class: a blueprint for creating Person instances.
The companion object: shares the same name, lives in the same file.
The apply method is magic — lets you write Person("John", 25) without "new".
Factory method: create a Person from a CSV string like "Jane,28".
Both ways of creating a Person work seamlessly.
💡 Why companions exist
Scala has no static keyword (unlike Java). Companion objects are where you put "static-like" methods — factory methods, constants, utility functions that belong to the concept but not to any single instance.
Screen 4 of 5

Quick Hits: Strings & Tail Recursion

Two more topics interviewers sneak in. Let's nail them.

📝 String (Immutable)

Every modification creates a new string. Like writing on a whiteboard, erasing, and writing on a fresh one each time. Fine for small changes.

⚡ StringBuilder (Mutable)

Modifies in-place. Like having a notebook where you keep adding pages. Much faster for lots of text manipulation.

import scala.annotation.tailrec @tailrec def factorial(n: Int, acc: Int = 1): Int = { if (n <= 0) acc else factorial(n - 1, n * acc) }
@tailrec = "Dear compiler, please verify this is tail-recursive."
Tail-recursive means: the recursive call is the LAST thing in the function.
The compiler converts it into a loop under the hood — no stack overflow!
The "acc" (accumulator) carries the running result forward.
🎯 Interview Favorite
"What does @tailrec do?" → It tells the compiler to check that a function is tail-recursive, so it can be optimized into a loop, preventing stack overflow errors on large inputs.
Screen 5 of 5

Test Yourself 🧠

Q1: You need to store a database connection that's expensive to create and might not be needed. Which do you use?
val connection = createDBConnection()
var connection = createDBConnection()
lazy val connection = createDBConnection()
def connection = createDBConnection()
Q2: What does a case class give you for FREE that a regular class doesn't?
Mutable fields by default
Auto-generated toString, equals, hashCode, copy, and apply methods
Faster runtime performance
Automatic database persistence
Q3: Why does Scala use companion objects instead of the "static" keyword?
Because static is reserved for other use
Because static is slower
Because Scala is purely object-oriented — even "static" things are objects with full OOP capabilities
Because companion objects use less memory