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 |
$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: $_" }./loop-preview.ps1while: 0
while: 1
while: 2
foreach: 1
foreach: 2
foreach: 3
ForEach-Object: 1
ForEach-Object: 2
ForEach-Object: 3When 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-Objectwhen 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
foreachstatement 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
whilewhen the condition should be checked before each iteration and the loop may execute zero or more times. - Use
do-whilewhen the loop must execute at least once and should continue while the condition remains true. - Use
do-untilwhen 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-whilewhen the block must execute at least once before evaluating the condition. - Use
do-untilwhen 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
foreachstatement (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
breakandcontinuewith 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:
$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:
pwsh ./compare-loop-patterns.ps1=== 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