skip to content

A PowerShell function's last line is `return $result`, yet the caller receives several extra objects as well. Why does a PowerShell function emit more than the value named by `return`, and how do you stop it?

level: middleimportance: should knowfreq 50%

answer

  1. a function does not have one return value
  2. uncaptured expression values leave the scope
  3. return is control flow, not a filter
  4. the .Add() index has to go somewhere
  5. $null =, [void], Out-Null

basics

~20 s

In PowerShell every expression whose value is not captured, assigned or redirected goes to the function's output stream. return only exits the scope and adds its argument to what was already emitted. Discard unwanted values with $null = expression, void, or | Out-Null.

solid answer

~50 s

PowerShell functions do not have a single return value; they have an output *stream*. Any statement that produces a value and does not assign it, cast it away or redirect it writes that value to the caller. `return $result` is not "the answer" — it means "emit `$result` and leave", and everything emitted earlier in the body is already on its way out. The usual culprits are method calls that return something you did not want: `$list.Add($x)` on an `ArrayList` returns the new index, `New-Item` returns the object it created, `$sb.AppendLine(...)` returns the builder itself. Suppress them with `$null = $list.Add($x)` — the cheapest form — or `[void]$list.Add($x)`, or `| Out-Null`, which is the slowest because it sets up a pipeline. This is also why deliberate, disciplined output matters: a function that leaks stray objects breaks every caller that pipes it somewhere.

code

powershell · 8 lines
powershell
function Get-Summary {
    $rows = [System.Collections.ArrayList]::new()
    $rows.Add('row1')          # .Add() returns 0 - it leaks to the caller
    $null = $rows.Add('row2')  # index discarded
    return $rows.Count
}

@(Get-Summary).Count   # 2 objects came back, not 1

go deeper

for a junior

Know that anything a PowerShell function evaluates and does not store is sent back to the caller, and that $null = or [void] is how you throw an unwanted value away.

for a middle

Explain the output stream model: return only exits the scope and adds its argument, and name the classic leak sources such as ArrayList.Add returning an index.

for a senior

Treat the output stream as the function's contract — audit for stray emissions, prefer $null = in loops for cost, and handle the unrolling of single-element collections at both ends.

for a principal

Push for an explicit output convention across shared tooling: one documented object type per function, declared with [OutputType], so downstream automation can rely on shape instead of defensive filtering.

## There is no return value; there is an output stream The single most useful mental model correction for someone coming from C#, Java or Python is this: a PowerShell function does not return *a* value. It writes zero or more objects to the **success output stream**, and the caller receives all of them. If the caller assigns the call to a variable, the variable holds one object when exactly one was emitted and an array when several were. The rule that produces the surprise is simple and absolute: **an expression statement whose value is not consumed is output.** Consumed means assigned to a variable, cast to `[void]`, redirected, or piped into something that swallows it. Anything else goes to the caller. ```powershell function Get-Summary { $rows = [System.Collections.ArrayList]::new() $rows.Add('row1') # returns 0 - leaks into the output stream $null = $rows.Add('row2') # returns 1 - discarded return $rows.Count } Get-Summary # emits 0, then 2 ``` The caller asked for a count and got two objects. Nothing errored; the value is simply wrong in a way that only shows up downstream. ## What `return` actually means `return $x` in PowerShell is shorthand for "write `$x` to the output stream, then exit this scope". It is a **control-flow** statement first and an output statement second. It does not filter, cancel or replace anything already emitted, and `return` with no argument emits nothing and just exits. So a function with three stray expressions and a `return` emits four objects. This also means `return` is often optional. Many idiomatic PowerShell functions end with a bare expression — the value flows out on its own. Using `return` for early exit is good practice for readability; expecting it to define the function's entire output is the error. ## The usual leak sources - **.NET methods with a return value.** `ArrayList.Add()` returns the index of the added element. `StringBuilder.Append()` and `AppendLine()` return the builder for chaining. `HashSet.Add()` returns a boolean. In C# you ignore these for free; in PowerShell ignoring them means emitting them. - **Cmdlets that emit by design.** `New-Item`, `New-Object`, `Copy-Item -PassThru`, `Start-Process -PassThru` all return objects. If you called them for their side effect, you must discard the result. - **Assignment used as an expression in parentheses.** `($x = 5)` emits 5, whereas `$x = 5` does not. ## The four ways to discard 1. `$null = expression` — assignment to `$null`. Fastest, works everywhere, and reads as intent once you have seen it a few times. 2. `[void]expression` — a cast that throws the value away. Equally fast in practice; the form C# refugees find most natural. 3. `expression | Out-Null` — a real pipeline into a cmdlet that discards everything. Functionally identical but the slowest of the four, because it constructs a pipeline for every call. Inside a tight loop that cost is measurable. 4. `expression > $null` — redirection of the success stream. Works, but reads as file redirection and is easy to confuse with the other streams' redirection operators. Prefer `$null =` in loops, and be consistent across a codebase so reviewers can spot a *missing* suppression. ## The unrolling gotcha on the way out One more behaviour lives at the same boundary. When a function emits a collection, PowerShell **unrolls** it: each element is written to the stream separately. The caller reassembles them into an array, so most of the time this is invisible. But it means a returned array of one element arrives as a bare scalar, and a returned empty array arrives as nothing at all. Defensive callers write `$result = @(Get-Things)` to force an array regardless. On the emitting side, if you genuinely need the collection to arrive as one object — say you are returning an array of arrays — use the unary comma to wrap it (`return ,$array`) or `Write-Output -NoEnumerate $array`. ## Why this matters beyond neatness Stray output is not a cosmetic problem. A function whose output stream carries junk cannot be piped anywhere: the downstream command sees objects of the wrong type, `Select-Object` picks a leaked integer instead of your object, an export writes a nonsense column. Because the extras appear only in some code paths, the failure is intermittent and lands far from the cause. Treat a function's output stream as its public contract, and suppress everything you did not deliberately choose to emit.

  • Which suppression form would you use inside a tight loop, and why?
    `$null = expression`. It is a plain assignment, so it costs nothing beyond evaluating the expression. `| Out-Null` builds a real pipeline for every iteration, which is measurably slower at high counts. `[void]` is equally cheap and mostly a style choice. Consistency matters more than the micro-difference: pick one so a missing suppression stands out in review.
  • What happens when a PowerShell function emits an array of exactly one element?
    The array is unrolled onto the output stream, so the caller receives a single bare object, not a one-element array. Code that then calls `.Count` or indexes `[0]` behaves differently than on multi-element days. Callers guard with `@(...)`; emitters that truly need the collection kept whole use `,$array` or `Write-Output -NoEnumerate`.
  • Is `return` ever necessary in a PowerShell function?
    Not for producing output — a bare expression is emitted just as well. It is worth using for early exit, where it makes the control flow explicit and avoids nesting the rest of the body in an else branch. Using it as the final statement is a readability choice; expecting it to define the function's entire output is the misconception.

saying these in an interview costs you the question

  • Thinks return determines everything the function emits
  • Assumes ignoring a method's return value is free, as in C#
  • Uses Out-Null everywhere including hot loops
  • Believes a returned one-element array stays an array
  • Says stray output is harmless because callers only read the last value

context