Back to Blog
C#

C# Dictionary vs List: Choosing the Right Collection

c# dictionary vs list: Compare C# Dictionary and List for lookup, iteration, and memory behavior. Learn which collection fits your access pattern and performance needs.

DictionaryListC# CollectionsPerformanceData Structures
A visual comparison of a C# Dictionary and List, showing key-based lookup versus index-based access with a performance scale.

When you need to store and retrieve data in C#, the choice between Dictionary<TKey, TValue> and List<T> often comes down to how you access that data. The c# dictionary vs list decision is not about which is better in absolute terms, but about matching the collection's internal behavior to your workload.

A List<T> is an ordered, index-based collection. You access elements by their position, and you can add or remove items at the end in constant time. A Dictionary<TKey, TValue> is a hash-based collection that stores key-value pairs. It provides near-constant-time lookup by key, but it does not preserve insertion order by default. Understanding these fundamental differences will help you avoid performance pitfalls and write clearer code.

The Core Difference: Keyed Lookup vs Indexed Access

The most immediate distinction is how you retrieve an element. With a List<T>, you use an integer index:

var fruits = new List<string> { "apple", "banana", "cherry" }; string second = fruits[1]; // "banana"

With a Dictionary<TKey, TValue>, you use a key of any type:

var fruitColors = new Dictionary<string, string> { ["apple"] = "red", ["banana"] = "yellow", ["cherry"] = "dark red" }; string appleColor = fruitColors["apple"]; // "red"

If your data naturally has a unique identifier—like an ID, a name, or a composite key—the dictionary eliminates the need to scan the list to find a match. That scan is what makes list lookup O(n) in the worst case, while dictionary lookup is O(1) on average.

Performance Characteristics: Lookup, Insert, Delete

Performance is the most common reason developers compare these two collections. The underlying data structures dictate the big-O complexity for basic operations.

OperationList<T>Dictionary<TKey, TValue>
Access by indexO(1)Not applicable
Lookup by keyO(n) linear scanO(1) average, O(n) worst case
Insert at endO(1) amortizedO(1) average
Insert at middleO(n) shiftO(1) average (no order)
Remove by valueO(n) scanO(1) average by key
Remove by indexO(n) shiftNot applicable

A list's index-based access is unbeatable when you know the position. But if you need to find an element by a property, the list forces a linear search. The dictionary trades the ability to access by position for the ability to access by key without scanning.

It is important to note that dictionary lookups can degrade to O(n) if many keys collide in the same hash bucket. In practice, a well-implemented hash function keeps collisions rare, but you should be aware that the guarantee is average-case, not absolute.

Memory and Allocation Behavior

Both collections store references to objects, but their internal layouts differ. A List<T> uses a contiguous array that grows as needed. When the capacity is exceeded, it allocates a new array and copies elements. This can cause memory spikes during growth, but it also provides excellent cache locality during iteration.

A Dictionary<TKey, TValue> uses an array of buckets plus a separate entry structure for each key-value pair. Each entry stores the key, the value, and the hash code. This means a dictionary consumes more memory per element than a list, especially for small value types. The extra memory is the price you pay for O(1) key lookup.

If you are storing a large number of simple records and only need sequential access, a list will be more memory-efficient. If you need fast random access by a key, the dictionary's overhead is justified.

Iteration and Ordering Semantics

A List<T> preserves the order in which you add elements. Iterating with foreach or a for loop gives you a deterministic sequence. This is critical when order matters, such as when rendering a UI list or processing items in a specific sequence.

A Dictionary<TKey, TValue> does not guarantee any order. The enumeration order is implementation-defined and can change when you add or remove items. If you rely on the order of a dictionary, your code will be fragile. For example:

var scores = new Dictionary<string, int> { ["alice"] = 90, ["bob"] = 85, ["carol"] = 95 }; foreach (var kvp in scores) { Console.WriteLine($"{kvp.Key}: {kvp.Value}"); }

