PowerShell Array vs ArrayList vs List: Differences, Speed and Conversion

The PowerShell array vs ArrayList difference is size: an array is fixed, while an ArrayList grows. For new scripts, skip ArrayList and use a generic List. It grows too, stays quiet when you add items, and holds one type:

$array = @('web01', 'web02')
$list  = [System.Collections.Generic.List[string]]::new()
$list.Add('web01')
$list.Add('web02')

'Array: fixed size = {0}, type = {1}' -f $array.IsFixedSize, $array.GetType().Name
'List:  fixed size = {0}, type = {1}' -f $list.IsFixedSize, $list.GetType().Name

Output:

Array: fixed size = True, type = Object[]
List:  fixed size = False, type = List`1

Below I compare all three, measure the speed of +=, and show how to convert between them. I ran every example in PowerShell 7.6 and Windows PowerShell 5.1.

Arrays have a fixed size

An array’s size is set when it’s created. Its Add method always fails, and += works by building a brand-new array with the extra item:

$servers = @('web01', 'web02')

try { $servers.Add('sql01') }
catch { "Failed. $($_.Exception.InnerException.Message)" }

$before = $servers
$servers += 'sql01'
"After +=, count is $($servers.Count). Same array as before? $([object]::ReferenceEquals($before, $servers))"

Output:

Failed. Collection was of a fixed size.
After +=, count is 3. Same array as before? False
PowerShell array Add fails because the array has a fixed size
Add fails on an array, and += returns a new array (PowerShell 7)

For a handful of items, += is fine. Adding items to an array covers += and its alternatives in detail.

ArrayList: growable, but noisy

System.Collections.ArrayList has a real Add method. It also returns the index of the new item, which ends up in your output or a function’s return value:

$servers = [System.Collections.ArrayList]::new()
$servers.Add('web01')
$servers.Add('web02')
'--- Silenced with [void] ---'
[void]$servers.Add('sql01')
"Count: $($servers.Count)"

Output:

0
1
--- Silenced with [void] ---
Count: 3
PowerShell ArrayList Add prints the index of each new item
Each Add prints 0, then 1, until it’s cast to [void] (Windows PowerShell 5.1)

Cast each call to [void] or assign it to $null. Microsoft’s ArrayList documentation also says not to use ArrayList for new development and recommends the generic List instead.

List[T]: the one to use

A generic List works like an ArrayList, but Add returns nothing. It also has Insert, Remove, RemoveAt, Contains and AddRange:

$servers = [System.Collections.Generic.List[string]]::new()
$servers.Add('web01')
$servers.Add('web02')
$servers.AddRange([string[]]@('sql01', 'sql02'))
$servers.Insert(0, 'dc01')
[void]$servers.Remove('web02')
$servers.RemoveAt($servers.Count - 1)

"Count: $($servers.Count)"
"Items: $($servers -join ', ')"
"Contains sql01? $($servers.Contains('sql01'))"

Output:

Count: 3
Items: dc01, web01, sql01
Contains sql01? True

Remove deletes the first matching item, and RemoveAt deletes by position. Arrays have neither, so removing items from an array means building a new one.

A List holds one type

The type in brackets is enforced. PowerShell converts values when it can, like the text ‘8080’ to a number, and refuses when it can’t:

$ports = [System.Collections.Generic.List[int]]::new()
$ports.Add(443)
$ports.Add('8080')

try { $ports.Add('https') }
catch { "Failed. 'https' can't be converted to an int, so it wasn't added" }

"Ports: $($ports -join ', ')"
"Type of item 1: $($ports[1].GetType().Name)"

Output:

Failed. 'https' can't be converted to an int, so it wasn't added
Ports: 443, 8080
Type of item 1: Int32

Use [System.Collections.Generic.List[object]] when you need to mix types, like strings and numbers in one list.

Dynamic arrays: let the loop build the array

The fastest way to build an array doesn’t add anything at all. Assign the output of a loop to a variable, and PowerShell collects every value into an array for you:

$squares = foreach ($n in 1..5) {
    $n * $n
}

"Count: $($squares.Count), type: $($squares.GetType().Name)"
"Values: $($squares -join ', ')"

Output:

Count: 5, type: Object[]
Values: 1, 4, 9, 16, 25

This works with foreach, ForEach-Object, if blocks and functions. The foreach loop shows more patterns.

How fast is +=?

Here’s the same 20,000 items added three ways. Your times will differ, but the pattern holds:

$count = 20000

$direct = Measure-Command { $result = foreach ($i in 1..$count) { $i } }
$list   = Measure-Command {
    $result = [System.Collections.Generic.List[int]]::new()
    foreach ($i in 1..$count) { $result.Add($i) }
}
$plus   = Measure-Command {
    $result = @()
    foreach ($i in 1..$count) { $result += $i }
}

"PowerShell $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor), adding $count items:"
'  Loop output   {0,8:N0} ms' -f $direct.TotalMilliseconds
'  List.Add      {0,8:N0} ms' -f $list.TotalMilliseconds
'  Array +=      {0,8:N0} ms' -f $plus.TotalMilliseconds

Output in PowerShell 7.6:

PowerShell 7.6, adding 20000 items:
  Loop output         22 ms
  List.Add           277 ms
  Array +=         1,271 ms

Output in Windows PowerShell 5.1:

PowerShell 5.1, adding 20000 items:
  Loop output         18 ms
  List.Add            23 ms
  Array +=        12,601 ms
PowerShell array += vs List Add speed in Windows PowerShell 5.1
In 5.1, += took seconds while the other two took milliseconds (times vary by PC)

PowerShell 7.5 made += much faster, as Microsoft notes in What’s New in PowerShell 7.5. In 5.1 it slows down badly as the array grows, so use loop output or a List there.

Convert an ArrayList or List to an array

Call ToArray, or cast to a typed array. ToArray gives an Object[] unless you pass the element type:

$list = [System.Collections.ArrayList]@('web01', 'web02', 'sql01')

$a = $list.ToArray()
$b = [string[]]$list
$c = @($list)
$d = $list.ToArray([string])

'ToArray()         -> {0}' -f $a.GetType().Name
'[string[]] cast   -> {0}' -f $b.GetType().Name
'@() wrapper       -> {0}' -f $c.GetType().Name
'ToArray([string]) -> {0}' -f $d.GetType().Name

Output:

ToArray()         -&