Chapter 06Lesson 04~125 minutes

for, while, do/while, do/until, break, and continue

Select loop syntax from the repetition rule, use counters only when meaningful, build bounded polling/retry workflows, and apply break/continue and labels without creating runaway automation.

Learning objectives

  • Choose foreach, for, while, do/while, or do/until based on what controls repetition.
  • Use for loops when a counter/index is meaningful without importing unnecessary C-style habits.
  • Build while-based polling with explicit attempt/time bounds so it cannot run forever.
  • Explain the postcondition behavior of do/while and do/until and preserve safety limits in both.
  • Use break and continue predictably inside language loops and understand labeled flow as an advanced nested-loop tool.
  • Implement a deterministic bounded retry lab with backoff, history objects, and explicit failure.

1. Choose a loop by what controls repetition

Different loop forms answer different questions. Do not start from “which keyword do I remember?” Start from the repetition rule: are you visiting every item, counting positions, repeating while a condition remains true, or guaranteeing one attempt before testing a condition?

Control questionUsually clearest formExample
Every item in a collection?foreachValidate every deployment target.
Counter/index controls repetition?forRun rollout waves 1 through 3.
Check condition before every attempt?whilePoll until ready or timeout.
Run once, then continue while condition is true?do/whileAttempt an operation, then decide whether to repeat.
Run once, then continue until condition becomes true?do/untilRetry until success or safety limit.

2. Use for when a counter or position is part of the problem

A for statement has initialization, condition, and repeat expressions. It is useful when an index itself matters. If you simply need every element, foreach is normally clearer.

$waves = 'canary', 'small', 'full'

for ($i = 0; $i -lt $waves.Count; $i++) {
    [pscustomobject]@{
        WaveNumber = $i + 1
        Name = $waves[$i]
    }
}

The index is meaningful because the output includes a wave number. A foreach loop would be simpler if the index were not needed.

3. break exits a loop; continue skips the rest of the current iteration

These keywords are predictable in language loops. break transfers control to the statement after the loop. continue goes to the next iteration of the innermost loop.

foreach ($target in 'api-01', 'skip-me', 'worker-01', 'stop-here', 'cache-01') {
    if ($target -eq 'skip-me') { continue }
    if ($target -eq 'stop-here') { break }
    "Process $target"
}
Process api-01
Process worker-01

4. while is appropriate for polling only when there is an explicit escape condition

Polling is common in DevOps automation: wait for a service, job, file, or deployment state. An unbounded while ($true) can run forever. Production-style polling needs a deadline or maximum attempt count, a delay, and a clear failure result.

$attempt = 0
$maxAttempts = 5
$ready = $false

while (-not $ready -and $attempt -lt $maxAttempts) {
    $attempt++
    "Probe attempt $attempt"

    # Simulated readiness for a deterministic lab.
    $ready = $attempt -ge 3

    if (-not $ready) {
        Start-Sleep -Milliseconds 200
    }
}

if (-not $ready) {
    throw "Resource did not become ready after $maxAttempts attempts."
}

The loop cannot run forever because $attempt -lt $maxAttempts is part of the condition. A real probe could call an API or test a socket later in the course.

5. Backoff makes repeated polling less aggressive

A backoff increases the wait between repeated attempts. It reduces pressure on an overloaded dependency and avoids tight retry loops. Keep the maximum delay bounded.

$delayMs = 200
$maxDelayMs = 1600

for ($attempt = 1; $attempt -le 5; $attempt++) {
    "Attempt $attempt; next delay is $delayMs ms"

    # Simulate failure until attempt 4.
    $succeeded = $attempt -ge 4
    if ($succeeded) { break }

    Start-Sleep -Milliseconds $delayMs
    $delayMs = [math]::Min($delayMs * 2, $maxDelayMs)
}

Chapter 13 will add HTTP-specific retry concerns such as status codes and rate limits. Here the lesson is bounded repetition.

6. do/while always runs the body once, then repeats while the condition is true

Use do/while when the first attempt must happen before there is any result to test.

$attempt = 0
$keepTrying = $true

do {
    $attempt++
    "Attempt $attempt"
    $keepTrying = $attempt -lt 3
} while ($keepTrying)

The body runs before the first condition test. That is the defining difference from while.

7. do/until repeats while the condition is false and stops when it becomes true

