Skip to content

PowerShell Loops

How to use PowerShell's loop constructs (while, for, foreach, ForEach-Object, do-while, do-until) for iterating collections, handling unknown durations, and optimizing memory usage in scripts.

A loop is a control flow structure that repeatedly executes a block of code as long as a specified condition evaluates to $true, or once for each item in a collection. PowerShell provides several loop constructs, each suited to different scenarios: traditional conditional loops, index-driven iterations, statement-based enumerators, and streaming pipeline cmdlets. These allow you to balance memory consumption and execution speed.

Overview of Loop Constructs

PowerShell categorizes loops into two broad families: condition-driven loops (which repeat based on a boolean expression) and collection-driven loops (which iterate across arrays, lists, or pipeline streams).

Loop Construct Type Primary Evaluation Rule Syntax Summary Best For
while Condition-driven Evaluates condition before entering the loop body. while (<condition>) { ... } Repeating while a condition remains $true, when the number of iterations isn't known in advance
do { } while () Condition-driven Executes body at least once, repeating while condition is $true. do { ... } while (<condition>) Same as while, but guarantees the block runs at least once
do { } until () Condition-driven Executes body at least once, repeating until condition becomes $true. do { ... } until (<condition>) Repeats until a condition becomes $true (inverse of while)
for Index-driven Uses an initializer, conditional test, and incrementor. for ($i=0; $i -lt $max; $i++) { ... } Repeating a known number of times, with full control over the counter
foreach Collection-driven Statement that loads all items into memory and iterates sequentially. foreach ($item in $collection) { ... } Iterating over each item in an array, collection, or range
ForEach-Object Pipeline-driven Cmdlet that streams items one at a time through the pipeline. $collection | ForEach-Object { ... } Iterating over pipeline input, especially in cmdlet chains
loop-preview.ps1powershell
$count = 0
while ($count -lt 3) {
    Write-Output "while: $count"
    $count++
}

foreach ($num in 1..3) {
    Write-Output "foreach: $num"
}

1..3 | ForEach-Object { Write-Output "ForEach-Object: $_" }
Consolepowershell
./loop-preview.ps1
Outputplaintext
while: 0
while: 1
while: 2
foreach: 1
foreach: 2
foreach: 3
ForEach-Object: 1
ForEach-Object: 2
ForEach-Object: 3

When to Use Which Loop

Selecting the right loop depends on what you are iterating over, whether the number of iterations is known, and how the data is being processed.

Iterating Over a Known Collection

When working with a known collection such as arrays, files, or objects, choose the loop based on memory requirements:

  • Use ForEach-Object when memory efficiency is important. It processes items one at a time through the pipeline rather than requiring the entire collection to be held in memory.
  • Use the foreach statement when the collection is already available in memory and memory constraints are not a major concern.

Iterating for an Unknown Duration

When the number of iterations depends on a dynamic condition or an external system state, use a condition-based loop:

  • Use while when the condition should be checked before each iteration and the loop may execute zero or more times.
  • Use do-while when the loop must execute at least once and should continue while the condition remains true.
  • Use do-until when the loop must execute at least once and should continue until the specified condition becomes true.

In short, use foreach or ForEach-Object for collections, and use while, do-while, or do-until when execution depends on a condition that may change over time.

Use while When...

You need to repeat a block of code as long as a condition holds true, and you don't know in advance how many iterations will be needed, such as waiting for a service to start or a file to appear. Only execute the block if the condition is already $true before starting (e.g., polling an external queue that might already be empty).

See While Loop

Use do-while / do-until When...

You need the loop body to execute at least once, regardless of the condition. This is useful for scenarios like displaying a menu at least one time before checking whether the user wants to exit, or prompting a user for input and then validating their response.

  • Use do-while when the block must execute at least once before evaluating the condition.
  • Use do-until when it is more natural to express the exit criteria as "stop when this event occurs" rather than "continue while this state persists".

