How to Compare Two Lists in PowerShell
PowerShell has a cmdlet built specifically for this job - Compare-Object - along with a couple of operator-based shortcuts that read more naturally in a pipeline. The main things to get right: which SideIndicator means what, and the default case-insensitivity that catches almost everyone at least once.
Quick answer
Compare-Object $a $b | Where-Object SideIndicator -eq '<=' returns items only in $a. SideIndicator '<=' means the item exists only in the first (reference) list, '=>' means only in the second, and '==' means both (only shown with -IncludeEqual). Compare-Object is case-insensitive by default - add -CaseSensitive if that's not what you want. $a | Where-Object { $_ -notin $b } is a simpler alternative that returns plain values instead of SideIndicator objects.
Need the same thing in another language?
See the full reference for Python, JavaScript, SQL, Java, Bash, and more.
Compare-Object - the built-in cmdlet
$a = @('apple', 'banana', 'cherry')
$b = @('banana', 'date')
Compare-Object $a $b
# InputObject SideIndicator
# ----------- -------------
# cherry <=
# apple <=
# date => SideIndicator tells you which side each value came from: <= means the value is only in $a (the first argument, called the reference object), and => means it's only in $b (the difference object). Matching values are omitted from the output entirely unless you add -IncludeEqual.
Filtering to just one side
$aOnly = Compare-Object $a $b |
Where-Object SideIndicator -eq '<=' |
Select-Object -ExpandProperty InputObject
$aOnly # apple, cherry Piping through Where-Object isolates the <= rows, and Select-Object -ExpandProperty InputObject unwraps the underlying values from the comparison objects Compare-Object returns, giving you back a plain array of strings.
-notin and -in: a simpler operator-based approach
$aOnly = $a | Where-Object { $_ -notin $b }
$aOnly # apple, cherry
$intersection = $a | Where-Object { $_ -in $b }
$intersection # banana -notin and -in read more naturally in a pipeline and return plain values directly rather than SideIndicator objects, at the cost of not distinguishing which original list each unmatched item belongs to (which doesn't matter here since you're already filtering one specific list).
The case-sensitivity trap
$a = @('Apple', 'Banana')
$b = @('apple', 'banana')
Compare-Object $a $b # no output - treated as identical
Compare-Object $a $b -CaseSensitive # shows Apple/apple and Banana/banana as different Both Compare-Object and the -in/-notin operators are case-insensitive by default in Windows PowerShell and PowerShell 7. If a comparison looks like it's silently ignoring an obvious difference, this is almost always why - add -CaseSensitive to Compare-Object, or use the explicitly case-sensitive operators -cnotin / -cin instead.
Comparing large lists with a HashSet
$setB = [System.Collections.Generic.HashSet[string]]::new($b)
$aOnly = $a | Where-Object { -not $setB.Contains($_) } Both Compare-Object and the -notin operator effectively scan under the hood, which becomes noticeable on lists in the tens of thousands. Building a .NET HashSet directly and checking .Contains() brings each lookup down to O(1), the same optimization other languages rely on for large-scale comparisons.
Just need the answer, not the code? Paste both lists.
Open Compare 2 Lists →Frequently Asked Questions
What does Compare-Object return by default?
InputObject (the value) and SideIndicator, which is <= if the value only exists in the first (reference) list, => if it only exists in the second (difference) list, and == for matches, but only when the -IncludeEqual switch is used.Is Compare-Object case-sensitive?
-CaseSensitive switch if you need an exact-case comparison - this is one of the most common surprises when the tool seems to be ignoring an obvious difference.How do I get only the items unique to the first list?
Compare-Object $a $b | Where-Object SideIndicator -eq '<=' filters the output down to just the <= rows, which represent values present in $a (the reference object) but not $b (the difference object).What is the -notin operator and how is it different from Compare-Object?
$a | Where-Object { $_ -notin $b } filters $a down to items that don't appear in $b, similar to Compare-Object's <= rows, but returns the raw values directly instead of objects with a SideIndicator property - useful when you want a plain array back rather than Compare-Object's structured output.Which is faster for large lists, Compare-Object or -notin?
System.Collections.Generic.HashSet[string] from one list and check .Contains() against it in a loop or with Where-Object - that brings the lookup down to O(1) per item.How do I compare two lists of objects (not plain strings) in PowerShell?
-Property to tell Compare-Object which property to compare on: Compare-Object $a $b -Property Id. Without -Property, Compare-Object compares whole objects, which usually fails to match custom objects that are logically equal but are different object instances.