The English reading is useful: “do this until success.” Because the condition is tested after the body, the body also runs at least once.

$attempt = 0

do {
    $attempt++
    $succeeded = $attempt -ge 3
    "Attempt $attempt; succeeded=$succeeded"
} until ($succeeded -or $attempt -ge 5)

The safety limit remains in the stop condition. “Until success” without a second boundary can still loop forever if success never occurs.

8. Labels are for nested-loop exits, not everyday control flow

A statement label is a name attached to an iteration statement. A labeled break or continue can target an outer loop. Learn ordinary loop flow first; labels are useful only when nested loops genuinely need non-local control.

:environmentLoop foreach ($environment in 'dev', 'staging', 'production') {
    foreach ($target in 'api-01', 'worker-01') {
        if ($environment -eq 'production' -and $target -eq 'worker-01') {
            break environmentLoop
        }

        "$environment -> $target"
    }
}

This exits the labeled outer loop completely when the condition is reached. If labeled flow becomes frequent, consider refactoring the logic into a function with clearer boundaries.

9. A production polling pattern separates probe, timing, and final decision

Readable polling code keeps three concerns distinct: how readiness is checked, how long to wait, and what happens after the bound is exhausted. Even before functions are fully taught, you can represent the probe as a script block; Lesson 05 explains that abstraction.

$probe = {
    param($Attempt)
    # Deterministic training probe.
    $Attempt -ge 3
}

$deadline = (Get-Date).AddSeconds(5)
$attempt = 0
$ready = $false

while (-not $ready -and (Get-Date) -lt $deadline) {
    $attempt++
    $ready = & $probe $attempt
    if (-not $ready) { Start-Sleep -Milliseconds 250 }
}

[pscustomobject]@{
    Ready = $ready
    Attempts = $attempt
    FinishedAt = Get-Date
}

The deadline prevents an infinite loop even if the probe never succeeds.

10. Lab: bounded retry with maximum attempts, timeout, and backoff

This lab cannot run forever. It simulates a dependency that becomes ready on the fourth probe and records each attempt as a structured object.

$maxAttempts = 6
$deadline = (Get-Date).AddSeconds(8)
$delayMs = 150
$maxDelayMs = 800
$history = [System.Collections.Generic.List[object]]::new()
$ready = $false

for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
    if ((Get-Date) -ge $deadline) {
        break
    }

    # Deterministic training probe: success on attempt 4.
    $ready = $attempt -ge 4

    $history.Add([pscustomobject]@{
        Attempt = $attempt
        Ready = $ready
        DelayMs = if ($ready) { 0 } else { $delayMs }
        CheckedAt = Get-Date
    })

    if ($ready) {
        break
    }

    Start-Sleep -Milliseconds $delayMs
    $delayMs = [math]::Min($delayMs * 2, $maxDelayMs)
}

$history | Format-Table Attempt, Ready, DelayMs, CheckedAt -AutoSize

if (-not $ready) {
    throw "Dependency not ready after $($history.Count) recorded attempts."
}

Change the simulated success rule to $false and verify that the maximum attempts/deadline terminate the loop. The failure becomes explicit rather than an infinite wait.

Verification checklist

11. Common mistakes to avoid

while ($true) with no deadline or break condition. Operational automation must have a bounded failure path.

Using for for every collection. If the counter has no meaning, foreach is usually clearer.

Retrying immediately in a tight loop. Add delay/backoff and make total time bounded.

Using labels to compensate for tangled nesting. Labels are valid, but frequent labeled jumps often signal a missing function or simpler decomposition.

12. Knowledge check

Question 1. When is for usually preferable to foreach?

Question 2. What must a production polling loop include?

Question 3. What is the defining difference between while and do/while?

Question 4. How does do/until decide to stop?

Question 5. What does a labeled break do?

13. Summary

Choose loop syntax from the repetition rule. foreach fits collection iteration; for fits meaningful counters and indexes; while fits precondition-controlled repetition; do/while and do/until guarantee an initial attempt. In operations code, polling and retries must be bounded by attempts or time, include delay/backoff, and produce an explicit final result.

14. Further reading

Next lesson

Treat behavior itself as data with script blocks and delayed execution

Lesson 05 connects every script block seen so far to the ScriptBlock object model, invocation operators, callbacks, closures, jobs/remoting boundaries, and code-injection safety.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.