TL;DR
  • Scala’s if, for, and match are all expressions that return values
  • for comprehensions concisely express collection transformations and monadic operations
  • match is more powerful than Java’s switch, supporting type matching and guard conditions
  • while is a statement, so avoid it in functional code

Target Audience: Developers with experience in other languages like Java/Python Prerequisites: Scala basic syntax (variables, types)

Scala’s control structures are expressions. That is, all control structures return values. This is fundamentally different from statement-based control structures in languages like Java or C. The expression-based approach makes code more concise and functional.

if Expression#

Scala’s if is an expression, not a statement. Therefore, there’s no need for a ternary operator—if itself returns a value.

Basic Usage

val x = 10

// if expression returns a value
val result = if (x > 5) "large" else "small or equal"
println(result)  // large

// No ternary operator needed (if itself returns a value)
val max = if (a > b) a else b

Scala 3 Syntax

In Scala 3, you can use the then keyword for more natural conditional syntax. Omitting parentheses and using indentation-based syntax makes code more readable.

val x = 10

// Using then keyword (recommended)
val result = if x > 5 then "large" else "small or equal"

// Multiple lines
val message =
  if x > 100 then
    "very large"
  else if x > 50 then
    "large"
  else
    "small"
val x = 10

// Parentheses required
val result = if (x > 5) "large" else "small or equal"

// Multiple lines
val message = {
  if (x > 100) {
    "very large"
  } else if (x > 50) {
    "large"
  } else {
    "small"
  }
}

Unit Return

Without else, the type can be inferred as Unit. In this case, if is used for side effects.

val x = 10

// Without else, type can be inferred as Unit
if (x > 5) println("large")

// Explicit Unit type
val result: Unit = if (x > 5) println("large")
Key Points
  • if is an expression that returns a value, eliminating the need for a ternary operator
  • Scala 3 allows more natural syntax with the then keyword
  • Without else, it can return Unit

for Expression#

Scala’s for is very powerful. It’s used for everything from simple iteration to collection transformation. Also called for comprehensions, it can concisely express monadic operations.

Basic Iteration

You can iterate over Range or collection elements. to includes the end value, while until excludes it.

// Iteration with Range
for (i <- 1 to 5) {
  println(i)  // 1, 2, 3, 4, 5
}

// until: excludes end value
for (i <- 1 until 5) {
  println(i)  // 1, 2, 3, 4
}

// Collection iteration
val fruits = List("apple", "banana", "cherry")
for (fruit <- fruits) {
  println(fruit)
}

Guards (Condition Filters)

Using if guards allows processing only elements that meet specific conditions. You can also combine multiple conditions.

// Execute only when condition is true
for (i <- 1 to 10 if i % 2 == 0) {
  println(i)  // 2, 4, 6, 8, 10
}

// Multiple conditions
for {
  i <- 1 to 100
  if i % 3 == 0
  if i % 5 == 0
} println(i)  // 15, 30, 45, 60, 75, 90

Nested Iteration

Using multiple generators allows concise expression of nested iteration. Useful for multiplication tables or coordinate generation.

// Multiplication table
for {
  i <- 2 to 9
  j <- 1 to 9
} {
  println(s"$i x $j = ${i * j}")
}

// Generate coordinates
for {
  x <- 0 until 3
  y <- 0 until 3
} println(s"($x, $y)")

yield - Create New Collection

Using yield makes the for expression return a new collection. This is equivalent to a combination of map, flatMap, and filter.

// Transform each element to create new list
val numbers = List(1, 2, 3, 4, 5)
val doubled = for (n <- numbers) yield n * 2
// List(2, 4, 6, 8, 10)

// Filter + Transform
val evenSquares = for {
  n <- 1 to 10
  if n % 2 == 0
} yield n * n
// Vector(4, 16, 36, 64, 100)

// Nested + yield
val pairs = for {
  x <- 1 to 3
  y <- 1 to 3
} yield (x, y)
// Vector((1,1), (1,2), (1,3), (2,1), (2,2), (2,3), (3,1), (3,2), (3,3))

With Pattern Matching

You can use pattern matching in for expressions. Useful for decomposing tuples, case classes, or Option values.

val pairs = List((1, "one"), (2, "two"), (3, "three"))

for ((num, str) <- pairs) {
  println(s"$num = $str")
}

// Extract values from Option
val maybeValues = List(Some(1), None, Some(3), None, Some(5))
for (Some(value) <- maybeValues) {
  println(value)  // 1, 3, 5 (skips None)
}

Scala 3 Syntax

In Scala 3, you can write match with indentation-based syntax using the do keyword.

// do keyword (optional)
for i <- 1 to 5 do
  println(i)

// Indentation-based
for
  i <- 1 to 3
  j <- 1 to 3
do
  println(s"$i, $j")

// yield
val result = for
  i <- 1 to 5
  if i % 2 == 0
yield i * i
// Using braces
for (i <- 1 to 5) {
  println(i)
}

// Multiple generators
for {
  i <- 1 to 3
  j <- 1 to 3
} {
  println(s"$i, $j")
}

// yield
val result = for {
  i <- 1 to 5
  if i % 2 == 0
} yield i * i
Key Points
  • for is an expression that creates new collections with yield
  • Guards (if) filter conditions, nested generators enable multiple iterations
  • Can be used with pattern matching to decompose tuples or Option values
  • for comprehension is syntactic sugar for map, flatMap, and filter

