ForEach versus ForEach-Object

The ForEach-Object cmdlet performs an operation on each item in an array of input objects. There are two methods to implement the ForEach command;

1. $Range | ForEach-Object {}
2. ForEach ($Number in $Range) {}

Both methods perform the same actions with the same outcome, but they do their job in a different way. In this blog, I’ll explain the differences between these methods.

The numbers
Let’s start with crunching some numbers. This tiny script will display all numbers from 1 till 1.000.000. We can measure the runtime with the cmdlet Measure-Command.

Let’s start with ForEach-Object.

$Range = 1..1000000
Measure-Command {$Range | ForEach-Object {$_}}

This command gives us the following output:

The ForEach-Object command took 2.878 seconds to complete. Let’s do the same action with the ForEach command:

$Range = 1..1000000
Measure-Command {ForEach ($Number in $Range) {$Number}}

This command comes with the following result:

The ForEach command lasted only 0.565 seconds.

What is the cause of the difference in performance?
To determine this, we first need to understand how both cmdlets work.

The ForEach-Object method performs the following steps:
1. Get the next value in the queue.
2. Perform all actions defined in the action list.

The ForEach method performs the following steps:
1. Load the full array ($Range) into the system memory.
2. Get the next value in the queue.
3. Perform all actions defined in the action list.

Now that we defined the steps each method takes, we can clearly see where the runtime difference between both methods is coming from. ForEach first loads the array into the system memory, while ForEach-Object treats the array as a stream and starts crushing commands right away. Since working with memory is way faster than working with a stream, the ForEach cmdlet will be able to complete its task quicker.