C#
.NET
Collections
Data Structures
Programming

What is the difference between HashSetT and ListT?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

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.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms