Java, like most other programming languages, supports nested loops. This means just a loop within a loop. In this article, we are going to find out about how to work with nested loops in Java.

Java nested loops

A loop is called nested if it is placed inside another loop. On the first pass, the outer loop calls the inner loop, which runs to completion, after which control is transferred to the body of the outer loop. On the second pass, the outer loop calls the inner one again. And so on until the outer loop ends. There are four types of loops in Java:
  • for loop

  • while loop

  • do...while loop

  • for-each loop

All of them support nested loops. Nested-loop constructs are used when two conditions must be met, one depending on the other. For example, if you need to display a two-dimensional matrix, a semi-pyramid or a multiplication table.

How Java nested loops work

Probably the most used loop in Java is for, in large part because it is quite versatile and the code with it is quite easy to read. Here is the general syntax for nested for loop:

// outer loop
for (initialization; condition; increment) {
  //write here your code 

  //nested loop
  for(initialization; condition; increment) {
    //write here your code
  }
..
}
How does he work? The outer loop starts. Then the nested for loop starts the work and goes through its index until the condition is met, and again passes the work to the outer loop, and this happens until the condition of the outer loop is met. Sounds a little tricky, doesn't it? Well, It will be much easier to understand with a specific example, so let's move on to it.

Nested for loop code example

Here is one classic example. Let’s print out a half pyramid using two for loops. One of them is nested.

public class NestedLoopsDemo1 {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {
           for (int j = 0; j<=i;  j++)
               System.out.print("*");
           System.out.println();
       }
      
   }
}
The output is:
* ** *** **** ***** ****** ******* ******** ********* **********

Nested while loop code example


public class NestedLoopsDemo2 {

   public static void main(String[] args) {

       int i = 0;
       while (i < 10) {
           int j = 0;
           while (j <= i) {
               System.out.print("*");
               j++;
           }
           System.out.println();
           i++;

       }
   }
}
The output is just the same as in the previous example:
* ** *** **** ***** ****** ******* ******** ********* **********
The do...while loop is similar to while loop. The main difference is, that the body of do...while loop is executed once before the expression checking.

Nested foreach loops code example

for-each loop can be nested like usual for loop. Here is the example for nested for-each loop which iterates 2-dimensional array.

public class NestedLoops2 {

       public static void main(String[] args)
       {
           int[][] mainArray = { {5, 4, 3, 2, 1}, {7, 8, 9, 10, 11} };

           for (int[] myArray : mainArray)
           {
               for (int i : myArray)
               {
                   System.out.print(i+" ");
               }
               System.out.println("");
           }
       }
}
The output is:
5 4 3 2 1 7 8 9 10 11

Mixed for and while loop example

Sometimes we can nest different types of loops inside each other. For example, for inside while or for inside for-each. However, it’s not the best programming practice. Such constructs significantly impair the readability of the code. So professional programmers try not to mix one with the other. Well, they do, but only if it’s really needed. And one more little rule: if you are choosing between while and for, use for where possible. Nevertheless, here we are going to have an example of using a for loop inside the while. Let's build our semi-pyramid again.

public class NestedLoopsDemo2 {

   public static void main(String[] args) {
       int i = 0;
       while (i < 10) {
           for (int j = 0; j <= i; j++) {
               System.out.print("*");
           }
           System.out.println();
           i++;

       }
   }
}
The output is without surprises:
* ** *** **** ***** ****** ******* ******** ********* **********

Tracing the Execution of Nested Loops

Understanding how nested loops execute is crucial for debugging and writing efficient code. Tracing involves systematically following the iterations of each loop to understand the flow and ensure the expected behavior.

Nested loops consist of an outer loop and one or more inner loops. The inner loop completes all its iterations for each single iteration of the outer loop.

Example: Simple Nested Loop


for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 2; j++) {
        System.out.println("Outer: " + i + ", Inner: " + j);
    }
}

Output:

 Outer: 1, Inner: 1
 Outer: 1, Inner: 2
 Outer: 2, Inner: 1
 Outer: 2, Inner: 2
 Outer: 3, Inner: 1
 Outer: 3, Inner: 2

Step-by-Step Execution of Nested Loops

To trace a nested loop, follow the iteration count and print intermediate results. Let’s break down the example:

  1. Outer Loop (i = 1): Enters the first iteration of the outer loop.
  2. Inner Loop (j = 1 to 2): Executes the inner loop twice while i remains 1.
  3. Outer Loop (i = 2): Proceeds to the second iteration of the outer loop.
  4. Inner Loop (j = 1 to 2): Executes the inner loop twice while i remains 2.
  5. Outer Loop (i = 3): Enters the third and final iteration of the outer loop.
  6. Inner Loop (j = 1 to 2): Executes the inner loop twice while i remains 3.

