What a nested loop is
A nested loop is a loop placed inside the body of another loop. The outer loop starts one pass, and the inner loop then runs from beginning to end. Only when the inner loop has finished does the outer loop move on to its next pass, and then the inner loop starts again from scratch.
If the outer loop runs 5 times and the inner loop runs 10 times per pass, the inner body runs 5 × 10 = 50 times in total. Nested loops are how you work with two dimensions: rows and columns, grids, tables, and every pair of items in a collection.
The exercise: a rectangle of stars
The task is to print a rectangle of stars with 5 rows and 10 columns:
**********
**********
**********
**********
**********
The outer loop handles the rows, and the inner loop handles the columns of one row.
Step by step
- Loop over the rows. Write
for (row in 0 until 5). The range0 until 5excludes its end, sorowtakes the values 0, 1, 2, 3 and 4: five passes. - Loop over the columns inside it. Write
for (col in 0 until 10)in the body of the first loop. It gives ten passes for every row. - Print one star without a newline. In the inner loop,
print("*")writes the star and stays on the same line.printlnwould end the line after every star and give a column of 50 stars. - End the row. After the inner loop's closing brace, but still inside the outer loop, call
println()with no argument. It only moves to the next line. Placed inside the inner loop, it would break every row into single stars.
The solution in Kotlin
for (row in 0 until 5) {
for (col in 0 until 10) {
print("*")
}
println()
}
Some Kotlin details about these lines:
- Ranges.
0 until 5stops before 5. The range1..5includes both ends. Choose whichever matches the number you need. For a count of 5 starting from zero,untilis the natural fit. - The loop variables are read-only. You can't assign to
roworcolinside the body. Each pass gets a fresh value. - Unused variable. The inner loop never uses
col. It exists only to count the passes. Kotlin warns about an unused variable but compiles; naming it_is allowed and tells readers it is unused. - Scope.
rowis visible inside the inner loop, so you can use it there.coldisappears when the inner loop ends.
Using both counters
The loop variables are most useful when the inner loop uses them. A multiplication table prints i * j for each pair:
for (i in 1..3) {
for (j in 1..3) {
print("${i * j}\t")
}
println()
}
The output has three rows:
1 2 3
2 4 6
3 6 9
When the inner range depends on the outer variable, the shape changes. With for (j in 1..i) as the inner loop, row i gets i stars, which makes a triangle with 1, 2, 3 and then 4 stars on successive rows when i runs from 1 to 4.
Looping over a two-dimensional array
An array of arrays is a grid. In Kotlin, arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6)) holds three rows of two numbers. The outer loop goes through the rows, and the inner loop goes through the numbers of the current row:
val grid = arrayOf(intArrayOf(1, 2), intArrayOf(3, 4), intArrayOf(5, 6))
for (i in grid.indices) {
for (j in grid[i].indices) {
print(grid[i][j])
}
println()
}
This prints 12, 34 and 56 on separate lines. A Kotlin array reports its length with .size, not .length, and .indices is the range of valid positions, 0 until size. The inner loop uses the size of grid[i], because each row is itself an array. When you don't need the positions, loop over the values directly: for (row in grid) for (v in row) print("$v ").
Break in nested loops
A break leaves only the loop that directly contains it. Inside the inner loop, it ends that inner loop, and the outer loop continues:
for (i in grid.indices) {
for (j in grid[i].indices) {
if (grid[i][j] == 3) break
println(grid[i][j])
}
}
This prints 1, 2, 5 and 6, each on its own line. The 3 stops the second row early, so the 4 is skipped, but the third row still runs. To leave both loops at once, put a label on the outer loop and break to it:
outer@ for (i in grid.indices) {
for (j in grid[i].indices) {
if (grid[i][j] == 3) break@outer
println(grid[i][j])
}
}
This prints only 1 and 2. A break written in the outer loop's own body, outside the inner loop, also stops everything, because there is nothing else it can leave.
Common mistakes
- Putting
println()in the wrong place. Inside the inner loop it prints each star on its own line. After the outer loop it prints a single line of 50 stars. - Using
println("*")for the stars. The rectangle needsprintso that the stars of a row stay together. - Off-by-one ranges.
0 until 5gives 5 passes, while0..5gives 6. - Swapping rows and columns. The outer loop is the 5 rows and the inner loop is the 10 columns. Swapped, you get 10 rows of 5 stars.
- Using
.length. Kotlin arrays use.size;.lengthdoesn't compile. - Expecting
breakto leave both loops. Without a label it leaves only the innermost one.
Key takeaways
- The inner loop runs completely for every pass of the outer loop, so the body runs outer × inner times.
- Use
printinside the inner loop andprintln()after it to build rows. - A grid is an array of arrays, walked with one loop inside another.
breakaffects the innermost loop only, unless you break to a label.