100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Programming

Loops and For-Comprehensions

Explore Scala's imperative loop constructs alongside the functional for-comprehension, including guards, nested generators, and yield-based transformations.

Functions & Control FlowIntermediate9 min readJul 10, 2026
Analogies

Imperative Loops in Scala

Scala supports the familiar while and do-while loops inherited from imperative languages, but they are used sparingly in idiomatic Scala because they rely on mutable state and produce Unit, giving them no return value to compose with the rest of an expression-oriented language. A while (condition) { body } loop repeatedly executes its body only while the condition holds, checked before each iteration, whereas do { body } while (condition) guarantees the body runs at least once before the condition is checked - both remain useful for tight, performance-sensitive loops or when interfacing with mutable, stateful APIs.

🏏

Cricket analogy: A bowler running through a fixed pre-delivery routine (mark the run-up, check the field, bowl) every single ball regardless of the match situation is like a do-while loop - the routine runs at least once before any condition about the next ball is even considered.

for Loops and Ranges

The most common way to iterate a fixed number of times is for (i <- 1 to 10), which iterates over a Range object built by the to (inclusive) or until (exclusive) methods on Int; by default a bare for loop without yield is executed purely for its side effects, such as printing, and itself evaluates to Unit. Ranges can also specify a step, as in 1 to 10 by 2, and iterating in reverse is done with 10 to 1 by -1, since a naive ascending range with a negative step produces an empty sequence.

🏏

Cricket analogy: An over consists of exactly 1 to 6 deliveries - an inclusive range where both endpoints count, just as Scala's to method includes both boundary values, unlike until which excludes the upper bound.

scala
for (i <- 1 to 5) println(s"Iteration $i")

for (i <- 10 to 1 by -3) println(i)  // 10, 7, 4, 1

for Comprehensions with yield

Adding yield transforms a for loop from a side-effecting statement into a for comprehension that produces a new collection: for (x <- List(1, 2, 3)) yield x * 2 returns List(2, 4, 6). Under the hood, this is pure syntactic sugar that the compiler desugars into calls to map, flatMap, and withFilter on whatever collection type is being iterated - a single generator with yield becomes map, multiple generators become nested flatMap calls, and any if guard becomes a withFilter call, which is why any type that implements those methods (like Option or Future) can be used in a for comprehension, not just collections.

🏏

Cricket analogy: A run-chase calculator that takes each ball faced and 'yields' a running required-run-rate value for every delivery, rather than just printing scores, is like Scala's for-yield transforming each element into a new value collected into a result.

scala
val pairs = for {
  x <- List(1, 2, 3)
  y <- List("a", "b")
} yield (x, y)
// List((1,a), (1,b), (2,a), (2,b), (3,a), (3,b))

Guards and Nested Generators

Multiple generators inside a single for block, separated by newlines or semicolons within braces, produce a cross-product-style nested iteration, and adding an if guard such as if x % 2 == 0 filters out combinations before the yield is evaluated, exactly as if .withFilter(x => x % 2 == 0) had been called explicitly. Because each generator can also introduce an intermediate val binding (e.g., total = x + y), for comprehensions become a concise way to express filtered, multi-step transformations that would otherwise require deeply nested flatMap/map/filter chains.

🏏

Cricket analogy: A fixture scheduler generating every possible team pairing across two divisions, then filtering out pairs where both teams are from the same city, is like Scala's nested for-generators with an if guard acting as withFilter to drop unwanted combinations.

For comprehensions work with any type that defines map and flatMap (and optionally withFilter) - this includes Option, Try, and Future, which is why you'll often see for { a <- optionA; b <- optionB } yield a + b used to chain optional or asynchronous computations cleanly.

A for loop without yield returns Unit and exists purely for its side effects - using it when you actually meant to build a new collection is a common beginner mistake; forgetting yield silently discards all the values instead of raising an error.

  • while and do-while are imperative loops that mutate state and evaluate to Unit.
  • for loops without yield run only for side effects and also evaluate to Unit.
  • to includes the upper bound; until excludes it; by sets a custom step, including negative for descending ranges.
  • Adding yield turns a for loop into a for comprehension that builds a new collection.
  • For comprehensions desugar to map, flatMap, and withFilter, so any type implementing those (Option, Future) can be used.
  • Guards (if) inside a for comprehension filter elements before yield is applied, equivalent to calling withFilter.
  • Multiple generators create nested iteration equivalent to nested flatMap/map calls.

Practice what you learned

Was this page helpful?

Topics covered

#Programming#ScalaStudyNotes#LoopsAndForComprehensions#Loops#Comprehensions#Imperative#Scala#StudyNotes#SkillVeris#ExamPrep