100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C#

Set Operations: Union, Intersect, Except

Learn how LINQ's set operators — Union, Intersect, Except, and Distinct — deduplicate and compare sequences using equality semantics.

Aggregation and SetsIntermediate9 min readJul 10, 2026
Analogies

LINQ's Set Operators

LINQ provides four set-based operators that treat sequences like mathematical sets rather than ordered lists: Union() combines two sequences and removes duplicates, Intersect() returns only elements present in both sequences, Except() returns elements in the first sequence that are absent from the second, and Distinct() removes duplicates from a single sequence. All four use equality comparison (EqualityComparer<T>.Default by default, or a supplied IEqualityComparer<T>) to determine 'sameness', and all four are deferred — they don't run until you enumerate the result, and internally they build a hash-based lookup for efficiency rather than doing nested-loop comparisons.

🏏

Cricket analogy: Combining the squad lists from two different IPL franchises a player has represented, removing the player's name if it appears in both, mirrors how Union() merges two sequences while deduplicating shared entries.

Intersect and Except in Practice

Intersect() and Except() are asymmetric in a way that trips people up: skillsA.Intersect(skillsB) returns the same result regardless of argument order (intersection is commutative), but skillsA.Except(skillsB) is not the same as skillsB.Except(skillsA) — Except() returns 'what's in the first sequence but missing from the second', so a.Except(b) finds elements unique to a, while b.Except(a) finds elements unique to b. A classic real use is finding a symmetric difference (elements in exactly one of the two sets) by combining a.Except(b).Union(b.Except(a)), since LINQ has no single built-in operator for that.

🏏

Cricket analogy: Comparing a player's IPL-only skills against their Test-only skills gives asymmetric results depending on direction — 'what IPL skills aren't in Tests' differs from 'what Test skills aren't in IPL' — mirroring how a.Except(b) differs from b.Except(a).

Distinct and Custom Equality Comparers

Distinct() removes duplicate elements from a single sequence, and like the other set operators it needs a way to determine equality — for value types and strings the default comparer works naturally, but for custom classes you must either override Equals()/GetHashCode() or pass an IEqualityComparer<T> to get meaningful deduplication instead of accidentally keeping every 'duplicate' because reference equality never matches. Since .NET 6, DistinctBy(keySelector) lets you deduplicate based on just one property (e.g., DistinctBy(p => p.Email)) without needing a full custom comparer, mirroring how UnionBy, IntersectBy, and ExceptBy work for the other set operators.

🏏

Cricket analogy: Deduplicating a list of player appearances by matching jersey number and team rather than by object reference ensures two DTOs representing the same player are recognized as duplicates, mirroring why Distinct() needs a proper Equals() override on custom classes.

csharp
var teamA = new List<string> { "Alice", "Bob", "Carol" };
var teamB = new List<string> { "Bob", "Dave", "Eve" };

var combined = teamA.Union(teamB);          // Alice, Bob, Carol, Dave, Eve
var shared = teamA.Intersect(teamB);         // Bob
var onlyInA = teamA.Except(teamB);           // Alice, Carol
var onlyInB = teamB.Except(teamA);           // Dave, Eve — order matters!

// Symmetric difference: elements unique to exactly one side
var symmetricDifference = onlyInA.Union(onlyInB); // Alice, Carol, Dave, Eve

public record Customer(int Id, string Email);

var customers = new List<Customer>
{
    new(1, "a@example.com"),
    new(2, "a@example.com"), // duplicate email, different Id
    new(3, "c@example.com"),
};

// DistinctBy (since .NET 6) dedupes by a single key without a full comparer
var uniqueByEmail = customers.DistinctBy(c => c.Email); // 2 customers remain

Except() and Intersect() are order-sensitive on the receiver: a.Except(b) is not the same as b.Except(a). Only Union() and Intersect() are commutative in the mathematical sense of returning equivalent element sets — Except() explicitly means 'first sequence minus second'.

All four set operators build an internal hash set from the second sequence (or from previously-seen elements for Distinct()) to achieve roughly O(n + m) performance rather than the O(n × m) you'd get from a naive nested-loop comparison — this is why a well-defined GetHashCode() matters for custom types.

  • Union() merges two sequences and removes duplicates; Intersect() keeps only elements present in both; Except() keeps elements in the first sequence but not the second.
  • Distinct() removes duplicates within a single sequence.
  • Except() and the ordering of arguments matters — a.Except(b) differs from b.Except(a); Union() and Intersect() are commutative.
  • All four rely on equality comparison — override Equals()/GetHashCode() or pass IEqualityComparer<T> for custom types.
  • DistinctBy, UnionBy, IntersectBy, and ExceptBy (since .NET 6) allow key-based comparison without a full custom comparer.
  • Internally these operators use hash-based lookups for roughly O(n + m) performance.
  • Symmetric difference (elements unique to exactly one sequence) requires combining Except() calls with Union().

Practice what you learned

Was this page helpful?

Topics covered

#LINQStudyNotes#MicrosoftTechnologies#SetOperationsUnionIntersectExcept#Set#Operations#Union#Intersect#StudyNotes#SkillVeris#ExamPrep