Laziness Explained
Compute values only when needed.
Laziness Explained is a free Scala for Backend Engineering & Functional Programming lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Scala for Backend Engineering & Functional Programming learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
What Is Laziness?
In Scala, most values are strict: they are computed the moment you define them. Laziness flips this. A lazy value is only computed when it is first needed, never before.
This matters when computations are expensive, may fail, or might never be used at all. Why pay for work nobody asks for?
Strict val
A normal val evaluates its right-hand side immediately, even if you never read it again.
Run this and notice that "computing" prints before "after". The work happens at definition time.
object Demo extends App {
val x = { println("computing"); 42 }
println("after")
println(x)
}lazy val
Prefix a val with lazy and evaluation is deferred until first access.
Here "after" prints first, then "computing" only when x is read. The result is also cached: the body runs at most once.
object Demo extends App {
lazy val x = { println("computing"); 42 }
println("after")
println(x)
println(x)
}By-name Parameters
A parameter declared as => A is passed by name: the argument is not evaluated until used inside the method.
This is the building block of laziness. LazyList uses by-name parameters so a tail is never computed until requested.
def maybe(cond: Boolean, value: => Int): Int =
if (cond) value else 0
val r = maybe(false, { println("side"); 99 })
// "side" never prints; value was unusedMeet LazyList
LazyList is Scala's lazy sequence (it replaced the older Stream in 2.13+). Its head is evaluated eagerly, but its tail is lazy.
Elements are produced on demand and memoized once computed, so you never recompute the same cell twice.
val ll = LazyList(1, 2, 3)
println(ll)
// prints LazyList(<not computed>) until forcedForcing Evaluation
A LazyList stays unevaluated until you force it. Calling toList, foreach, or printing each element forces the cells.
The REPL shows <not computed> for the lazy tail. Forcing reveals the real values.
object Demo extends App {
val ll = LazyList(1, 2, 3)
println(ll.toList)
}The #:: Operator
You prepend to a LazyList with #::. Unlike list's ::, the right operand of #:: is by-name, so the tail is not built until needed.
This lets you describe sequences whose rest is computed lazily.
val ll = 1 #:: 2 #:: 3 #:: LazyList.empty
println(ll.head)Memoization
Once a LazyList cell is computed, its value is stored. Reading it again is free and the producing expression does not run twice.
Run this: "calc" prints only on first access of each forced element, not on repeated reads.
object Demo extends App {
val ll = LazyList(1, 2).map { n =>
println("calc " + n); n * 10
}
println(ll.head)
println(ll.head)
}Lazy Tail in Action
Because the tail is lazy, you can map over a LazyList and only the elements you touch get computed.
Here "map" prints just once even though the list has three elements, because only the head is forced.
object Demo extends App {
val r = LazyList(1, 2, 3).map { n =>
println("map " + n); n + 1
}
println(r.head)
}Why Laziness Helps
Laziness enables three things: avoiding unused work, building infinite sequences, and decoupling how you describe a computation from how much of it runs.
You write the full pipeline once; the consumer decides how many elements to pull.
A Caution
Laziness has costs. A LazyList memoizes, so holding a reference to its head keeps every forced element in memory. For huge or infinite data, avoid retaining the head.
Also, side effects in lazy code run at unexpected times, which can confuse debugging.
Quick Check
Test your understanding of lazy evaluation.
Recap
Strict evaluation runs work immediately; lazy evaluation defers it until needed. lazy val and by-name parameters (=> A) are the primitives.
LazyList builds on them: eager head, lazy memoized tail, forced via toList or element access. Next, we build LazyLists by hand.
Frequently asked questions
Is the “Laziness Explained” lesson free?
Yes — the full text of “Laziness Explained” is free to read here on the web, and the Scala for Backend Engineering & Functional Programming course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Scala for Backend Engineering & Functional Programming course, upgrade to CoddyKit PRO.
What will I learn in “Laziness Explained”?
Compute values only when needed. You practise Scala for Backend Engineering & Functional Programming with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Scala for Backend Engineering & Functional Programming?
No prior experience is required. Scala for Backend Engineering & Functional Programming on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Laziness Explained” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Scala for Backend Engineering & Functional Programming lesson?
Yes. Every Scala for Backend Engineering & Functional Programming lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Laziness Explained
- Building a LazyList
- Infinite Streams
- Taking and Filtering Lazily