Scope surprises cause silent bugs, and a few habits make scripts orders of magnitude faster on large data.
Each script, function and script block runs in its own scope. A child scope can read variables from its parents, but assigning creates a new local variable that hides the parent's one. To change an outer variable, use a scope modifier: $script:count (the script's top level) or $global:count (the session). $private:x hides a variable from child scopes. Modules have their own isolated session state, so their internals do not leak into yours. Dot-sourcing runs a script in the caller's scope instead of a new one.
Usually it is better to return values than to modify outer variables; scope modifiers are best kept for module-level state.
Performance habits that matter on large inputs:
- Do not grow arrays with
+=: assign the output of the loop ($r = foreach (...) { ... }) or useList[T]. - The
foreachstatement and the.Where()/.ForEach()methods are much faster thanForEach-ObjectandWhere-Objectfor data already in memory; the cmdlets shine for streaming. - Filter left with cmdlet parameters, and discard output with
$null =rather than| Out-Nullin hot loops. - Build strings with
-joinor a StringBuilder, not repeated+=.
Always measure with Measure-Command { ... } before and after an optimisation.
Example
$count = 0
function Bump { $count++; "inside: $count" } # creates a LOCAL copy
Bump; "outside: $count" # inside: 1 / outside: 0
function BumpScript { $script:count++ }
BumpScript; "outside: $count" # outside: 1
$n = 20000
$slow = Measure-Command { $a = @(); foreach ($i in 1..$n) { $a += $i } }
$fast = Measure-Command { $a = foreach ($i in 1..$n) { $i } }
$slow.TotalMilliseconds -gt $fast.TotalMilliseconds * 10 # True
$cmdlet = Measure-Command { 1..$n | ForEach-Object { $_ * 2 } }
$stmt = Measure-Command { foreach ($i in 1..$n) { $i * 2 } }
$cmdlet.TotalMilliseconds -gt $stmt.TotalMilliseconds # True
(1..$n).Where({ $_ % 2 -eq 0 }, 'First', 3) -join ',' # 2,4,6 (stops early)Remember
- Assignment creates a local variable; use $script: or $global: deliberately
- Capture loop output instead of +=; prefer foreach and .Where() in memory
- Measure-Command before and after optimising
Watch out for
- Expecting a function's
$total += 1to update the script's $total.