Scala from the ground up

Loops: for and while

Ranges with to, until and by, a while loop with a counter that has to move, a for over a list, and building a line of text one pass at a time.

Part 5 of 12

Intermediate15 min readBack to Courses

Counting with for

for (i <- 1 to 5) runs the body once for each number from 1 to 5, and to means the last number is included. The arrow is read as taken from, and i is a fresh val on every pass: it holds the current number and cannot be reassigned inside the body.

until stops one short, which is the version you want when counting positions where the last index is one less than the count. by sets the step, so 0 to 10 by 5 gives 0, 5 and 10 rather than eleven passes of a single number. The word after the range does exactly what it says.

while, and the variable that moves

while repeats as long as a condition stays true, and unlike a for it takes a condition rather than a range. Something inside the body has to move that condition towards false, and that something is a var: a total that grows and a counter that climbs, one line each. If the counter line is missing, the loop never ends.

for is the safer default because the range decides when to stop. while is for the cases where the number of passes is not known in advance, and it is worth keeping to that use. Both run the body in order, top to bottom, so anything printed appears in the order the passes happened.

Loops over a list, and a line built by hand

The same for walks a list: for (w <- List("a", "b", "c")). Instead of a counter you are handed each item, and the item is what the body works with. Since a word knows its own length, a loop over words can print one line per word without ever mentioning a position.

A loop is also how a line of text gets assembled. Start with an empty var line, add each piece inside the body, and put the separator in front of everything except the first item — a counter kept in another var tells you which pass you are on. Lists have shorter ways to do this, which the collection chapter covers, but the loop is the version that shows what is happening.

A range is a value, not a loop

1 to 5 builds a Range, which is a collection of whole numbers. The for does not count in the background; it asks the range for its items one at a time and binds i to each. That is why the same shape walks a list without changing anything else: a range and a list are both things you can iterate over.

The end of a range is decided by one word. to includes its last number, until stops one before it, and by sets the step between items. A range can also be empty: 3 until 3 has no items, so the body runs zero times rather than failing. A descending range needs a negative step, as in 3 to 1 by -1.

while is different: it evaluates its condition before every pass, including the first, so a condition that starts false means the body never runs. The body has to move something the condition reads, and that something is normally a var. A for over a range is safer when the number of passes is known, because the range itself decides when to stop.

A loop that builds something

A for without yield runs for its effect, so it produces Unit and is used for printing, accumulating into a var, or filling a collection. When the goal is a new collection, map and filter say so in one expression and leave no counter to get wrong. The loop is still the right tool when each pass depends on the one before it.

Three loop mistakes: assigning to the for variable (i = i + 1) when i is a fresh val on each pass, writing a while whose counter never moves, and treating 1 to 0 as a countdown. It is an empty range, so the body does not run at all.

Try these next

  • Print the even numbers from 2 to 10 with a step of 2.
  • Use a while to count down from 5 to 1, printing each number.
  • Walk a list of three prices and print a running total after each one.

Four ways to repeat

// to includes its last number.
for (i <- 1 to 5) {
  println("count " + i)
}

// until stops one before it.
for (i <- 1 until 4) {
  print(i + " ")
}

// by sets the step.
for (i <- 0 to 10 by 5) {
  println("step " + i)
}

// while needs a var that moves.
var total = 0
var n = 1
while (n <= 4) {
  total = total + n
  n = n + 1
}

// The item, not the position.
for (w <- List("a", "b")) {
  println(w + " has " + w.length)
}

Counting a week

Run it and count the lines of each block. Then change the first range to `1 to 3` and the step to `by 2`, and predict the output before you run it again.

Output
Click "Run" to execute your code

A savings plan

Run it and read how the total grows each week. Then change the weekly amount and the number of weeks, and predict the final total before running it again.

Output
Click "Run" to execute your code

to includes its upper number and until does not: 1 to 5 is five passes and 1 until 5 is four. Reading a loop means reading that one word carefully, because an off-by-one is the usual place a program goes wrong.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

How many times does for (i <- 1 until 4) run its body?