.NET
ConcurrentList
.NET 4.0
threading
collections

No ConcurrentListT in .Net 4.0?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

.NET 4.0 introduced System.Collections.Concurrent, but it did not include a ConcurrentList type. That was not an omission by accident. List semantics such as index-based mutation and stable ordering are hard to make both thread-safe and scalable under heavy contention.

Why There Is No Built-In ConcurrentList

List<T> is optimized for single-threaded random access and amortized append performance. In concurrent workloads, operations that look simple on one thread become coordination-heavy.

Key friction points:

  • Index-based inserts and removes shift many elements.
  • Enumeration plus mutation requires strict synchronization.
  • Capacity growth involves array reallocation and copy.
  • Fine-grained locking can still serialize hotspots.

The concurrent types shipped in .NET 4.0 focus on access patterns that scale better in parallel systems, such as queue, stack, bag, and dictionary workloads.

Use the Right Concurrent Type for the Job

Many teams ask for ConcurrentList when they actually need a different data structure.

If order is FIFO, use ConcurrentQueue<T>.

csharp
1using System;
2using System.Collections.Concurrent;
3
4var queue = new ConcurrentQueue<int>();
5queue.Enqueue(1);
6queue.Enqueue(2);
7
8if (queue.TryDequeue(out var value))
9{
10    Console.WriteLine(value);
11}

If key-based lookup matters, use ConcurrentDictionary<TKey,TValue>.

csharp
1using System;
2using System.Collections.Concurrent;
3
4var map = new ConcurrentDictionary<string, int>();
5map["activeUsers"] = 10;
6map.AddOrUpdate("activeUsers", 1, (_, oldVal) => oldVal + 1);
7Console.WriteLine(map["activeUsers"]);

Choosing by access pattern usually solves the original concurrency problem better than forcing list semantics.

Safe List<T> Wrapper with Locks

When you truly need ordered indexed data, wrap a regular list with explicit locking. Keep the API small and clear.

csharp
1using System;
2using System.Collections.Generic;
3using System.Threading;
4
5public sealed class LockedList<T>
6{
7    private readonly List<T> _inner = new List<T>();
8    private readonly ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
9
10    public void Add(T item)
11    {
12        _rw.EnterWriteLock();
13        try
14        {
15            _inner.Add(item);
16        }
17        finally
18        {
19            _rw.ExitWriteLock();
20        }
21    }
22
23    public T GetAt(int index)
24    {
25        _rw.EnterReadLock();
26        try
27        {
28            return _inner[index];
29        }
30        finally
31        {
32            _rw.ExitReadLock();
33        }
34    }
35
36    public T[] Snapshot()
37    {
38        _rw.EnterReadLock();
39        try
40        {
41            return _inner.ToArray();
42        }
43        finally
44        {
45            _rw.ExitReadLock();
46        }
47    }
48}

The snapshot method is important because it avoids iterating while other threads mutate the underlying list.

Immutable Alternative for Read-Heavy Scenarios

If writes are infrequent and reads are frequent, immutable snapshots often outperform lock-heavy mutation patterns. A writer creates a new version, then atomically swaps reference.

csharp
1using System;
2using System.Collections.Immutable;
3using System.Threading;
4
5ImmutableList<int> items = ImmutableList<int>.Empty;
6
7void AddItem(int x)
8{
9    ImmutableList<int> oldList, newList;
10    do
11    {
12        oldList = items;
13        newList = oldList.Add(x);
14    }
15    while (Interlocked.CompareExchange(ref items, newList, oldList) != oldList);
16}

This pattern trades extra allocations for simpler concurrent reads.

Practical Selection Guide

Choose based on workload:

  • Mostly append and consume in order: ConcurrentQueue<T>.
  • Mostly key lookup and update: ConcurrentDictionary<TKey,TValue>.
  • Strict ordered index operations required: locked wrapper over List<T>.
  • Read-heavy with occasional writes: immutable snapshot strategy.

Trying to use one generic concurrent list for every case usually leads to contention or unclear semantics.

Common Pitfalls

  • Assuming List<T> plus occasional lock statements is enough without a consistent lock policy.
  • Exposing raw list enumerators while writes are happening on other threads.
  • Building a large custom concurrent list when queue or dictionary already matches the access pattern.
  • Overusing coarse locks and blocking high-throughput paths.
  • Ignoring benchmarks and selecting structure by habit instead of workload.

Summary

  • .NET 4.0 intentionally shipped several concurrent collections but no ConcurrentList.
  • List semantics are costly to synchronize under contention.
  • Most use cases map better to queue, bag, or dictionary types.
  • If ordered indexed access is required, wrap List<T> with a strict lock strategy.
  • Validate your design with workload-based tests, not assumptions.

Related reading
Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Track what you have practised

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

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.