The output order is not guaranteed to be insertion order. If you need both keyed lookup and insertion order, you can use OrderedDictionary from System.Collections.Specialized, but that adds complexity. Alternatively, maintain a separate List<T> of keys if you need deterministic ordering.

When to Use a Dictionary Over a List

Use a dictionary when your primary operation is looking up a value by a unique key. Typical scenarios include:

  • Configuration settings where each key is a setting name.
  • Caching results keyed by a request ID or URL.
  • Mapping user IDs to user objects in a session store.
  • Counting occurrences where the key is the item and the value is the count.

In these cases, a dictionary avoids the O(n) scan that a list would require. For example, to count word frequencies in a text, a dictionary is the natural choice:

var wordCounts = new Dictionary<string, int>(); foreach (var word in words) { wordCounts.TryGetValue(word, out int count); wordCounts[word] = count + 1; }

This code would be much slower with a list because each increment would require a linear search for the word.

When a List Is the Better Choice

Choose a list when you need to preserve order, access elements by index, or when the collection size is small enough that linear search is negligible. Lists are also better when you need to iterate over all elements frequently and the order matters.

For example, if you are displaying a list of products in a fixed order, a list is straightforward:

var products = new List<Product>(); products.Add(new Product { Name = "Laptop", Price = 1200 }); products.Add(new Product { Name = "Mouse", Price = 25 }); for (int i = 0; i < products.Count; i++) { Console.WriteLine($"{i + 1}. {products[i].Name}"); }

If you only need to check whether an item exists and the list is small (say fewer than 50 elements), the linear scan is fast enough. Prematurely introducing a dictionary for a small, order-sensitive collection adds complexity without measurable benefit.

Practical Example: Lookup-Intensive Code

Consider a method that processes a batch of orders and needs to find the customer for each order. If you have a list of customers and you search by ID inside a loop, the total cost becomes O(n*m), where n is the number of customers and m is the number of orders. With a dictionary, the cost drops to O(m) after the initial build.

// List approach: O(n*m) var customers = GetCustomers(); // List<Customer> foreach (var order in orders) { var customer = customers.FirstOrDefault(c => c.Id == order.CustomerId); // process order } // Dictionary approach: O(n + m) var customerDict = customers.ToDictionary(c => c.Id); foreach (var order in orders) { var customer = customerDict[order.CustomerId]; // process order }

The dictionary version builds the lookup structure once and then each order lookup is constant time. This pattern is common in data processing and is a clear win for the dictionary.

Common Pitfalls and Edge Cases

One common mistake is using a mutable type as a dictionary key. If the key's hash code changes after it is inserted, the dictionary will not be able to find it. For example, if you use a List<int> as a key and then modify it, the hash code changes and the entry becomes unreachable. Keys should be immutable or at least have a stable hash.

Another issue is the KeyNotFoundException when you access a dictionary with a missing key. Always use TryGetValue or the ContainsKey method to avoid exceptions in production code:

if (dictionary.TryGetValue(key, out var value)) { // use value } else { // handle missing key }

For lists, a common pitfall is removing items while iterating. Modifying the list inside a foreach loop throws an InvalidOperationException. Use a for loop backwards or List<T>.RemoveAll to safely remove elements.

Finally, consider the impact of value types versus reference types. A Dictionary that stores value types copies the values on each access, which can be a performance concern for large structs. A List also copies values, but the overhead is the same. The real difference is in the internal storage: a dictionary stores the key and value in separate entry objects, which can increase memory pressure for many small entries.

Understanding the internal behavior of these collections helps you make an informed choice. The c# dictionary vs list decision is not about one being superior; it is about matching the collection's strengths to your access patterns. For keyed lookups, use a dictionary. For ordered, index-based access, use a list. When performance matters, measure your specific workload, but the algorithmic differences described here will guide you in the right direction.

c# dictionary vs list: Practical Usage and Code Examples | RYUSLOG DEV