while Loop#

while is a statement, not an expression. It doesn’t return a value and returns Unit. Since it requires mutable state, it’s avoided in functional programming.

var i = 0
while (i < 5) {
  println(i)
  i += 1
}

do-while (Scala 2 Only)

do-while was removed in Scala 3. In Scala 3, you should replace it with another approach.

⚠️ Warning: do-while was removed in Scala 3. Use while loop instead in Scala 3.

// Use while instead of do-while
var j = 0
while {
  println(j)
  j += 1
  j < 5  // Evaluate condition at the end
} do ()

// Or more simply
var k = 0
while
  println(k)
  k += 1
  k < 5
do ()
// do-while available
var j = 0
do {
  println(j)
  j += 1
} while (j < 5)

In functional programming, prefer for or recursion over while. while requires mutable state (var).

Key Points
  • while is a statement that doesn’t return a value (returns Unit)
  • Since it requires mutable state (var), it’s avoided in functional code
  • do-while was removed in Scala 3

match Expression#

Scala’s match is much more powerful than Java’s switch. It supports value matching, type matching, pattern matching, and guard conditions.

Basic Matching

Returns different results based on values. _ is a wildcard that matches any value.

val day = 3

val dayName = day match {
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case 4 => "Thursday"
  case 5 => "Friday"
  case 6 => "Saturday"
  case 7 => "Sunday"
  case _ => "Invalid"  // Default (wildcard)
}
println(dayName)  // Wednesday

Type Matching

Can branch based on value types. Safely performs type checking and casting.

def describe(x: Any): String = x match {
  case i: Int    => s"Integer: $i"
  case s: String => s"String: $s"
  case d: Double => s"Double: $d"
  case _         => "Unknown type"
}

println(describe(42))      // Integer: 42
println(describe("hello")) // String: hello
println(describe(3.14))    // Double: 3.14

Guard Conditions

Using if guards allows specifying additional conditions. Guard conditions are evaluated after pattern matching.

val x = 15

val result = x match {
  case n if n < 0  => "negative"
  case n if n == 0 => "zero"
  case n if n < 10 => "single digit positive"
  case n if n < 100 => "two digit positive"
  case _ => "three or more digits"
}
println(result)  // two digit positive

OR Pattern

Using | allows grouping multiple patterns into one case.

val char = 'a'

val result = char match {
  case 'a' | 'e' | 'i' | 'o' | 'u' => "vowel"
  case _ => "consonant"
}

Scala 3 Syntax

In Scala 3, you can write match with indentation-based syntax.

val day = 3

// Indentation-based
val dayName = day match
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case _ => "Other"
val day = 3

// Braces required
val dayName = day match {
  case 1 => "Monday"
  case 2 => "Tuesday"
  case 3 => "Wednesday"
  case _ => "Other"
}
Key Points
  • match is an expression that returns a value
  • Supports value matching, type matching, guard conditions (if), and OR patterns (|)
  • _ is a wildcard that matches all values
  • More powerful than Java’s switch and the foundation of pattern matching

Expression vs Statement#

Almost everything in Scala is an expression. Blocks, try-catch, and even throw return values.

// Blocks are also expressions - last value is the result
val result = {
  val a = 1
  val b = 2
  a + b  // Block's result value
}
println(result)  // 3

// try-catch is also an expression
val parsed: Int = try {
  "42".toInt
} catch {
  case _: NumberFormatException => 0
}

// throw is also an expression (Nothing type)
def divide(a: Int, b: Int): Int =
  if (b == 0) throw new ArithmeticException("Cannot divide by zero")
  else a / b
Key Points
  • Almost everything in Scala is an expression
  • The last value of a block becomes the result value
  • try-catch is also an expression that returns a value
  • throw is an expression of type Nothing

Exercises#

Practice control structures with these exercises.

1. FizzBuzz

For numbers 1 to 100, print “Fizz” for multiples of 3, “Buzz” for multiples of 5, “FizzBuzz” for multiples of both 3 and 5, and the number otherwise.

Show Answer
for (i <- 1 to 100) {
  val result = (i % 3, i % 5) match {
    case (0, 0) => "FizzBuzz"
    case (0, _) => "Fizz"
    case (_, 0) => "Buzz"
    case _      => i.toString
  }
  println(result)
}

2. Multiplication Table

Generate a multiplication table from 2 to 9 using for + yield.

Show Answer
val table = for {
  i <- 2 to 9
  j <- 1 to 9
} yield s"$i x $j = ${i * j}"

table.foreach(println)

3. Grade Calculator

Write a function that takes a score (0-100) and returns a letter grade: A for 90 and above, B for 80 and above, C for 70 and above, D for 60 and above, and F for below 60.

Show Answer
def grade(score: Int): String = score match {
  case s if s >= 90 => "A"
  case s if s >= 80 => "B"
  case s if s >= 70 => "C"
  case s if s >= 60 => "D"
  case _            => "F"
}

println(grade(95))  // A
println(grade(72))  // C
println(grade(55))  // F

Next Steps#

Once you’ve learned control structures, proceed to the next topics.