.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.
List<T> is type-safe and avoids boxing for value types.
Prefer List<T> unless maintaining legacy APIs.
2. Hashtable vs Dictionary<TKey,TValue>
Hashtable is non-generic and older.
Dictionary<TKey,TValue> is faster in most modern scenarios and type-safe.
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.
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.
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
SortedListfor 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.

