Scala For Comprehensions
A for comprehension looks like a for loop, but instead of only running side effects, it produces a new collection. Add the yield keyword and every iteration generates a value — all these values are collected into a new sequence. For comprehensions are Scala's elegant way to map, filter, and combine collections without explicit method calls.
For with yield
val numbers = List(1, 2, 3, 4, 5)
val doubled = for n <- numbers yield n * 2
println(doubled) // List(2, 4, 6, 8, 10)
for n <- List(1, 2, 3, 4, 5) yield n * 2
│ │
generator transformation
(source) (what to produce)
│
Produces List(2, 4, 6, 8, 10)
For vs map
A for comprehension with yield desugars to map under the hood:
val names = List("alice", "bob", "carol")
// These three are equivalent:
val via_for = for name <- names yield name.capitalize
val via_map = names.map(_.capitalize)
val via_map2 = names.map(name => name.capitalize)
println(via_for) // List(Alice, Bob, Carol)
Filtering with Guards
val nums = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val evenSquares = for
n <- nums
if n % 2 == 0
yield n * n
println(evenSquares) // List(4, 16, 36, 64, 100)
A guard inside a for comprehension desugars to filter. The above is equivalent to nums.filter(_ % 2 == 0).map(n => n * n).
Multiple Generators (Cartesian Product)
val suits = List("♠", "♥", "♦", "♣")
val values = List("A", "K", "Q", "J")
val cards = for
suit <- suits
value <- values
yield s"$value$suit"
println(cards.take(8))
// List(A♠, K♠, Q♠, J♠, A♥, K♥, Q♥, J♥)
println(s"Total cards from subset: ${cards.length}") // 16
suits: ♠ ♥ ♦ ♣
values: A K Q J
For each suit, pair with every value:
♠ × (A K Q J) → A♠ K♠ Q♠ J♠
♥ × (A K Q J) → A♥ K♥ Q♥ J♥
...
Total: 4 × 4 = 16
Intermediate Values with val
val words = List("hello", "world", "scala", "is", "awesome")
val report = for
word <- words
upper = word.toUpperCase
length = word.length
if length > 4
yield s"$upper ($length chars)"
report.foreach(println)
// HELLO (5 chars)
// WORLD (5 chars)
// SCALA (5 chars)
// AWESOME (7 chars)
Nested Comprehension for Flatten
val matrix = List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
val flat = for
row <- matrix
element <- row
yield element
println(flat) // List(1, 2, 3, 4, 5, 6, 7, 8, 9)
Multiple generators flatten nested structures. This is identical to matrix.flatMap(row => row) or matrix.flatten.
For Comprehensions with Option
For comprehensions work with Option. If any step yields None, the whole expression short-circuits to None:
def findUser(id: Int): Option[String] =
if id == 1 then Some("Alice") else None
def findEmail(user: String): Option[String] =
if user == "Alice" then Some("alice@example.com") else None
val email = for
user <- findUser(1)
email <- findEmail(user)
yield email.toUpperCase
println(email) // Some(ALICE@EXAMPLE.COM)
val missing = for
user <- findUser(99) // None
email <- findEmail(user)
yield email
println(missing) // None
Desugaring: What the Compiler Does
for
x <- List(1, 2, 3)
if x > 1
y = x * 10
yield y + 1
desugars to:
List(1, 2, 3)
.withFilter(x => x > 1)
.map(x => { val y = x * 10; y + 1 })
Result: List(21, 31)
Practical: Student Report Card
case class Student(name: String, score: Int)
val students = List(
Student("Aarav", 92),
Student("Diya", 78),
Student("Rohan", 85),
Student("Priya", 45),
Student("Kiran", 67)
)
val topStudents = for
s <- students
if s.score >= 75
grade = if s.score >= 90 then "A" else "B"
yield s"${s.name}: ${s.score} ($grade)"
topStudents.foreach(println)
// Aarav: 92 (A)
// Diya: 78 (B)
// Rohan: 85 (B)
For Comprehension vs Method Chain
// For comprehension style
val result1 = for
n <- 1 to 10
if n % 2 != 0
sq = n * n
if sq > 10
yield sq
// Equivalent method chain
val result2 = (1 to 10)
.filter(_ % 2 != 0)
.map(n => n * n)
.filter(_ > 10)
println(result1.toList) // List(25, 49, 81)
println(result2.toList) // List(25, 49, 81)
Choose for comprehension when: Choose method chain when:
───────────────────────────── ────────────────────────────
Multiple generators are involved Single transformation
Complex intermediate values Short, linear pipeline
Working with Option/Future/Either Readability is clearer
Code reads like English Colleagues prefer method style
For Comprehension with Future (Preview)
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
val f1 = Future(10)
val f2 = Future(20)
val sum = for
a <- f1
b <- f2
yield a + b
sum.foreach(println) // 30
For comprehensions work with any type that implements map and flatMap — List, Option, Either, Try, Future, and more. This uniformity is why for comprehensions are so powerful in Scala.