See Do-While Loop

Use for When...

You know the exact number of iterations in advance and need full control over the counter variable. For example, you may need to loop a fixed number of times, step by increments other than 1 (e.g., $i += 2), access multiple arrays at the same offset ($listA[$i] and $listB[$i]), or iterate backwards from an array length to zero.

See For Loop

Use foreach When...

You need to process every item in a known collection (an array, a hashtable, or a range) and you already have that collection stored in a variable.

See Foreach Loop

Use ForEach-Object When...

You're working within a pipeline, processing objects as they flow from one cmdlet to another, especially with large or streamed data where holding the entire collection in memory isn't ideal.

See ForEach-Object

foreach Statement vs. ForEach-Object Cmdlet

The most common decision in PowerShell scripting is choosing between the foreach statement and the ForEach-Object pipeline cmdlet:

  • Use the foreach statement (foreach ($item in $collection)) when:
    • Raw execution speed is the top priority.
    • The dataset fits easily into local system RAM.
    • You need to use loop-control keywords like break and continue with predictable scoping.
  • Use ForEach-Object ($stream | ForEach-Object { }) when:
    • You are processing very large datasets (e.g., millions of log lines or Active Directory user accounts) where loading everything into memory upfront would exhaust RAM.
    • You want to begin processing output immediately as upstream cmdlets discover items, rather than waiting for the entire query to complete.
    • You are chaining operations inside a continuous pipeline.

Quick Decision Table

Scenario Recommended Loop
Repeat until a condition changes, unknown iteration count while
Must run at least once before checking a condition do-while / do-until
Fixed number of iterations with counter control for
Loop through an existing array or collection foreach
Process pipeline output from another cmdlet ForEach-Object
Loop through a numeric sequence foreach with .. range operator
Loop through command-line arguments foreach over $args

Practical Example: Comparing Loop Implementations

The following script demonstrates three different loop styles (foreach, for, and do-while) to process a list of target hostnames and simulate network status polling:

compare-loop-patterns.ps1powershell
$ClusterNodes = @("web-01", "web-02", "sql-01")

Write-Output "=== 1. Collection Enumeration via foreach ==="
foreach ($node in $ClusterNodes) {
    Write-Output "Processing node: $node"
}

Write-Output "`n=== 2. Index-Driven Iteration via for ==="
for ($i = 0; $i -lt $ClusterNodes.Count; $i++) {
    Write-Output "Node index [$i]: $($ClusterNodes[$i])"
}

Write-Output "`n=== 3. Polling State via do-while ==="
$pollCount = 0
$targetNode = "sql-01"
$isReady = $false

do {
    $pollCount++
    Write-Output "Poll attempt #${pollCount}: Checking status of $targetNode..."
    
    if ($pollCount -ge 2) {
        $isReady = $true
    }
} while (-not $isReady -and $pollCount -lt 5)

[PSCustomObject]@{
    TargetNode   = $targetNode
    PollAttempts = $pollCount
    IsReady      = $isReady
}

where:

  • foreach ($node in $ClusterNodes) iterates through each string element sequentially.
  • for ($i = 0; $i -lt $ClusterNodes.Count; $i++) tracks the array offset explicitly using $i.
  • do { ... } while (-not $isReady ...) executes the status check at least once, repeating until the boolean condition flips or retries are exhausted.

Execute the script from the console:

Consolepowershell
pwsh ./compare-loop-patterns.ps1
Outputplaintext
=== 1. Collection Enumeration via foreach ===
Processing node: web-01
Processing node: web-02
Processing node: sql-01

=== 2. Index-Driven Iteration via for ===
Node index [0]: web-01
Node index [1]: web-02
Node index [2]: sql-01

=== 3. Polling State via do-while ===
Poll attempt #1: Checking status of sql-01...
Poll attempt #2: Checking status of sql-01...

TargetNode PollAttempts IsReady
---------- ------------ -------
sql-01                2    True
Navigation