Last Updated: May 17, 2026
A for loop is C#'s answer to the question "do this a known number of times." When you already know how many items are in the cart, how many rows to print, or how many positions to step through, for packs the setup, the stop condition, and the step into a single header so the body stays focused on the actual work. This chapter walks through the syntax, the execution order, common patterns from cart processing, and the mistakes that catch people on day one.
A for loop has three slots in its header, separated by semicolons, followed by the body in braces:
Each slot has one job:
true, the body runs. If it's false, the loop ends.A small concrete example. A cart holds five items, and we want to print one line per item:
Five iterations, counter goes from 0 to 4, body runs each time. Read the header out loud: "start i at zero; keep going while i is less than five; after each pass add one to i."
The three slots run in a specific order, and getting that order right is the difference between a loop that does what you want and one that's off by one or runs forever.
The diagram shows the full life cycle. The initializer runs exactly once. Then the runtime loops between condition check, body, and iterator, in that order. The loop exits the first time the condition is false. Two things follow from this:
for loop can run zero times if the condition starts out false.i has its initial value the first time the body runs.Here's a trace of the example above, showing each step:
Five body runs, six condition checks. The last check is the one that ends the loop.
Most for loops you'll write count up through the indices of an array or a List<T>. Pair the loop counter with the collection's length and you can read every position by index.
A few things to notice. The counter starts at 0 because arrays and lists in C# are zero-indexed. The condition uses <, not <=, because index cart.Length is one past the last valid index. The body uses i two ways: as a position into the array, and as a human-readable position when printed with i + 1.
Cost: Array.Length and List<T>.Count are O(1). Both store the size in a field and just read it. IEnumerable<T>.Count() (the LINQ extension method) is different. On a sequence without a known count, it walks the whole thing, which is O(n). Calling it inside the loop's condition turns an O(n) loop into O(n^2). When you're working with raw IEnumerable<T>, cache the count or switch to foreach.
A second pattern: walking a price list and accumulating a running total.
The counter walks the array, the body reads prices[i] and adds it to a total declared before the loop. The total survives the loop because it's declared in the outer scope; we'll come back to scoping rules in a moment.
Sometimes you need to walk a list in reverse: showing the most recent order first, removing items from the end of a cart while iterating, or rendering a stack with the top item on top. Flip the initializer, condition, and iterator and you're done.
The counter starts at the last valid index (Length - 1), the condition is >= 0 (the first valid index), and the iterator is i--. Reading the header: "start at the end; keep going while the index is still valid; step back one each time."
The condition is now >= 0, not > 0. With > 0 you'd stop one element early and miss index zero, which is the first order in the array. This is one of the classic off-by-one errors a for loop can hide if you're not careful.
The iterator slot is just an expression. i++ is the common case, but you can step by any amount.
Stepping by two, useful when you want every other row of a price grid:
Stepping in dollar amounts, useful when you're building a price-tier list for filtering:
The counter doesn't have to be an int. Here it's a decimal because we're stepping through monetary tiers.
Float counter warning: Don't use float or double for the counter when the step is a fraction. Floating-point rounding can make 0.1 + 0.1 + 0.1 + ... drift slightly off the value you expected, and for (double d = 0; d <= 1.0; d += 0.1) might run nine times instead of ten depending on the accumulated drift. Use int for the counter and compute the floating value inside the body, or use decimal if exact base-10 stepping matters.
Both the initializer and the iterator can hold multiple expressions, separated by commas. The classic use case is walking two indices toward each other from opposite ends.
Two counters, i starting from the front and j starting from the back. They march toward each other one step at a time until they meet. Five items means two pairs plus a middle item that gets skipped because i < j becomes false when i and j both equal 2.
A few constraints worth knowing:
int i = 0, j = 5 works. int i = 0, double d = 0.0 doesn't.i++, total += prices[i] is legal, just hard to read.You don't have to use this often. When in doubt, declare the second variable outside the loop and update it in the body. The code is usually easier to scan.
Any of the three slots can be left empty. The semicolons still have to be there.
An empty initializer, when the counter is already declared:
Notice i is still in scope after the loop because it was declared outside. We'll come back to this in the scope section.
An empty iterator, when the body updates the counter itself:
An empty condition, which means "always true." This is the infinite loop form:
for (;;) runs forever. You'd combine it with a break statement inside the body to exit when you're done. The _Break & Continue_ lesson covers break and continue in detail; for now, just know the infinite-loop form exists.
If the body has no break and no return, you've written an infinite loop on purpose or by accident. Either way, the program will sit there until you stop it.
When you declare the counter inside the header, the counter is local to the loop. You can't read it after the loop ends.
The compiler error CS0103: The name 'i' does not exist in the current context is the rule in action. The counter's scope is the loop header plus the loop body. The moment the loop exits, the name is gone.
When you declare the counter outside the loop instead, the counter survives:
Most of the time, declaring the counter in the header is what you want. It keeps the name from leaking into surrounding code and signals clearly that this variable belongs to the loop. Pull the declaration outside only when you genuinely need the final value after the loop ends.
You also can't declare two loops with the same counter name in the same surrounding scope:
The compiler rejects this with CS0136 because the loop's i would shadow the outer i, which C# doesn't allow. Pick a different name (or remove the outer declaration).
Loops nest just like any other block. The classic use case is anything with rows and columns: a price grid, a table of stock counts, or a 2D layout.
The outer loop walks the rows (one product per row). For each row, the inner loop walks the columns (one discount tier per column) and prints the discounted price. The order matters: the outer loop runs to completion for each one iteration of itself, but the inner loop completes fully for every single iteration of the outer.
How many times does the body of the inner loop run in total? Three products times three discount tiers, so nine times. That's the cost rule for nested loops:
Cost: A loop nested inside another loop runs outer count * inner count times. Two nested loops over a list of size n is O(n^2). Three is O(n^3). This is fine for a small fixed grid, but it gets expensive quickly when the sizes grow. Watch out when both loops walk the same large collection.
The counters in nested loops get their own scope. The outer row and the inner col don't collide because the inner counter only exists inside its own loop body. You can reuse the name i in an inner loop too, but it's usually clearer to pick descriptive names (row, col, i, j) so a reader can tell the two apart at a glance.
A few for patterns come up so often in e-commerce code that they're worth pinning down by name.
When you generate a batch of sequential order IDs, the counter doubles as the suffix:
The first loop fills the array; the second prints it. D4 is a format specifier that pads numbers to four digits with leading zeros (in this case it doesn't add any because the numbers are already four digits, but it would for IDs under 1000).
A per-item discount touches every element, so the loop walks the index and writes back to it:
This is a pattern where for is genuinely better than foreach: writing back into the array by index needs the index, and foreach doesn't give you one.
Totals, counts, averages: any aggregate is a variable declared outside the loop and updated inside.
total and items live in the outer scope so they're still readable after the loop. Inside the loop, total += cart[i] is the accumulating step.
When you want to precompute a small mapping (price tier to label, rating to verdict), a for loop fills the table once and the rest of the program reads from it:
The first loop fills positions 1 through 5; the second prints positions 0 through 5. Building a table once and looking it up many times can replace a switch inside a hot loop. The lookup is O(1) while the switch is O(number of cases).
Counting items by a category is a for loop plus a counter variable for each category:
Three counters, one loop, one pass through the array. This kind of single-pass tally is exactly what for is good at.
Five mistakes show up over and over when people write their first for loops. They're easy to fix once you see them named.
Using <= when you meant < (or the reverse) walks one step too far or stops one step short. The valid indices of an array of length n are 0 through n - 1. Two correct headers:
The mistake:
The runtime throws IndexOutOfRangeException when i becomes cart.Length. The compiler can't catch this because the condition is legal C#; only the runtime sees the actual length. The fix is <, not <=.
You can change the counter inside the body, but it's almost always a mistake. The iterator slot is supposed to be the only place the counter changes. Mixing in extra updates inside the body makes the loop hard to read and easy to break.
If you really need to skip an item, use continue or restructure the loop so the iterator slot has the only update. The version with continue is the one that reads cleanly:
float or double for the CounterWe touched on this in the step-size section. Floating-point arithmetic isn't exact, so a loop like this can run a different number of times than you expect:
0.1 can't be represented exactly in binary, so after ten additions the accumulated value is slightly off from 1.0, the equality d != 1.0 is still true, and the loop keeps running. Use int for the counter and compute the floating value inside the body:
When the counter is declared in the loop header, it's local to the loop. Trying to use it afterward is a compile error. The fix is to either move the declaration outside the loop or compute the final value differently.
If you want the final count, use cart.Length directly. If you want the loop to "stop early" and report where it stopped, you'd combine a counter declared outside with a break.
.Count() in the ConditionWhen the collection is an IEnumerable<T> (a LINQ result, a sequence, anything that doesn't expose Length or Count), calling .Count() in the condition walks the whole sequence every iteration. That turns the loop from O(n) into O(n^2):
Cache the count once:
For raw arrays and List<T>, this isn't a concern because Length and Count are O(1) field reads.
This chapter is about for, but you'll meet foreach in the _foreach Loop_ lesson and run into the same question: which one fits the situation? A quick preview of the trade-off.
| You want to... | Pick |
|---|---|
| Walk every item in a collection in order | foreach |
| Read the index along with the item | for |
| Step by something other than one | for |
| Walk in reverse | for |
| Modify the collection's elements by index | for |
| Iterate two collections in parallel by index | for |
Walk an IEnumerable<T> that doesn't expose Count cheaply | foreach |
The default for "just give me each item" is foreach. The reasons to reach for for are mostly index-related: you need the position, you want to step in a non-default way, or you want to write back into the collection. The _foreach Loop_ lesson gives foreach the full treatment.
Pulling several ideas together. A program prints a cart summary, applies a per-item discount, calculates the total, and counts how many items are over $20.
One loop, three independent jobs in the body: print a row, accumulate the total, count the premium items. The counter i indexes both arrays in parallel. The aggregates cartTotal and premiumItems are declared outside so they survive past the loop's last iteration.
for loop has three header slots: initializer (runs once), condition (checked before each pass), and iterator (runs after each pass). The body runs while the condition is true.i++, count down with i--, step by anything with i += step. Avoid float or double counters when the step is a fraction; floating-point drift makes the loop unreliable.for (;;) is the infinite-loop form, usually paired with a break inside the body.n and inner of m runs the body n * m times. Watch the complexity when both grow.<= instead of <, modifying the counter inside the body, using floating-point counters, reusing the counter after the loop, and calling .Count() on an IEnumerable<T> inside the condition.for when you need the index, want a non-default step, want to walk in reverse, or want to modify a collection by index. Use foreach (see the _foreach Loop_ lesson) for the plain "give me each item" case.