SortedList vs SortedDictionary in C

Both SortedList<TKey, TValue> and SortedDictionary<TKey, TValue> are generic collection classes in C# that maintain elements in sorted order based on their keys. While their public interfaces and functional outcomes appear almost identical, their internal implementations are fundamentally different. SortedList<TKey, TValue> relies on contiguous arrays, whereas SortedDictionary<TKey, TValue> is implemented as a self-balancing binary search tree. These architectural differences create distinct trade-offs in memory consumption, insertion speed, lookup performance, and indexed access.

Underlying Data Structure

  • SortedList<TKey, TValue> maintains two internal arrays: one for keys and one for values. The elements are kept sorted by performing a binary search to find the correct index for any operation.
  • SortedDictionary<TKey, TValue> uses a Red-Black Tree, which is a self-balancing binary search tree. Each element is stored in an independent tree node that links to its parent and child nodes.

Performance Characteristics

The difference in data structures dictates the time complexity of common operations:

Operation SortedList<TKey, TValue> SortedDictionary<TKey, TValue>
Lookup by Key \(O(\log n)\) \(O(\log n)\)
Insertion \(O(n)\) \(O(\log n)\)
Deletion \(O(n)\) \(O(\log n)\)
Indexed Access \(O(1)\) Not Supported (\(O(n)\) via LINQ)

While both collections offer \(O(\log n)\) key retrieval, SortedList<TKey, TValue> is typically faster in practice for lookups because arrays provide better CPU cache locality than node-based trees.

Conversely, inserting or removing elements in a SortedList<TKey, TValue> requires shifting all subsequent elements in the array, making these modifications \(O(n)\). SortedDictionary<TKey, TValue> performs insertions and deletions in \(O(\log n)\) time because it only needs to update node pointers and rebalance the tree.

Memory Overhead

  • SortedList<TKey, TValue> has minimal memory overhead. Because it stores data in flat arrays, memory is allocated contiguously without the need for node pointers or object wrapper metadata.
  • SortedDictionary<TKey, TValue> has significantly higher memory overhead. Each entry requires a separate node object that stores references to the key, value, left child, right child, parent, and node color. This design also increases pressure on the garbage collector due to frequent allocations and deallocations.

Indexing Capabilities

SortedList<TKey, TValue> exposes indexed access to keys and values through its Keys and Values properties (e.g., myList.Keys[i]). This allows developers to retrieve the \(i\)-th smallest element in \(O(1)\) time.

SortedDictionary<TKey, TValue> does not support efficient indexed access. Accessing an element by index requires iterating through the tree using an enumerator, which takes \(O(n)\) time.

When to Use Each Collection

Use SortedList<TKey, TValue> when:

  • The collection is populated once or modified infrequently, but queried often.
  • Memory usage is a critical constraint.
  • You need fast, direct access to keys and values by numeric index.
  • Data is inserted in sequential, sorted order (which eliminates the need to shift array elements).

Use SortedDictionary<TKey, TValue> when:

  • The application performs frequent insertions, updates, and deletions of unsorted data.
  • Fast insertion speed takes priority over memory efficiency and lookup caching.
  • Indexed access to elements is unnecessary.