C#
.NET
Collections
Data Structures
Programming

What is the difference between HashSetT and ListT?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

List<T> and HashSet<T> are both foundational .NET collections, but they solve different problems. List<T> is built for ordered sequences and index access, while HashSet<T> is built for uniqueness and fast membership checks. Picking the right one affects both correctness and performance.

Core Behavior Differences

List<T> acts like a dynamic array:

  • Preserves insertion order.
  • Allows duplicates.
  • Supports indexing.

HashSet<T> acts like a set:

  • Enforces uniqueness.
  • Does not provide index access.
  • Iteration order should be treated as non-contractual.

Simple example:

csharp
1using System;
2using System.Collections.Generic;
3
4var list = new List<string> { "apple", "banana", "apple" };
5var set = new HashSet<string> { "apple", "banana", "apple" };
6
7Console.WriteLine(list.Count); // 3
8Console.WriteLine(set.Count);  // 2

Same values, different semantics.

Performance Characteristics

Typical complexity behavior:

  • 'List<T>[index] is O(1).'
  • 'List<T>.Contains is O(n).'
  • 'HashSet<T>.Contains is near O(1) average.'
  • 'HashSet<T>.Add and Remove are near O(1) average.'

If your workload is many membership checks, HashSet<T> is usually better.

csharp
1using System;
2using System.Collections.Generic;
3
4var ids = new HashSet<int>();
5foreach (var id in new[] { 10, 11, 10, 12 })
6{
7    if (!ids.Add(id))
8    {
9        Console.WriteLine($"duplicate: {id}");
10    }
11}

Doing the same dedupe with list requires repeated linear scans.

When You Need Both Order and Uniqueness

Many real problems need unique values in first-seen order. A combined pattern works well.

csharp
1using System;
2using System.Collections.Generic;
3
4var input = new[] { 4, 2, 4, 1, 2, 3 };
5var seen = new HashSet<int>();
6var orderedUnique = new List<int>();
7
8foreach (var value in input)
9{
10    if (seen.Add(value))
11    {
12        orderedUnique.Add(value);
13    }
14}
15
16Console.WriteLine(string.Join(",", orderedUnique));

HashSet<T> handles uniqueness, List<T> preserves deterministic order.

Equality Rules Matter for HashSet<T>

HashSet<T> correctness depends on equality and hash code consistency. For custom classes, implement value-based equality correctly.

csharp
1using System;
2using System.Collections.Generic;
3
4public sealed class User : IEquatable<User>
5{
6    public int Id { get; }
7
8    public User(int id) => Id = id;
9
10    public bool Equals(User? other) => other is not null && Id == other.Id;
11    public override bool Equals(object? obj) => obj is User other && Equals(other);
12    public override int GetHashCode() => Id.GetHashCode();
13}
14
15var users = new HashSet<User> { new User(1), new User(1) };
16Console.WriteLine(users.Count); // 1

Without proper equality logic, duplicates may slip through unexpectedly.

Set Algebra Features

HashSet<T> includes operations that are verbose with lists:

  • 'UnionWith'
  • 'IntersectWith'
  • 'ExceptWith'
csharp
1using System;
2using System.Collections.Generic;
3
4var a = new HashSet<int> { 1, 2, 3 };
5var b = new HashSet<int> { 3, 4, 5 };
6
7var union = new HashSet<int>(a);
8union.UnionWith(b);
9
10var intersection = new HashSet<int>(a);
11intersection.IntersectWith(b);
12
13Console.WriteLine(string.Join(",", union));
14Console.WriteLine(string.Join(",", intersection));

If your logic is naturally set-oriented, this is usually cleaner and faster.

Choosing in Practice

Use List<T> when order and index-based operations matter. Use HashSet<T> when uniqueness and membership checks dominate.

A practical decision flow:

  1. Need duplicates or stable positional order by index uses List<T>.
  2. Need fast contains and dedupe uses HashSet<T>.
  3. Need both uses a combined pattern.

Measure with realistic data size before finalizing hot-path decisions.

Common Pitfalls

  • Using List<T>.Contains in large hot loops.
  • Assuming HashSet<T> has stable business-safe iteration order.
  • Forgetting equality and hash code rules for custom set items.
  • Mutating fields that influence hash code after insertion into set.
  • Replacing lists with sets without checking ordering requirements.

Summary

  • 'List<T> and HashSet<T> solve different collection problems.'
  • 'List<T> is best for ordered, indexable, duplicate-friendly sequences.'
  • 'HashSet<T> is best for uniqueness and fast membership tests.'
  • Combine both when you need ordered unique output.
  • Correct equality implementation is essential for reliable HashSet<T> behavior.

Course illustration
Course illustration

All Rights Reserved.