AlgoMaster Logo

For Loop

High Priority23 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

A for loop runs the same block of code a known number of times. It's the right tool when the question "how many iterations?" has an answer before the loop starts: print every item in a cart, charge five customers, count down from ten to one. This lesson covers the classic C-style for loop, its three parts, the scope rules around the loop variable, common patterns like counting down and stepping by two, nested loops, and the bugs that show up most often.

The Three Parts of a For Loop

The classic for loop has three pieces inside the parentheses, separated by semicolons, and then a body in braces.

Each piece has a job:

  • init runs once, before the loop starts. It usually declares a counter variable.
  • condition is checked before every iteration. If it's true, the body runs. If it's false, the loop exits.
  • update runs after every iteration, just before the next condition check.

A for loop that prints the receipt for a small shopping cart:

Reading the loop header: start with i = 0, keep going while i < 4, and add one to i after each pass. The body runs four times, with i taking on the values 0, 1, 2, 3.

That four-iteration pattern is the most common shape of a for loop in C++. It walks an index from 0 up to (but not including) some limit, which lines up with how arrays and std::vector are indexed.

For built-in types like int, i++ and ++i behave identically as loop updates. There's an old convention to write ++i because for user-defined types the pre-increment can be cheaper, but for plain integers the compiler emits the same code either way.

Execution Order

The order in which the three pieces run is not obvious from the syntax. The init runs once, then for every iteration the condition runs, then the body, then the update, then the condition again. The loop exits the first time the condition returns false, before the body runs.

The arrow from update back up to condition is the key detail. The condition is checked once at the start and then again after every iteration's update step. That's why a for loop's body might run zero times: if the condition is false the very first time it's checked, the body is skipped entirely.

itemsInCart is 0, so i < itemsInCart is 0 < 0, which is false. The body never runs. The init still runs once though, because init runs before the first condition check.

Scope of the Loop Variable

A variable declared inside the init part of a for loop is local to the loop. It exists from the start of the init expression until the closing brace of the loop body, and not a millisecond longer.

Uncommenting the line that reads i after the loop makes g++ reject it with error: 'i' was not declared in this scope. The compiler is right: i was created inside the for header and ended at the matching }. The name no longer refers to anything.

This is helpful, not annoying. It means two adjacent for loops can reuse the name i without conflict, and the counter can't accidentally be read by code that runs after the loop is supposed to be done.

When the counter needs to outlive the loop (to record how far the loop got), declare the variable before the loop and only initialize it in the header:

Here i is declared in the surrounding scope, so it's still visible after the loop. The loop header leaves the init part empty (just a bare ;) because i is already declared. The next section returns to empty init/condition/update.

For now, all that matters about break is that it exits the loop immediately.

All Three Parts Are Optional

C++ allows leaving any of the three parts of the header empty. The two semicolons are still required, but each piece is a separate, optional expression. The most extreme version:

That's an infinite loop. With no condition, the loop never decides to stop, so it relies on a break, a return, or some other escape inside the body. This pattern shows up when reading input from a user until they type "quit", or when running an event loop.

A concrete version:

The loop runs forever in principle, but the break inside the body exits when countdown reaches 0. This shape is fine for examples, but in real code a while (countdown > 0) reads more clearly. The for (;;) form shows that the header pieces aren't mandatory.

The parts can also be omitted individually. Each row below is a valid for loop:

Most loops use all three parts, because that's the shape for was designed for. The empty forms are tools for occasional use, not the default.

Multiple Variables with the Comma Operator

The init and update parts each accept a single expression, but the comma operator allows packing several into one. The most common use is walking two indices toward each other from opposite ends of a sequence.

Two details matter. First, the init declares both i and j on the same line. They must have the same type (here, int), because a single declaration can only declare variables of one type. Second, the update does i++, j--, which moves the two indices toward each other.

The loop stops when i and j meet in the middle. With seven elements, the middle index 3 is never paired, which is the correct behavior for an inward walk.

Comma can also combine a declaration with an unrelated initializer, but most of the time the use case is two counters moving together. A different pattern: a running total alongside an index.

That works, but cramming the work into the update makes the loop hard to read. A regular for loop with the addition in the body is clearer:

The comma operator is useful for the two-pointers-from-both-ends pattern. For most other cases, keeping the body honest is the right call.

Counting Up, Down, and Stepping by N

The update isn't limited to i++. It's an expression, so the step can be any amount in any direction.

Counting down:

The condition uses > (greater than) rather than <, because the counter is moving down. When countdown hits 0, the condition is false and the loop exits.

Stepping by two, useful for things like alternating rows in a table:

The update i += 2 jumps by two each iteration, so the loop runs five times instead of ten. The condition is still i < 10, so i never reaches or passes the limit.

The step can be any value, including non-integer values if the counter is a floating-point type. Floating-point counters are usually a bad idea (rounding error accumulates), but stepping by larger integers (5, 10, 100) shows up a lot in pagination, batching, or sampling every Nth row.

A loyalty-points example that awards a bonus every fifth purchase in a sequence:

The condition is i <= totalPurchases, with <= instead of <, to include the 25th purchase. Switching < and <= is one of the most common sources of off-by-one bugs.

Nested For Loops

A for loop's body can contain another for loop. The inner loop runs to completion for every single iteration of the outer loop. This is how to walk a grid: rows on the outside, columns on the inside.

A small multiplication table of product quantities:

For each value of r (1, 2, 3), the inner loop runs all four iterations of c (1, 2, 3, 4). The inner std::cout prints one cell. The outer std::cout << std::endl after the inner loop ends prints the line break that finishes the row.

The total number of times the inner body runs is rows * cols, which is 12 here. That multiplication is the thing to watch: a nested loop with two counters that each run to n does n * n work, which gets expensive fast.

Two nested loops over n items do n * n (O(n^2)) work in total. For n = 1000, that's a million iterations. For n = 100,000, it's ten billion, which will take minutes or hours. Know the size of inputs before writing nested loops over them.

A more application-flavored example: applying a discount to every item in every order.

The outer loop walks orders. The inner loop walks items in the current order. orderTotal is declared inside the outer loop, so it resets to 0.0 at the start of each order, which is the desired behavior. Declaring it before the outer loop would make every order inherit the previous order's total.

That reset is one of the subtleties of nested loops: the inner loop's counter resets each time it starts (it's a fresh variable each iteration of the outer loop), and any variable declared inside the outer body also resets. Variables declared outside the loops persist, which is sometimes the desired behavior and sometimes a bug. Pay attention to where things are declared.

Off-by-One Errors

The single most common bug in for loops is going one iteration too many or one too few. It's so common that it has a name: an off-by-one error, sometimes called a fencepost error.

The two usual flavors:

The array has four elements, with valid indices 0 through 3. The loop runs five times, ending at i = 4, and prices[4] reads memory past the end of the array. That's undefined behavior: the program might print a garbage value, crash, or appear to work fine and corrupt something else later. There's no friendly error message.

The fix is to use < instead of <=, or use the array's size directly:

The rule of thumb: when iterating from 0, use i < size, not i <= size. When iterating from 1 and the upper bound is inclusive, use i <= upperBound. Mixing the two is where bugs come from.

The other form of off-by-one is starting at the wrong value:

Array indices in C++ start at 0. If the loop starts at 1, the element at index 0 is skipped without warning. The compiler won't help: the loop is valid C++, it just walks the wrong elements.

A useful habit: before running a loop, mentally trace the first iteration and the last iteration. With exact values for both, off-by-one errors get rare.

Modifying the Loop Variable Inside the Body

The counter is a regular variable, so writing to it from inside the body is possible. That's almost always a mistake, even when it looks like a clever shortcut.

This prints 0 1 2 3 8 9, because at i = 3 the body bumps i to 7, then the header runs i++, making i = 8, and the loop continues. A reader has to step through the loop carefully to figure that out.

To skip ahead deliberately, use a while loop where the control is explicit, or restructure the logic with continue. Reaching into a for loop's counter from the body breaks the contract a reader expects: that the header decides how the counter moves.

There's one exception. If the body removes elements from a std::vector while iterating by index, the index needs to be adjusted. Even there, most code uses iterators or std::remove_if and avoids the index-fiddling entirely.

Signed vs Unsigned: The size_t Trap

Container sizes in C++ (std::vector::size(), std::string::size(), std::array::size()) return std::size_t, which is an unsigned integer type. Loop counters are usually int, which is signed. Comparing the two with < or <= triggers a compiler warning and, in rare cases, a bug.

With g++ -Wall, that loop produces:

The warning is correct. prices.size() is std::size_t. Comparing int to std::size_t promotes the int to std::size_t. If i ever held a negative value (it doesn't in this loop, but the compiler can't tell), the promotion would turn -1 into a huge positive number and the comparison would lie.

Three reasonable fixes:

The range-based for is the cleanest answer when the index isn't needed. When the index is needed, prefer std::size_t over int, and the warning goes away. Casting works too but feels like papering over the issue.

When to Pick For Over While

A for loop and a while loop can do the same things. The choice is about which one matches the shape of the problem more clearly.

Use a for loop when...Use a while loop when...
The number of iterations is known up frontThe loop runs "until something happens"
Walking an index from one value to anotherThe condition depends on external state, like user input
The counter has a fixed start, end, and stepThere's no natural counter
All the loop bookkeeping fits in the headerThe body decides when to exit

A clean way to remember it: if the answer to "how many times?" is known before the loop starts, prefer for. If the answer is "unknown, keep going until X happens," prefer while.

Two examples of the same task, written both ways:

Both are legal C++. The for reads cleanly because the start, end, and step are all visible at the top of the loop. The while reads cleanly because there's no counter, only a condition. Forcing either pattern into the other shape produces awkward code.

For arrays and std::vector, the range-based for is often a better fit when the index isn't needed, because it can't go out of bounds.

Receipt Example: Putting It Together

A small program that uses several of the patterns above to print a receipt with subtotals, a discount, and a final total. It's longer than the snippets so far, but every line uses something from this lesson.

The for loop does three things in one pass: it computes each line total, adds it to the running subtotal, and prints the receipt row. After the loop, the subtotal is used to compute a tiered discount (10% if the subtotal is over $50, nothing otherwise), and the discount is subtracted from the subtotal to get the final total.

The whole pattern (walk a list, accumulate a value, do something with the total after the loop) is one of the most common shapes for shows up in.

Quiz

For Loop Quiz

10 quizzes