Using print statements or debugging tools helps track these iterations in real-time during program execution.

Example: Visualizing Output in a Grid


for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        System.out.print("(" + i + "," + j + ") ");
    }
    System.out.println(); // Move to the next line after each row
}

Output:

(1,1) (1,2) (1,3) 
(2,1) (2,2) (2,3) 
(3,1) (3,2) (3,3)

Common Pitfalls and Debugging Techniques

Pitfall: Infinite Loops

Nested loops can accidentally run infinitely if the termination condition is not properly defined. For example:


for (int i = 1; i <= 3; i++) {
    for (int j = 1; j > 0; j++) { // Infinite loop
        System.out.println(i + "," + j);
    }
}

Solution: Ensure that each loop has a termination condition that will eventually be met.

Pitfall: Incorrect Loop Boundaries

Using incorrect start or end values can lead to unexpected results, such as skipping iterations or out-of-bounds errors.


// Incorrect: Starts from 0 instead of 1
for (int i = 0; i < 3; i++) {
    for (int j = 1; j <= 3; j++) {
        System.out.print("(" + i + "," + j + ") ");
    }
}

Solution: Double-check loop boundaries and initialization conditions during coding.

Debugging Techniques

  • Print Statements: Add print statements to display the values of loop variables during each iteration.
  • Debugging Tools: Use IDE debugging tools to set breakpoints and step through the loop execution.
  • Manual Tracing: Use pen and paper to simulate the iterations and predict the output before running the code.

Using break in Nested Loops

The break statement is used to terminate the nearest enclosing loop. When working with nested loops, break can be particularly useful for exiting an inner loop without affecting the outer loop.

Example: Breaking Out of an Inner Loop


for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {
        if (j == 3) {
            break; // Exit the inner loop when j equals 3
        }
        System.out.println("Outer: " + i + ", Inner: " + j);
    }
}

Output:

 Outer: 1, Inner: 1
 Outer: 1, Inner: 2
 Outer: 2, Inner: 1
 Outer: 2, Inner: 2
 Outer: 3, Inner: 1
 Outer: 3, Inner: 2

In this example, the break statement ensures that the inner loop stops executing as soon as j == 3, but the outer loop continues to the next iteration.

Using Labeled break

When dealing with deeply nested loops, you can use a labeled break to exit multiple levels of loops:


outerLoop:
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {
        if (j == 3) {
            break outerLoop; // Exit the outer loop
        }
        System.out.println("Outer: " + i + ", Inner: " + j);
    }
}

Output:

 Outer: 1, Inner: 1
 Outer: 1, Inner: 2

The labeled break immediately terminates both the inner and outer loops when j == 3.

Using continue in Nested Loops

The continue statement skips the current iteration of the nearest enclosing loop and proceeds with the next iteration. In nested loops, it is often used to bypass specific iterations in the inner loop without interrupting the overall flow.

Example: Skipping Iterations in an Inner Loop


for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {
        if (j == 3) {
            continue; // Skip the rest of this iteration when j equals 3
        }
        System.out.println("Outer: " + i + ", Inner: " + j);
    }
}

Output:

 Outer: 1, Inner: 1
 Outer: 1, Inner: 2
 Outer: 1, Inner: 4
 Outer: 1, Inner: 5
 Outer: 2, Inner: 1
 Outer: 2, Inner: 2
 Outer: 2, Inner: 4
 Outer: 2, Inner: 5
 Outer: 3, Inner: 1
 Outer: 3, Inner: 2
 Outer: 3, Inner: 4
 Outer: 3, Inner: 5

In this example, the continue statement skips the iteration where j == 3, while the outer loop and remaining inner loop iterations proceed as normal.

Using Labeled continue

Just like with break, you can use a labeled continue to skip iterations of an outer loop:


outerLoop:
for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 5; j++) {
        if (j == 3) {
            continue outerLoop; // Skip the rest of the current outer loop iteration
        }
        System.out.println("Outer: " + i + ", Inner: " + j);
    }
}

Output:

 Outer: 1, Inner: 1
 Outer: 1, Inner: 2
 Outer: 2, Inner: 1
 Outer: 2, Inner: 2
 Outer: 3, Inner: 1
 Outer: 3, Inner: 2

The labeled continue skips all remaining iterations of the inner loop and jumps directly to the next iteration of the outer loop when j == 3.

Practical Applications of break and continue

  • Filtering Data: Skip unwanted entries in a dataset using continue.
  • Early Exit: Use break to terminate loops when a specific condition is met, such as finding a required value.
  • Optimizing Performance: Avoid unnecessary iterations to save computation time in large datasets.