Beginner

Control Flow in Scala

Master conditionals, pattern matching, loops, and for-comprehensions in Scala with practical Docker-ready examples

Control flow determines the order in which your program executes its statements—which branches it takes, which blocks it repeats, and which values it produces. Scala approaches control flow with a distinctly functional philosophy: rather than treating if, match, and for as mere statements that do things, Scala treats them as expressions that return values.

This expression-oriented design is the single most important idea to internalize. In an imperative language you might write if (cond) { x = 1 } else { x = 2 }, mutating a variable. In Scala, the if itself evaluates to a value: val x = if (cond) 1 else 2. Because of this, Scala has no dedicated ternary operator—if/else already fills that role.

As a multi-paradigm language blending object-oriented and functional styles, Scala gives you familiar while loops when you need them, but it strongly favors pattern matching over switch statements and for-comprehensions over imperative iteration. In this tutorial you’ll learn conditionals, match expressions, loops, and comprehensions—and see why Scala developers reach for the functional tools first.

Conditionals: if/else as Expressions

The if/else construct works as you’d expect, but remember that it always evaluates to a value. This eliminates the need for a separate ternary operator.

Create a file named Conditionals.scala:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
object Conditionals {
  def main(args: Array[String]): Unit = {
    val temperature = 18

    // if/else used as a statement for side effects
    if (temperature > 25) {
      println("It's warm outside")
    } else if (temperature > 10) {
      println("It's mild outside")
    } else {
      println("It's cold outside")
    }

    // if/else used as an EXPRESSION — it returns a value
    val label = if (temperature > 20) "warm" else "cool"
    println(s"The weather is $label")

    // Scala has no ternary operator; if/else fills that role
    val adjusted = if (temperature > 20) temperature else 20
    println(s"Adjusted reading: $adjusted")
  }
}

The chained else if reads like any C-family language, but the second use is uniquely Scala: if (temperature > 20) "warm" else "cool" is an expression whose result is bound to label. The s"..." string interpolation embeds the value directly. Because both branches must produce a compatible type, the compiler infers label as a String.

Pattern Matching: Scala’s Powerful switch

Where other languages have a limited switch, Scala has match—an expression that can match on values, multiple alternatives, guards, and even types. It is the idiomatic replacement for long if/else if chains.

Create a file named Matching.scala:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
object Matching {
  def main(args: Array[String]): Unit = {
    val day = 6

    // Match on a literal value; returns a result
    val name = day match {
      case 1 => "Monday"
      case 2 => "Tuesday"
      case 3 => "Wednesday"
      case 4 => "Thursday"
      case 5 => "Friday"
      case 6 => "Saturday"
      case 7 => "Sunday"
      case _ => "Unknown"   // _ is the catch-all (default) case
    }
    println(s"Day $day is $name")

    // Combine alternatives with | and add a guard with 'if'
    val kind = day match {
      case 6 | 7                 => "Weekend"
      case d if d >= 1 && d <= 5 => "Weekday"
      case _                     => "Invalid day"
    }
    println(s"Day $day is a $kind")

    // Match on the runtime TYPE of each element
    val items: List[Any] = List(42, "hello", 3.14, true)
    items.foreach { item =>
      val description = item match {
        case i: Int    => s"an Int: $i"
        case s: String => s"a String: $s"
        case d: Double => s"a Double: $d"
        case other     => s"something else: $other"
      }
      println(description)
    }
  }
}

Several pattern-matching features appear here. The _ wildcard is the default case. The | operator matches several alternatives at once (case 6 | 7). A guard (case d if ...) adds a boolean condition to a case, binding the matched value to d. Finally, type patterns (case i: Int) let you branch on an element’s runtime type—indispensable when working with Any or sealed hierarchies. Unlike a C-style switch, there is no fall-through and no break is needed.

Loops and For-Comprehensions

Scala supports while loops and for loops, but the for construct is far more capable than its imperative cousin: with yield it becomes a for-comprehension that builds a new collection.

Create a file named Loops.scala:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
object Loops {
  def main(args: Array[String]): Unit = {
    // 'to' is inclusive: 1, 2, 3, 4, 5
    print("Counting: ")
    for (i <- 1 to 5) {
      print(s"$i ")
    }
    println()

    // 'until' is exclusive; 'by' sets the step
    print("Even numbers: ")
    for (i <- 0 until 10 by 2) {
      print(s"$i ")
    }
    println()

    // A while loop — needs a mutable var
    var countdown = 3
    while (countdown > 0) {
      println(s"T-minus $countdown")
      countdown -= 1
    }
    println("Liftoff!")

    // for-comprehension: 'yield' builds a new collection
    val squares = for (n <- 1 to 5) yield n * n
    println(s"Squares: $squares")

    // Nested generators with a guard act like a filtered loop
    print("Pairs summing to 5: ")
    for {
      a <- 1 to 4
      b <- 1 to 4
      if a + b == 5
    } print(s"($a,$b) ")
    println()
  }
}

The 1 to 5 syntax creates an inclusive Range, while 0 until 10 is exclusive of the upper bound, and by 2 sets the step. The while loop is the one place you’ll genuinely need a mutable var—functional style otherwise avoids it. The for ... yield form is the star: it transforms a range into a new collection (here a Vector of squares) without any manual accumulation. The final block uses multiple generators plus an if guard to iterate over filtered combinations—Scala’s concise alternative to nested loops with an if inside.

Running with Docker

Run each example with the Scala CLI image. No local Scala installation required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Pull the Scala CLI image
docker pull virtuslab/scala-cli:latest

# Run the conditionals example
docker run --rm -v $(pwd):/app -w /app virtuslab/scala-cli:latest run Conditionals.scala

# Run the pattern matching example
docker run --rm -v $(pwd):/app -w /app virtuslab/scala-cli:latest run Matching.scala

# Run the loops example
docker run --rm -v $(pwd):/app -w /app virtuslab/scala-cli:latest run Loops.scala

Expected Output

Conditionals.scala produces:

It's mild outside
The weather is cool
Adjusted reading: 20

Matching.scala produces:

Day 6 is Saturday
Day 6 is a Weekend
an Int: 42
a String: hello
a Double: 3.14
something else: true

Loops.scala produces:

Counting: 1 2 3 4 5 
Even numbers: 0 2 4 6 8 
T-minus 3
T-minus 2
T-minus 1
Liftoff!
Squares: Vector(1, 4, 9, 16, 25)
Pairs summing to 5: (1,4) (2,3) (3,2) (4,1) 

Key Concepts

  • Everything is an expressionif/else and match return values, so you can assign their result directly to a val. This is why Scala has no separate ternary operator.
  • No fall-through in match — each case is self-contained; there is no break and no accidental fall-through like C-style switch.
  • Pattern matching is the idiomatic branch tool — prefer match with literals, alternatives (|), guards (if), and type patterns over long if/else if chains.
  • to vs until1 to 5 is inclusive (1–5); 1 until 5 is exclusive (1–4). Use by to set a step size.
  • for ... yield builds collections — a for-comprehension transforms and filters data into a new collection, replacing manual accumulation loops.
  • Mutability is the exceptionwhile loops require a mutable var; functional Scala favors comprehensions and recursion that avoid mutation.
  • Guards and multiple generators — a single for block can combine several generators with if guards, expressing nested-and-filtered iteration concisely.

Running Today

All examples can be run using Docker:

docker pull virtuslab/scala-cli:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining