.Net
Data Structures
Performance
Memory Management
Collections

.Net Data structures ArrayList, List, HashTable, Dictionary, SortedList, SortedDictionary -- Speed, memory, and when to use each?

Master System Design with Codemia

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

Introduction

Choosing between ArrayList, List<T>, Hashtable, Dictionary<TKey,TValue>, SortedList<TKey,TValue>, and SortedDictionary<TKey,TValue> in .NET is mostly about type safety, lookup complexity, insertion patterns, and memory overhead. Many legacy guides overfocus on raw speed and ignore developer productivity and correctness costs from boxing/unboxing and weak typing.

In modern .NET, generic collections are usually the default. Non-generic types remain relevant mainly for compatibility with older APIs. This article gives practical selection criteria grounded in common workloads.

Core Sections

1. ArrayList vs List<T>

ArrayList stores object, so value types box/unbox.

csharp
ArrayList a = new ArrayList();
a.Add(1); // boxing
int x = (int)a[0]; // unboxing

List<T> is type-safe and avoids boxing for value types.

csharp
List<int> numbers = new List<int>();
numbers.Add(1);
int x = numbers[0];

Prefer List<T> unless maintaining legacy APIs.

2. Hashtable vs Dictionary<TKey,TValue>

Hashtable is non-generic and older.

csharp
Hashtable h = new Hashtable();
h["key"] = 42;

Dictionary<TKey,TValue> is faster in most modern scenarios and type-safe.

csharp
var d = new Dictionary<string, int>();
d["key"] = 42;

Average lookup for both is near O(1) with good hash distribution.

3. Sorted key-value options

SortedList<TKey,TValue> uses arrays internally. Good memory profile for mostly static data and fast index-based access.

SortedDictionary<TKey,TValue> uses a tree structure. Better for frequent inserts/removes with ordered keys.

csharp
var sl = new SortedList<int, string>();
var sd = new SortedDictionary<int, string>();

Both have O(log n) lookup by key.

4. Memory and mutation tradeoffs

  • List<T>: compact and cache-friendly for sequential data.
  • Dictionary<TKey,TValue>: hash buckets add overhead but fast lookup.
  • SortedDictionary: more node overhead than sorted list.
  • SortedList: insertions can shift arrays (O(n)).

Choose based on mutation frequency, not just lookup benchmark numbers.

5. Thread-safety considerations

None of these are intrinsically safe for concurrent writes.

csharp
var concurrent = new System.Collections.Concurrent.ConcurrentDictionary<string, int>();
concurrent.AddOrUpdate("k", 1, (_, v) => v + 1);

For multi-threaded mutation, use concurrent collections.

6. Practical selection guide

  • Sequential indexed data: List<T>.
  • Fast key lookup: Dictionary<TKey,TValue>.
  • Ordered keys, few inserts: SortedList<TKey,TValue>.
  • Ordered keys, many inserts/removes: SortedDictionary<TKey,TValue>.
  • Legacy interop only: ArrayList / Hashtable.

Benchmark with realistic data sizes before optimizing microseconds.

Common Pitfalls

  • Using non-generic collections in new code and paying boxing/type-cast overhead.
  • Choosing sorted collections when key order is never used.
  • Assuming dictionary operations are always O(1) under poor hash distributions.
  • Ignoring mutation patterns and picking SortedList for write-heavy workloads.
  • Using non-thread-safe collections in concurrent write paths.

Summary

In modern .NET, default to generic collections: List<T> for ordered sequences and Dictionary<TKey,TValue> for key lookup. Choose sorted variants only when ordered keys matter, and match collection internals to your mutation profile. Non-generic collections are mainly for legacy compatibility. With a workload-first selection strategy, you get better performance, lower memory waste, and safer code.

For teams maintaining net data structures arraylist list hashtable dictionary sortedlist sorteddictionary -- speed memory and when to use each closed in long-lived codebases, reliability improves when implementation guidance is paired with a lightweight verification routine. A practical pattern is to define three test categories up front. First, happy-path tests that validate normal expected inputs. Second, boundary tests that include empty values, minimum and maximum limits, and malformed records from real logs. Third, operational tests that simulate production-like behavior under retries, parallel execution, and partial failure. This combination catches both obvious logic defects and the subtle integration issues that usually appear after deployment.

It is also useful to encode assumptions close to the code rather than leaving them in scattered documentation. Add short comments where invariants matter, keep helper utilities centralized, and avoid repeating slightly different logic in multiple modules. In CI, run a small deterministic suite on every commit and a broader dataset suite on schedule. When incidents occur, convert the failing scenario into a permanent regression test before patching. Over time this creates a strong feedback loop where net data structures arraylist list hashtable dictionary sortedlist sorteddictionary -- speed memory and when to use each closed behavior remains stable even as dependencies, framework versions, and team ownership change. The result is less firefighting and faster review cycles.


Course illustration
Course illustration

All Rights Reserved.