Scala - While and Do-While Loops
While loops execute a code block repeatedly as long as the condition evaluates to true. The condition is checked before each iteration, meaning the loop body may never execute if the condition is...
Key Insights
- While loops in Scala are imperative constructs that execute a block of code repeatedly as long as a boolean condition remains true, making them suitable for scenarios where iteration count is unknown upfront
- Do-while loops guarantee at least one execution of the loop body before checking the condition, which is critical for input validation and menu-driven programs
- Scala’s functional programming paradigm offers alternatives like recursion and higher-order functions that often produce more idiomatic code than traditional loops, though while loops remain useful for performance-critical sections and state mutation
Understanding While Loops
While loops execute a code block repeatedly as long as the condition evaluates to true. The condition is checked before each iteration, meaning the loop body may never execute if the condition is initially false.
var count = 0
while (count < 5) {
println(s"Count is: $count")
count += 1
}
// Output:
// Count is: 0
// Count is: 1
// Count is: 2
// Count is: 3
// Count is: 4
While loops require mutable state through var declarations. The loop continues until the condition becomes false or a control statement like break (available through scala.util.control.Breaks) interrupts execution.
import scala.util.control.Breaks._
var sum = 0
var i = 1
breakable {
while (i <= 100) {
sum += i
if (sum > 50) break()
i += 1
}
}
println(s"Sum: $sum, i: $i") // Sum: 55, i: 11
Do-While Loop Fundamentals
Do-while loops guarantee at least one execution of the loop body because the condition is evaluated after each iteration. This makes them ideal for validation scenarios where you need to perform an action before determining whether to continue.
var input = ""
do {
println("Enter 'quit' to exit:")
input = scala.io.StdIn.readLine()
println(s"You entered: $input")
} while (input != "quit")
The syntax differs from while loops with the condition appearing after the body:
var number = 10
do {
println(number)
number -= 1
} while (number > 0)
// Prints numbers from 10 down to 1
Even when the condition is initially false, the do-while body executes once:
var x = 10
do {
println(s"Executing with x = $x")
x += 1
} while (x < 5)
// Output: Executing with x = 10
// Loop body executes once despite condition being false
Practical Examples with Collections
While loops work effectively when processing collections with complex termination conditions or when you need index-based access with custom logic.
val numbers = Array(3, 7, 2, 9, 4, 1, 8)
var index = 0
var foundValue = -1
while (index < numbers.length && foundValue == -1) {
if (numbers(index) > 5 && numbers(index) % 2 == 0) {
foundValue = numbers(index)
}
index += 1
}
println(s"First even number > 5: $foundValue at index ${index - 1}")
// Output: First even number > 5: 8 at index 6
Processing data until a specific condition with accumulated state:
val data = List(10, 20, 30, 40, 50, 60)
var iterator = data.iterator
var accumulated = 0
var threshold = 100
while (iterator.hasNext && accumulated < threshold) {
val value = iterator.next()
accumulated += value
println(s"Added $value, total: $accumulated")
}
println(s"Final accumulated value: $accumulated")
Performance-Critical Scenarios
While loops can outperform functional alternatives in performance-critical code where allocation overhead matters. Consider this comparison for summing array elements:
def sumWithWhile(arr: Array[Int]): Int = {
var sum = 0
var i = 0
while (i < arr.length) {
sum += arr(i)
i += 1
}
sum
}
def sumFunctional(arr: Array[Int]): Int = {
arr.sum
}
val largeArray = Array.fill(1000000)(scala.util.Random.nextInt(100))
// While loop approach typically shows better performance
// for primitive arrays due to reduced allocation
val result1 = sumWithWhile(largeArray)
val result2 = sumFunctional(largeArray)
Matrix operations benefit from while loops when optimizing memory access patterns:
def multiplyMatrices(a: Array[Array[Int]], b: Array[Array[Int]]): Array[Array[Int]] = {
val rows = a.length
val cols = b(0).length
val common = b.length
val result = Array.ofDim[Int](rows, cols)
var i = 0
while (i < rows) {
var j = 0
while (j < cols) {
var sum = 0
var k = 0
while (k < common) {
sum += a(i)(k) * b(k)(j)
k += 1
}
result(i)(j) = sum
j += 1
}
i += 1
}
result
}
val matrixA = Array(Array(1, 2), Array(3, 4))
val matrixB = Array(Array(5, 6), Array(7, 8))
val product = multiplyMatrices(matrixA, matrixB)
// Result: [[19, 22], [43, 50]]
State Machines and Event Processing
While loops excel at implementing state machines where transitions depend on complex conditions:
sealed trait State
case object Idle extends State
case object Processing extends State
case object Waiting extends State
case object Complete extends State
class StateMachine {
private var currentState: State = Idle
private var iterations = 0
def run(): Unit = {
while (currentState != Complete && iterations < 100) {
currentState match {
case Idle =>
println("Starting processing...")
currentState = Processing
case Processing =>
println("Processing data...")
iterations += 1
if (iterations % 3 == 0) currentState = Waiting
else if (iterations > 10) currentState = Complete
case Waiting =>
println("Waiting for resources...")
currentState = Processing
case Complete =>
println("Done")
}
}
println(s"Finished after $iterations iterations")
}
}
val machine = new StateMachine()
machine.run()
Infinite Loops and Server Applications
Server applications often use infinite while loops with explicit termination conditions:
import java.util.concurrent.atomic.AtomicBoolean
class SimpleServer {
private val running = new AtomicBoolean(true)
def start(): Unit = {
while (running.get()) {
try {
// Simulate processing requests
val request = pollForRequest()
if (request.nonEmpty) {
processRequest(request.get)
}
Thread.sleep(100)
} catch {
case _: InterruptedException =>
println("Server interrupted")
running.set(false)
}
}
println("Server stopped")
}
def stop(): Unit = {
running.set(false)
}
private def pollForRequest(): Option[String] = {
if (scala.util.Random.nextInt(10) > 7) Some("Request") else None
}
private def processRequest(req: String): Unit = {
println(s"Processing: $req")
}
}
When to Choose Functional Alternatives
Despite their utility, while loops aren’t always the best choice. Recursive functions often provide clearer intent:
// While loop approach
def factorialWhile(n: Int): Int = {
var result = 1
var i = 2
while (i <= n) {
result *= i
i += 1
}
result
}
// Recursive approach - more idiomatic Scala
def factorialRecursive(n: Int): Int = {
@scala.annotation.tailrec
def loop(n: Int, acc: Int): Int = {
if (n <= 1) acc
else loop(n - 1, n * acc)
}
loop(n, 1)
}
println(factorialWhile(5)) // 120
println(factorialRecursive(5)) // 120
For collection processing, higher-order functions provide better composability:
val numbers = List(1, 2, 3, 4, 5)
// While loop
var doubled = List.empty[Int]
var iter = numbers.iterator
while (iter.hasNext) {
doubled = doubled :+ (iter.next() * 2)
}
// Functional approach - clearer and more efficient
val doubledFunctional = numbers.map(_ * 2)
While and do-while loops remain valuable tools in Scala for specific scenarios: performance-critical code, state machines, and situations requiring fine-grained control over iteration. However, understanding when functional alternatives provide clearer, more maintainable solutions is essential for writing idiomatic Scala code. Choose while loops when mutability and imperative control flow genuinely simplify the problem, not out of habit from other programming languages.