Last Updated: May 17, 2026
Some loops don't have a clean iteration count up front. You're polling a queue until it's empty, retrying a flaky call until it succeeds, or asking a shopper to enter a quantity until they type a positive number. That's where while and do-while come in. This lesson covers both forms, when to pick one over the other, and the small set of patterns that turn up in real code.
while LoopA while loop runs its body as long as a condition is true. The condition is checked first, before the body runs even once. If the condition starts out false, the body is skipped entirely.
The shape:
A first concrete example. You're draining a notification queue of pending shipment alerts:
The loop has no counter. It just keeps pulling messages off the queue until there are none left. That's the natural fit for while: the work itself decides when the loop ends, not a pre-computed count.
while LoopThe condition is the gate. The body only runs when the gate is open, and after each pass the gate is checked again. If the condition is false on the very first check, the body never runs and execution jumps straight to whatever follows the loop.
The condition can be anything that produces a bool: a comparison, a method call, a flag, or a combination. Each of these is valid:
The third form, looping on a flag, is common when the decision to stop is buried inside the body. We'll see this pattern again in the retry section.
Forgetting to update the loop variable is the most common bug with while. The loop runs forever, the program hangs, and you reach for the kill switch:
The condition never changes, so it stays true forever. The fix is obvious once you spot it: decrement countdown inside the body. The reason it bites people is that the variable update lives in a different place than the condition check, so it's easy to forget when you're focused on the body's work.
Modern editors usually warn you when a loop has no path to falsify its condition, but only in obvious cases. If the update sits behind an if, the warning disappears even when the bug is still there:
When a while loop hangs, the first thing to check is whether the variable in the condition is actually being mutated on every path through the body.
while (true)Sometimes you actually want a loop with no fixed end. A console app sitting in a menu, a server polling for jobs, an input prompt that retries until the input is valid. The idiomatic C# pattern is while (true) plus an explicit break when it's time to exit.
while (true) looks like the world's worst idea on paper, but it reads cleanly when the exit condition naturally belongs inside the body. Two reasons people prefer it over a flag variable:
while (true) says "loop forever unless something inside decides otherwise," which is exactly what's happening.Use while (true) when there's a clear exit point inside the body. Use a condition expression when the loop ends based on data the loop is processing, like draining a queue or counting down.
Cost: while (true) itself is free. The danger is forgetting the break. If every path through the body skips the exit, you have an infinite loop with no warning from the compiler.
A classic real-world use of while is asking the user for input until they give you something valid. The shape is the same every time:
For an interactive run, this loop keeps prompting until the user types something that parses as a positive integer. Note that quantity is initialized to -1 so the condition is true on the first pass. That's a small tax while charges you for checking the condition first: you have to seed the variable with a value that will enter the loop.
That seeding step is mildly awkward. The next form, do-while, exists partly to fix it.
do-while LoopA do-while loop runs its body first, then checks the condition. If the condition is true, it runs the body again. The body always runs at least once, even when the condition is false to start.
The shape:
Note the semicolon after while (condition);. That's not optional, it's part of the syntax. Forgetting it is a compile error, CS1003: Syntax error, ';' expected.
The same input loop with do-while:
The quantity variable doesn't need a seed value. The body runs unconditionally, so by the time the condition is checked, quantity has been set inside the loop. The code reads more naturally: "do this, and keep doing it while the result isn't good enough."
do-while LoopThe diagram makes the difference visual. The body sits before the check, so the first pass through the body happens no matter what. The condition only gets a vote on whether to keep going.
do-while ExampleFor a runnable example without live input, simulate the user typing values from an array:
The loop runs four times. The first three inputs fail validation and the body prints the error. The fourth input is 3, the validation passes, quantity is now 3, the condition quantity <= 0 is false, and the loop exits.
The most common syntactic mistake with do-while is leaving off the semicolon after the closing while. Unlike while and for, where the parentheses and braces tell the compiler the statement is complete, do-while ends with the condition, and the condition expression needs a terminator:
Add the semicolon and it compiles:
Once you've made the mistake twice, you'll never make it again. But the first time you see CS1002 after a do-while, the trailing semicolon is the thing to look for.
Both forms can be made to do the same job. The question is which one fits the situation more naturally. The rule of thumb: ask whether the body should always run at least once.
| Question | Pick |
|---|---|
| Does the body need to run at least once, even if the condition starts false? | do-while |
| Is it valid to skip the loop entirely when there's nothing to do? | while |
| Are you draining a collection, polling, or processing until empty? | while |
| Are you prompting for input and need to ask before you can check? | do-while |
| Is the exit condition discovered inside the body? | while (true) with break |
A side-by-side example makes the choice obvious. Draining a queue with do-while requires an extra check, because the queue might already be empty when the loop starts:
The while version doesn't need the extra check, because the condition is evaluated before the first iteration:
Pick while when "zero iterations" is a sensible outcome. Pick do-while when the body's first run is what produces the value the condition checks.
for and foreachThe for loop and foreach loop cover the other common shapes. Here's the side-by-side comparison so the choice is clear.
| Form | When to use | Body runs at least once? | Counter required? |
|---|---|---|---|
for | Fixed number of iterations known up front (e.g. process 10 items) | No | Yes (typically) |
foreach | Iterating every element of a collection | No | No (managed by enumerator) |
while | Loop continues while a condition holds, count unknown | No | No |
do-while | Same as while, but must run at least once | Yes | No |
The pattern that tells you to pick while over for is "I don't know how many times this will run." Maybe the queue has 3 items, maybe 3,000, maybe none. for wants a count. while is happy without one.
The pattern that tells you to pick do-while over while is "I have to do the work to know whether I'm done." That's input prompts, retries, and any situation where the condition depends on a value computed inside the body.
Network calls fail. Databases get busy. A robust client retries a few times before giving up. This is one of the most common real uses of while, and the structure looks the same in every codebase that does it:
The condition !success && attempt < maxAttempts ends the loop in either of two ways: the call succeeds, or the attempt count hits the cap. The two-part exit guard is what keeps the loop bounded. Without attempt < maxAttempts, a permanently broken service would loop forever.
Production code usually adds a delay between retries, often with exponential backoff: 100 ms, 200 ms, 400 ms, and so on. The loop structure stays the same, only the body grows.
Cost: Retrying without a cap is the classic way to turn a transient failure into an outage. Always pair "retry until success" with a max attempt count or a deadline.
When a search returns more results than fit in one batch, the API hands them back one page at a time, along with a token or a count that tells you whether there's more. while is the natural fit because you don't know how many pages exist up front.
The outer loop is a while, the inner loop is a for. That's a common shape: while controls "are there more pages," and for walks the items inside the page. You couldn't easily write the outer loop as a for, because the total page count isn't known until you've fetched the last one.
A sentinel is a special value that means "stop." It might be null, 0, "", or whatever your data source uses to mark the end of a stream. while reads the next item, checks for the sentinel, and either processes it or exits.
The trick with sentinel loops is the "prime the pump" step: you read the first value before the loop, then read each subsequent value at the end of the body. The condition checks the most recently read value. If you forget the priming read, you check a variable that hasn't been assigned yet.
An alternative shape uses while (true) and a break:
Both work. The while (true) version is shorter and avoids the priming read. Pick whichever reads more clearly for the data flow at hand.
Some loops watch for an event rather than process data. You might be waiting for an order to leave the "preparing" state, or for a long-running export job to finish. The basic shape is the same:
This is a good candidate for do-while because the first check has to happen before the loop can decide whether to keep going. The second clause in the condition, checks < orderStates.Length, is a safety net so the loop doesn't run off the end of the array if the state never changes.
Real polling loops also need a delay between checks so they don't burn CPU. The Async Programming section covers that with await Task.Delay(...), which is what you'd use in production.
Three mistakes show up over and over with these loops.
1. Forgetting to update the loop variable. The condition stays true forever, the program hangs. The fix is to make sure every path through the body either updates the variable or hits a break.
2. Off-by-one with `do-while`. Because the body runs once before the check, a do-while body that already counts the first item will overshoot. This program prints 1 2 3 4 5 6, not 1 2 3 4 5:
If the intent was to print 1 through 5, either switch to while (n < 5) after pre-incrementing, or use a for loop where the bounds are explicit. The takeaway: when the body might overshoot the boundary you care about, while (with the check first) is usually less error-prone.
3. Missing the semicolon after `do-while`. The compiler reports CS1002: ; expected. The fix is to add ; after the closing while (...). This is the only loop in C# whose closing parentheses need a terminator.
A small end-to-end example that uses both forms. A customer is checking out. The system prompts for a quantity, validates it (using do-while because the first prompt has to happen no matter what), and then attempts to charge the card up to three times (using while because zero attempts is never the right answer in this state).
Both loops do their job. The do-while keeps asking for input until a valid quantity arrives. The while keeps retrying the charge until one succeeds or three attempts have failed. Neither knows how many iterations it will need when it starts, which is exactly when you reach for these forms.
while checks its condition first, so the body might run zero times. do-while runs the body first and then checks, so the body always runs at least once.while when zero iterations is a valid outcome (draining queues, processing items, paging). Pick do-while when the condition depends on a value computed inside the body (input prompts, polling).while (true) with a break is the idiomatic shape when the exit decision lives naturally inside the body.while loop with a condition that never goes false.do { ... } while (condition);. It's the only loop whose syntax requires a trailing semicolon.for loop is usually clearer. When you're walking every element of a collection, foreach is cleaner still.