Java for Loop Explained — Syntax, Examples and Common Mistakes
- The for loop header has three parts separated by semicolons: initializer (runs once before the loop), condition (checked before every iteration), and update (runs after every iteration). All three work together — get any one wrong and the loop either never runs, runs forever, or produces wrong output.
- Arrays in Java are zero-indexed. The first element is at index 0, the last is at index
array.length - 1. Always start your loop counter at 0 and usei < array.lengthas your condition — never<=. This single character difference is the most common source ofArrayIndexOutOfBoundsExceptionin beginner Java code. breakexits the entire loop immediately — no further iterations.continueexits only the current iteration and moves to the next one. Confusing them produces silent logic bugs, not compile errors. In nested loops, both keywords only affect the innermost loop they appear in.
- A for loop repeats a block of code a known number of times using three parts: initializer, condition, update
- Arrays are zero-indexed — always use i < array.length, never i <= array.length
- break exits the loop entirely; continue skips only the current iteration
- Nested loops multiply — an outer loop of N and inner loop of M means N x M executions
- The #1 runtime crash is ArrayIndexOutOfBoundsException from off-by-one errors
- Biggest trap: a stray semicolon after the for header creates an empty loop body with no compile error
- The condition is checked one more time than the body executes — relevant when the condition has side effects
Production Incident
for (int i = 0; i < array.length; i--) — the counter moved away from the stopping condition on every iteration, guaranteeing it would never reach a state where i < array.length became false. The compiler did not flag this because it is syntactically valid Java. The code review did not catch it because the diff looked like a one-character change.i-- with a corrected starting position of array.length - 1 and an updated condition of i >= 0. Added a loop iteration counter with a hard upper bound (maxIterations = array.length * 2) as a safety net that logs a fatal error and breaks if exceeded. Added structured logging every 1000 iterations so stalled loops become visible in the observability stack within minutes rather than hours.Almost every real program needs to repeat something. A banking app applies interest to thousands of accounts. A game redraws the screen 60 times per second. A search engine scores millions of web pages.
The for loop solves this elegantly. It lets you write an action once and tell Java exactly how many times to run it. It also keeps a counter variable automatically, so you always know which repetition you are on.
By the end of this article you will understand every part of a for loop's syntax, be able to write one from scratch without looking anything up, know how to loop through arrays correctly, nest one loop inside another without blowing up your performance budget, and spot the mistakes that trip up nearly every beginner — including a few that produce no compile error at all, which makes them genuinely dangerous.
These are not academic exercises. Every example here reflects something you will write in real production code within your first few months of Java development.
Anatomy of a Java for Loop — What Each Part Actually Does
A for loop has three parts crammed into one line, separated by semicolons. Each part has a specific job, and understanding each job separately makes the whole thing click immediately.
The first part is the initializer. It runs exactly once — right before the loop starts. You use it to create and set your counter variable. Think of it as setting the odometer to zero before a road trip. It runs once, establishes your starting position, and then stays out of the way.
The second part is the condition. Java checks this before every single repetition, including the very first one. If it evaluates to true, the loop body runs. If it evaluates to false, the loop stops immediately and Java moves on to whatever comes after the closing brace. This is the gatekeeper — it controls entry, not exit.
The third part is the update. It runs after every repetition of the loop body — right before the condition is checked again. You use it to change your counter so that the loop eventually ends. If you get this wrong, you get an infinite loop. If you leave it out entirely, same result.
The execution order is fixed and worth memorizing: initializer runs once → condition checked → body runs if true → update runs → condition checked again → repeat. That cycle continues until the condition is false.
One detail that catches people off guard: the condition is checked one more time than the body executes. A loop that runs 10 times checks its condition 11 times — once for each successful iteration, and once more when it evaluates to false and the loop exits. This is normally invisible, but if your condition calls a method with side effects, that method runs 11 times, not 10.
public class BirthdayInvitations { public static void main(String[] args) { // We need to write 5 birthday invitations. // The for loop handles the counting automatically. // // Part 1 — int invitationNumber = 1 // Creates the counter and starts it at 1. // Runs exactly ONCE, before the loop begins. Never again. // // Part 2 — invitationNumber <= 5 // Checked before EVERY iteration, including the first. // When invitationNumber reaches 6, this is false and the loop exits. // The loop body never sees invitationNumber = 6. // // Part 3 — invitationNumber++ // Runs AFTER every iteration of the body. // invitationNumber++ is shorthand for invitationNumber = invitationNumber + 1. // This is what moves the counter toward the stopping condition. for (int invitationNumber = 1; invitationNumber <= 5; invitationNumber++) { System.out.println("Writing invitation #" + invitationNumber); } // This line runs AFTER the loop finishes. // The loop ending does not affect code outside the loop block. System.out.println("All invitations written!"); // NOTE: invitationNumber is NOT accessible here. // Variables declared in the initializer are scoped to the loop only. // This is intentional — it prevents counter variables from leaking // into surrounding code where they have no meaning. } }
Writing invitation #2
Writing invitation #3
Writing invitation #4
Writing invitation #5
All invitations written!
- Initializer runs exactly once — before anything else, never again
- Condition is checked before every single iteration, including the first — if it starts false, the body never runs at all
- Body runs only if the condition is true — nothing inside the body executes if the gate is closed
- Update runs after every body execution, before the next condition check — this is your responsibility to get right
- The condition is evaluated one more time than the body executes — account for this if the condition has side effects like a method call
- If you can read the header as a plain English sentence and it makes sense, the loop is probably correct: 'start at 1; keep going while at or below 5; add 1 each round'
i < 10. It becomes a real issue when the condition calls a method: for (int i = 0; i < list.size(); i++) calls list.size() on every check. For an ArrayList that is a cheap O(1) call. For a database-backed collection or a method with side effects, that extra call has consequences.int size = list.size(); for (int i = 0; i < size; i++). This also eliminates the repeated method call overhead in tight loops.Looping Through an Array — The Most Common Real-World Use Case
The single most common use of a for loop in Java is walking through every element of an array. An array is a numbered list of values where every slot has an index. The critical detail — the one that causes more beginner crashes than anything else — is that Java arrays are zero-indexed. The first element lives at index 0, not index 1.
A 5-element array has indices 0, 1, 2, 3, and 4. The last valid index is always array.length - 1. There is no index 5 in a 5-element array. Ask for it and Java throws ArrayIndexOutOfBoundsException immediately at runtime with no warning beforehand.
This is precisely why the standard idiom for looping through an array is i < array.length with a strict less-than, not i <= array.length. With <=, when the counter reaches array.length (which is 5 for a 5-element array), the condition is still true, Java tries to read array[5], finds nothing there, and crashes.
The loop counter doubling as the array index is the elegant core of this pattern. You are not maintaining two separate things — the position in the array and the current iteration number are the same value. That is by design.
One practical note: if you only need the values and do not need the index for anything, the enhanced for-each loop (for (int score : testScores)) is cleaner. But the moment you need the position — to compare adjacent elements, to write back to the array, to display 'Score 3 of 5' — you need the indexed for loop.
public class StudentScoreCalculator { public static void main(String[] args) { // Five test scores stored in an array. // Index positions: [0]=85 [1]=92 [2]=78 [3]=95 [4]=88 // array.length = 5, but valid indices are 0 through 4. int[] testScores = {85, 92, 78, 95, 88}; int totalScore = 0; // i < testScores.length means i runs from 0 to 4 — exactly the valid indices. // The moment i reaches 5, the condition is false and the loop exits cleanly. // // If we wrote i <= testScores.length: // When i = 5, condition is true (5 <= 5), Java tries testScores[5]. // testScores[5] does not exist. JVM throws ArrayIndexOutOfBoundsException. // Program crashes with no output for that iteration. for (int i = 0; i < testScores.length; i++) { // i is the array index. (i + 1) is the human-readable position. // We use i for access, (i + 1) for display. Do not start i at 1. System.out.println("Score " + (i + 1) + ": " + testScores[i]); // Accumulate the running total. totalScore = totalScore + testScores[i]; } // Cast to double before dividing to get a decimal result. // Without the cast, integer division truncates: 438 / 5 = 87, not 87.6. double averageScore = (double) totalScore / testScores.length; System.out.println("----------------------------"); System.out.println("Total Score : " + totalScore); System.out.printf("Average Score: %.1f%n", averageScore); } }
Score 2: 92
Score 3: 78
Score 4: 95
Score 5: 88
----------------------------
Total Score : 438
Average Score: 87.6
i < array.length, never i <= array.length. With <=, on the final iteration i equals array.length — which is 5 for a 5-element array — and Java will throw ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 5. The array ends at index 4. There is no index 5. The crash happens at runtime with no compile warning, which is exactly what makes this the number one array loop mistake for beginners.
Secondary trap: i < array.length - 1 is also wrong — it silently skips the last element with no error. Your output looks almost right, which makes this harder to spot than an outright crash.array.length is computed dynamically and can be zero. A loop with i < 0 never executes — that is fine. But code that assumes the array has at least one element after the loop exits will behave unexpectedly. Always validate that an array is non-empty before processing it if your downstream logic assumes it has content.Arrays.stream(testScores).sum() and IntStream operations replace accumulator loops for simple aggregations. They handle edge cases like empty arrays cleanly and are harder to get wrong. Use them when index tracking is not needed.length - 1. This is not a quirk — it is how every array and list in Java works, and internalizing it early saves significant debugging time.<= instead of < is the single most common for loop bug in Java. The fix is one character. The crash it prevents is an immediate runtime exception.Nested for Loops — Loops Inside Loops (And When You Actually Need Them)
Sometimes one dimension of repetition is not enough. Printing a multiplication table requires every number from 1 to 10 multiplied by every other number from 1 to 10. Processing a 2D grid of pixels requires visiting every row and every column. Comparing every element in a list against every other element requires two passes through the data. These are inherently two-dimensional problems, and nested loops are how Java handles two dimensions.
A nested loop is a loop inside another loop. The outer loop controls one dimension — typically rows. The inner loop controls the other — typically columns. For every single iteration of the outer loop, the inner loop runs its full cycle from start to finish. If the outer loop runs 5 times and the inner loop runs 5 times, the body executes 25 times.
That multiplication is the key insight and the key danger. Two loops with bounds of 1000 each produce one million iterations. At one microsecond per iteration — a reasonable estimate for simple arithmetic — that is one second. Add a database call inside the inner loop at 10ms each and you are at 2.7 hours. Nested loops with large bounds and non-trivial inner bodies are a reliable path to production timeouts.
Before writing a nested loop, always calculate the total iterations explicitly. If N × M is larger than your data set comfortably allows within your latency budget, you need a different algorithm — often a HashMap or Set that turns an O(N²) comparison into O(N).
Variable naming in nested loops is not a style preference — it is a correctness requirement. Using i for both loops means the inner loop's i shadows the outer loop's i. The outer counter stops updating correctly and the output is wrong in a way that is genuinely confusing to debug. Use i and j, or better, use descriptive names like row and col that make the two-dimensional intent clear.
public class MultiplicationTable { public static void main(String[] args) { int tableSize = 5; // 5x5 table — 25 total cell calculations System.out.println("--- 5x5 Multiplication Table ---"); // OUTER LOOP: controls which row we are on (1 through 5). // This loop runs 5 times total. for (int row = 1; row <= tableSize; row++) { // INNER LOOP: controls which column we are on (1 through 5). // For every single value of 'row', this inner loop runs // completely from col=1 to col=5 before 'row' increments. // // 'row' and 'col' are separate variables — critical. // Using 'i' for both would cause the inner loop to overwrite // the outer counter, breaking the outer loop silently. for (int col = 1; col <= tableSize; col++) { int product = row * col; // %4d: print an integer right-aligned in a 4-character-wide field. // Without this padding, columns would not align and the table // would be unreadable for two-digit products. System.out.printf("%4d", product); } // After the inner loop completes one full row of columns, // print a newline to start the next row. // This println is part of the OUTER loop body, not the inner. System.out.println(); } // Total body executions: 5 rows x 5 columns = 25 // For a 1000x1000 table that would be 1,000,000 executions. // Always think about N x M before nesting loops. } }
1 2 3 4 5
2 4 6 8 10
3 6 9 12 15
4 8 12 16 20
5 10 15 20 25
- Outer loop runs N times — for each of those N runs, the inner loop completes its entire M-cycle before the outer counter increments
- Total executions = N × M — this multiplicative growth is the core characteristic and the core risk
- Always use different variable names —
rowandcol, oriandj. Reusing the same name causes the inner loop to shadow the outer counter, breaking both loops silently - Three nested loops produce N × M × P executions — the exponent grows fast. Three loops at 100 each is one million iterations
- If you can solve the problem with a single loop plus a data structure like a HashMap, that is almost always the right call for production code
- Break inside the inner loop only exits the inner loop — the outer loop continues. Plan your exit strategy before you write the nesting.
Controlling Loop Flow with break and continue
Sometimes you need to exit a loop before it naturally finishes, or skip one specific iteration without stopping the whole loop. Java provides two keywords for exactly these situations: break and continue.
break is the emergency exit. The moment Java encounters break, it leaves the current loop entirely — no more iterations, no revisiting the condition, no cleanup. Execution continues with the first line after the loop's closing brace. This is the right tool when you are searching for something and have found it. Checking the remaining elements would be wasted work.
continue is more nuanced. It does not stop the loop — it abandons only the current iteration and jumps immediately to the update step, then rechecks the condition. The loop continues normally from the next iteration. Think of it as 'never mind this one, move on.' This is useful when most elements need processing but a specific subset should be skipped — blank entries, null values, filtered-out categories.
The failure mode when you confuse them is silent: your code runs without error but produces wrong results. break when you meant continue terminates the loop too early, silently skipping every remaining element. continue when you meant break keeps processing elements you should have stopped at, potentially corrupting state or producing extra output. Neither produces a compile error or exception. The bug hides in the output.
One important scoping rule: both keywords only affect the loop they are directly inside. In a nested loop, break in the inner loop exits the inner loop and returns control to the outer loop — the outer loop continues. To exit the outer loop from inside the inner loop, use a boolean flag checked in the outer loop's condition, or use a labeled break if your team accepts that style.
public class SecurityScanner { public static void main(String[] args) { // Simulating a username list going through a security scan. // // Rules: // 1. Skip blank usernames — nothing to check, move on (continue) // 2. Stop the entire scan if a banned user is found (break) // 3. Log every username that passes both checks String[] usernames = {"alice", "bob", "", "charlie", "BANNED_USER", "diana"}; System.out.println("Starting security scan..."); for (int i = 0; i < usernames.length; i++) { // Rule 1: blank username — skip this slot, continue to the next. // 'continue' jumps to i++ then rechecks i < usernames.length. // The code below this block does NOT run for this iteration. if (usernames[i].isEmpty()) { System.out.println("Slot " + i + ": empty — skipping."); continue; // jumps to update step (i++), not to end of loop } // Rule 2: banned user found — raise alert and stop scanning entirely. // 'break' exits the for loop completely. // "diana" at slot 5 will never be checked — this is intentional. if (usernames[i].equals("BANNED_USER")) { System.out.println("ALERT: Banned user detected at slot " + i + "! Halting scan."); break; // exits the for loop, goes to "Scan complete." } // This line only runs if NEITHER condition above triggered. // If continue fired, we never reach here for that iteration. // If break fired, we never reach here for any future iteration. System.out.println("Slot " + i + ": '" + usernames[i] + "' cleared."); } // This line runs regardless of whether the loop ended naturally // or was exited early via break. break exits the loop, not the method. System.out.println("Scan complete."); } }
Slot 0: 'alice' cleared.
Slot 1: 'bob' cleared.
Slot 2: empty — skipping.
Slot 3: 'charlie' cleared.
ALERT: Banned user detected at slot 4! Halting scan.
Scan complete.
break ends the loop entirely — no more iterations, period. continue ends only the current iteration — the loop carries on with the next one.
If you mix them up, your program will not crash. It will silently produce wrong results, which is significantly harder to debug than an exception. Before writing either keyword, ask yourself one question out loud: 'Do I want to stop everything right now, or do I just want to skip this one item and keep going?' The answer tells you which keyword to use.break outerLabel;) is valid Java and does the same thing in fewer lines, but it tends to generate discussion in code reviews. The flag pattern communicates intent more clearly to the next engineer reading the code.| Aspect | for Loop | while Loop |
|---|---|---|
| Best used when | You know the number of iterations before the loop starts — processing N elements, counting from A to B, iterating an array | You do not know how many iterations you need — waiting for user input, reading until end-of-file, polling until a condition changes |
| Counter variable | Declared, initialized, and updated inside the loop header — all loop machinery is visible in one line | Declared before the loop, updated manually inside the body — the loop control logic is spread across multiple locations |
| Readability | All loop control visible in one header line — a reader can understand the loop's range without reading the body | Loop control split between the declaration, the condition, and the body update — requires reading all three locations |
| Risk of infinite loop | Lower — the update step is syntactically part of the header, making it hard to forget entirely (though easy to get wrong) | Higher — the update is inside the body, and it is easy to add a continue or early return that bypasses it, leaving the counter frozen |
| Iterating an array | Natural and idiomatic — i < array.length as the condition maps directly to array access semantics | Works but requires more boilerplate — declare i before the loop, update it inside, and remember not to bypass the update |
| Typical example | Print numbers 1 to 100; process every element in a results list; generate N rows of a report | Keep reading network input until the connection closes; retry an operation until it succeeds; run a game loop until the player quits |
| Scope of counter | Counter variable is scoped to the loop block only — invisible to code outside the loop, preventing accidental reuse | Counter is declared in the surrounding scope — accessible after the loop ends, which is sometimes needed to inspect the final value |
🎯 Key Takeaways
- The for loop header has three parts separated by semicolons: initializer (runs once before the loop), condition (checked before every iteration), and update (runs after every iteration). All three work together — get any one wrong and the loop either never runs, runs forever, or produces wrong output.
- Arrays in Java are zero-indexed. The first element is at index 0, the last is at index
array.length - 1. Always start your loop counter at 0 and usei < array.lengthas your condition — never<=. This single character difference is the most common source ofArrayIndexOutOfBoundsExceptionin beginner Java code. breakexits the entire loop immediately — no further iterations.continueexits only the current iteration and moves to the next one. Confusing them produces silent logic bugs, not compile errors. In nested loops, both keywords only affect the innermost loop they appear in.- Nested loops multiply execution count — an outer loop running N times and an inner loop running M times means N × M total body executions. Always calculate that product before writing nested loops. If N × M exceeds your performance budget, look for a HashMap or Set that turns the inner loop into a constant-time lookup.
- The stray semicolon after the for header is one of the hardest bugs to spot:
for (int i = 0; i < 10; i++);creates an infinite empty loop over nothing, then runs the following block once. No compile error, no exception — just silently wrong behavior. - The condition is evaluated one more time than the body executes. This is irrelevant for simple comparisons but matters when the condition calls a method with side effects or meaningful overhead. Cache expensive bounds outside the loop header.
⚠ Common Mistakes to Avoid
Interview Questions on This Topic
- QWhat are the three parts of a Java for loop header, and in what order does Java execute them? Can any of the three parts be left empty, and if so what happens?JuniorReveal
- QWhat is the difference between
breakandcontinuein a for loop? Write a short example that uses both in the same loop to demonstrate you understand the distinction.JuniorReveal - QIf you have a for loop with
i = 0, conditioni < 10, and updatei++, how many times does the loop body execute? How many times is the condition checked?Mid-levelReveal - QWhat is the time complexity of a nested for loop where both loops iterate N times? In what real-world scenario would you encounter this, and how would you optimize it?Mid-levelReveal
Frequently Asked Questions
What is a for loop in Java and when should I use it?
A for loop is a control flow statement that repeats a block of code a specific number of times, using a counter to track progress. Use it whenever you know in advance how many iterations you need — iterating through every element in an array, printing a times table, applying a transformation to N items, or repeating an action a fixed number of times.
The key signal is whether the iteration count is known before the loop starts. If it is, a for loop is the right tool. If you are waiting for something to happen — user input, a network response, a file reaching end-of-file — a while loop is a better fit because you do not know upfront how many iterations it will take.
Why does my Java for loop skip the last element of the array?
The most common cause is i < array.length - 1 instead of i < array.length. The expression array.length - 1 stops one element too early — it excludes the last valid index deliberately. Use i < array.length and the loop will correctly visit every element from index 0 through index length - 1.
If the first element is being skipped instead, check whether your initializer starts at 1 instead of 0. Arrays are zero-indexed, so starting at 1 misses the element at index 0 silently — same type of bug, opposite end of the array.
What happens if I leave the update part of a for loop empty?
Java allows you to omit any of the three parts of the for loop header. If you leave the update empty — like for (int i = 0; i < 10;) — the counter never changes automatically. The condition remains true on every check and the loop runs forever, consuming 100% of a CPU core until the process is killed.
The only valid reason to omit the update is if you are updating the counter manually inside the loop body using a break, a return, or an explicit i++ at the bottom. Even then, most experienced developers prefer to keep the update in the header where it is visible and expected. Hiding counter updates inside the body is a maintenance hazard.
Can I declare multiple variables in the initializer of a for loop?
Yes, but all variables must share the same type. For example: for (int i = 0, j = 10; i < j; i++, j--) is valid — two int counters moving toward each other from opposite ends. You can also update multiple counters in the update step using the comma operator as shown.
That said, keep loop headers simple. Multiple variables in a for header work correctly but reduce readability — a reader has to parse both counters, both conditions, and both update steps simultaneously. If the logic is genuinely two-dimensional, a nested loop with separate counters is usually clearer. If you find yourself needing three or more variables in a single for header, that is a strong signal the logic belongs in the loop body or in a dedicated method.
Developer and founder of TheCodeForge. I built this site because I was tired of tutorials that explain what to type without explaining why it works. Every article here is written to make concepts actually click.