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.

Leave a Comment

Your email address will not be published. Required fields are marked *