Java Nested Loops
Table of Contents + −
In the last lesson you learned about the Java continue statement, which skips one pass of a loop. But real life often comes in grids: calendars, spreadsheets, a chessboard.
One loop is not enough for a grid. You need a loop inside another loop, and that is a nested loop.
🤔 Why a loop inside a loop?
Want a grid of stars, 3 rows tall and 4 wide? One loop prints one row. To repeat that row 3 times you’d copy it by hand. Messy, and it crashes the day you want 100 rows.
- Put the row loop inside another loop.
- Outer loop runs once per row; inner loop prints a full row.
- One loop handles columns, the other repeats it for every row.
A nested loop is the tool whenever a job has two directions: down the rows and across the columns.
- Print a grid, table, or shape pattern.
- Touch every cell in a 2D structure like a matrix.
- Build a multiplication table.
- Compare every item with every other to find pairs.
🧩 The basic shape of a nested loop
A nested loop is just one for loop written inside another. No new syntax. The smallest example counts rows 1 to 3 (outer) and columns 1 to 4 (inner), printing each pair.
public class FirstNested { public static void main(String[] args) { for (int row = 1; row <= 3; row++) { // outer loop: rows for (int col = 1; col <= 4; col++) { // inner loop: columns System.out.println("row " + row + ", col " + col); } } }}How it runs:
- Outer starts with
rowat 1. Its body is the entire inner loop. - Inner runs all the way through,
col1 to 4. - Only when the inner loop finishes does outer move to
row2. - Then the inner loop runs fully again, and so on.
Output
row 1, col 1row 1, col 2row 1, col 3row 1, col 4row 2, col 1row 2, col 2row 2, col 3row 2, col 4row 3, col 1row 3, col 2row 3, col 3row 3, col 4The key idea: the outer loop controls the rows, and the inner loop controls the columns.
rowstays at 1 whilecolruns all the way through.- Then
rowbecomes 2 andcolstarts over from 1. - The inner loop finishes a full lap before the outer takes one step.
🔢 How many times does the inner body run?
The most important counting rule: total runs of the inner body equals outer count times inner count.
- Outer ran 3 times, inner 4 each time, so the inner body ran 12 times (count the output lines).
- Outer
m, innernmeansm × nruns. - Outer 10, inner 10 means 100 runs; outer 100, inner 100 means 10,000.
When both loops count to the same n, the work is n × n, that is n². The inner body runs once for every combination of outer and inner values.
🔍 A dry-run trace
A dry run means walking through the code by hand, one step at a time. Here is a tiny 2-by-2 loop so you can see the order.
for (int i = 1; i <= 2; i++) { for (int j = 1; j <= 2; j++) { System.out.println("i=" + i + " j=" + j); }}Trace it step by step, following i and j:
ibecomes 1. Outer condition1 <= 2is true. Enter the inner loop.jbecomes 1. Inner condition1 <= 2is true. Printi=1 j=1. Thenj++makesj2.jis 2. Condition2 <= 2is true. Printi=1 j=2. Thenj++makesj3.jis 3. Condition3 <= 2is false. Inner loop ends.
- Back to outer.
i++makesi2. Condition2 <= 2is true. Enter the inner loop again.jresets to 1. Printi=2 j=1. Thenjbecomes 2.jis 2. Printi=2 j=2. Thenjbecomes 3.jis 3. Condition false. Inner loop ends.
- Back to outer.
i++makesi3. Condition3 <= 2is false. Outer loop ends.
Output
i=1 j=1i=1 j=2i=2 j=1i=2 j=2The big thing: j resets to 1 every time the outer loop steps. That fresh start is what gives each row a full set of columns.
✖️ A multiplication table
A useful example: a multiplication table for 1 to 5. Outer picks the first number, inner multiplies it by 1 through 5. We use System.out.print (no ln) so values stay on one line, then add a newline after each row.
public class MultiplicationTable { public static void main(String[] args) { for (int i = 1; i <= 5; i++) { // outer: each row for (int j = 1; j <= 5; j++) { // inner: each column System.out.print(i * j + "\t"); // \t adds a tab gap } System.out.println(); // move to the next line } }}- For each
i, the inner loop runsjfrom 1 to 5 and printsi * j. \tis a tab that lines the numbers up in neat columns.- The lone
System.out.println()ends the row so the next starts fresh.
Output
1 2 3 4 52 4 6 8 103 6 9 12 154 8 12 16 205 10 15 20 25That System.out.println() does important work: without it, every number prints on one long line. It is what breaks the grid into rows.
🟦 Printing a grid
The classic first nested-loop drawing: a block of stars, 3 rows by 5 columns. Outer counts rows, inner prints the stars in each row.
public class StarGrid { public static void main(String[] args) { for (int row = 1; row <= 3; row++) { // 3 rows for (int col = 1; col <= 5; col++) { // 5 stars per row System.out.print("* "); } System.out.println(); // end the row } }}- Inner prints
*five times to make one row. System.out.println()ends the row.- Outer repeats this for all 3 rows.
Output
* * * * ** * * * ** * * * *This is the foundation of every pattern program. Triangles, pyramids, and diamonds are just grids where the inner loop’s bound changes per row.
More on those next lesson.
🗂️ Looping over a 2D array
A 2D array is a grid of values with rows and columns. To touch every cell, loop over rows on the outside and columns on the inside. Here we print each number, row by row.
public class Print2DArray { public static void main(String[] args) { int[][] grid = { {1, 2, 3}, {4, 5, 6} };
for (int row = 0; row < grid.length; row++) { // each row for (int col = 0; col < grid[row].length; col++) { // each cell in the row System.out.print(grid[row][col] + " "); } System.out.println(); } }}Look at the bounds:
- Outer uses
grid.length, the number of rows. - Inner uses
grid[row].length, the columns in that row. grid[row][col]reads one cell at a time.- Counters start at 0 because array positions start at 0.
Output
1 2 34 5 6This row-then-column pattern reads or changes every value in any 2D array: outer index is the row, inner index is the column.
👥 Finding pairs
Another common job: compare every item with every other item. To print every pair from a list, outer picks the first item and inner picks the second.
public class FindPairs { public static void main(String[] args) { int[] nums = {1, 2, 3};
for (int i = 0; i < nums.length; i++) { for (int j = i + 1; j < nums.length; j++) { // start at i+1 to avoid repeats System.out.println("(" + nums[i] + ", " + nums[j] + ")"); } } }}The trick is the inner loop starting at i + 1, not 0:
- We never pair an item with itself.
- We never print the same pair twice (like both
(1, 2)and(2, 1)). - Each unique pair shows up once.
Output
(1, 2)(1, 3)(2, 3)This pattern shows up a lot, all using one item from the outer loop and another from the inner:
- Finding duplicates.
- Checking which two numbers add up to a target.
- Comparing every record with every other record.
🛑 break and continue affect only the inner loop
Here is what surprises people: a break or continue inside the inner loop affects only the inner loop. The outer loop does not notice and keeps going. Let’s prove it by breaking when col reaches 2.
public class InnerBreak { public static void main(String[] args) { for (int row = 1; row <= 3; row++) { for (int col = 1; col <= 3; col++) { if (col == 2) { break; // stops only the inner loop, not the outer one } System.out.println("row " + row + ", col " + col); } } }}- When
colhits 2,breakends the inner loop early. - The outer loop carries right on to the next row.
- Every row prints
col1, then breaks beforecol2. - The outer loop still runs all 3 times.
Output
row 1, col 1row 2, col 1row 3, col 1The rule to remember: a plain break or continue controls only the loop it sits inside. It cannot reach out and stop the outer loop, even though many beginners expect it to.
🏷️ Labeled break and continue to control the outer loop
To stop both loops at once, put a label on the outer loop and break with that label. A label is a name followed by a colon, written right before the loop. Here we name the outer loop outer and call break outer.
public class LabeledBreak { public static void main(String[] args) { outer: for (int row = 1; row <= 3; row++) { for (int col = 1; col <= 3; col++) { if (row == 2 && col == 2) { break outer; // stops BOTH loops at once } System.out.println("row " + row + ", col " + col); } } System.out.println("Done"); }}- The loops run normally until
rowis 2 andcolis 2. - At that point
break outerfires and jumps out of the labeled outer loop too. - The program goes straight to printing
Done.
Output
row 1, col 1row 1, col 2row 1, col 3row 2, col 1DoneLabeled continue works the same way but for skipping: continue outer jumps to the next pass of the outer loop and skips the rest of the inner loop. Here we skip to the next row as soon as col reaches 2.
public class LabeledContinue { public static void main(String[] args) { outer: for (int row = 1; row <= 3; row++) { for (int col = 1; col <= 3; col++) { if (col == 2) { continue outer; // jump to the next row, skip the rest of this row } System.out.println("row " + row + ", col " + col); } } }}Each row prints col 1, then continue outer skips straight to the next row. So col 2 and col 3 never print.
Output
row 1, col 1row 2, col 1row 3, col 1When to reach for labels
Use a labeled break or continue only when you truly need to control the outer loop from inside the inner one. They are powerful, but too many labels make code hard to follow. Often a plain flag variable or a helper method reads more clearly.
⏱️ Performance: nested loops are O(n²)
Remember the counting rule: outer times inner. When both loops run n times, the inner body runs n × n times, that is n². We write this as O(n²), said “order n squared”, a rough measure of how fast the work grows as data gets bigger.
n² grows fast:
nis 100 means 10,000 runs.nis 1,000 means 1,000,000 runs.nis 10,000 means 100,000,000 runs.
A list ten times bigger means a hundred times more work, so an O(n²) loop can turn a fast program into one that seems to freeze.
- Nested loops are right for grids, tables, and small data.
- For totalling a list or finding one value, one loop is enough.
- Reach for a second loop only when the problem has two dimensions, like rows and columns or every pair.
⚠️ Common Mistakes
Traps to watch for:
- Reusing the same counter for both loops. If both use
i, the inner loop changes the value the outer depends on. Give each loop its own counter. - Wrong inner bound. Using the outer count or wrong array length gives too many or too few items per row, or a crash.
- Expecting break to exit both loops. A plain
breakonly stops the inner loop. Use a labeled break for both.
The first two side by side.
// ❌ Wrong: both loops use i, so the inner loop corrupts the outer counterfor (int i = 1; i <= 3; i++) { for (i = 1; i <= 3; i++) { // reuses i, outer loop never advances correctly System.out.println(i); }}
// ✅ Right: each loop has its own counterfor (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { // separate counter j for the inner loop System.out.println(i + " " + j); }}And the break mix-up.
// ❌ Wrong: this break only ends the inner loop, the outer loop keeps runningfor (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (found) { break; // outer loop continues anyway } }}
// ✅ Right: label the outer loop and break it to stop bothsearch:for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { if (found) { break search; // stops both loops } }}✅ Best Practices
Habits for clean nested loops:
- Give each loop its own counter named after its job, like
rowandcol. - End each row with a newline. Put
System.out.println()after the inner loop so rows do not run together. - Use the right length for each loop. For a 2D array,
grid.lengthfor rows andgrid[row].lengthfor columns. - Avoid nesting you do not need. One loop is enough when it solves the problem. Keep an eye on the O(n²) cost.
- Use labels sparingly. Only when you genuinely need to control the outer loop.
🧩 What You’ve Learned
- ✅ A nested loop is a loop inside another loop. The outer loop controls rows, the inner loop controls columns.
- ✅ The inner loop runs fully on every single pass of the outer loop, resetting its counter each time.
- ✅ The inner body runs outer count × inner count times in total.
- ✅ A dry-run trace shows the inner counter resetting to its start on every outer step.
- ✅ Nested loops power multiplication tables, grids, 2D arrays, and finding pairs.
- ✅ A plain
breakorcontinueaffects only the inner loop. - ✅ A labeled break or continue lets you control the outer loop from inside the inner one.
- ✅ Nested loops are O(n²), so the work grows fast. Avoid nesting you do not need.
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
In a nested loop, which loop usually controls the rows?
Why: The outer loop steps once per row, while the inner loop runs all the columns for that row.
- 2
If the outer loop runs 5 times and the inner loop runs 4 times, how many times does the inner body run in total?
Why: It is outer count times inner count, so 5 × 4 = 20.
- 3
What does a plain break inside the inner loop do?
Why: A plain break only ends the loop it sits in, which is the inner loop. The outer loop carries on.
- 4
Why are nested loops described as O(n²) when both loops run n times?
Why: With both loops running n times, the inner body runs n × n times, which is n², so the work grows quickly with bigger data.
🚀 What’s Next?
Now that you can put one loop inside another, you have the tool behind every shape and pattern. Next we’ll use nested loops to draw triangles, pyramids, and other patterns on screen. It is the most fun way to get truly comfortable with loops, because you can see the result take shape line by line.