Mastering Concurrency in .NET: Concurrent Collections Cheat-Sheet 🧵
Here are 5 collections from System.Collections.Concurrent namespace:
1️⃣ ConcurrentQueue
Pattern: FIFO (first-in, first-out)
Use cases: background job queues, logging pipelines, task schedulers
Features: lock-free Enqueue/Dequeue, snapshot enumeration
Gotchas: heavy producer contention can still throttle throughput - consider sharding multiple queues for extreme load.

2️⃣ ConcurrentStack
Pattern: LIFO (last-in, first-out).
Use cases: re-use pools (buffers, DbContexts), depth-first task dispatch.
Features: Treiber stack > ultra-fast push/pop.
Gotchas: LIFO can starve older items; not ideal for fair or time-sensitive workloads.

3️⃣ ConcurrentBag
Pattern: unordered "bag" with thread-local buckets.
Use cases: parallel algorithms where order is irrelevant; quick stash of work units.
Features: minimal contention thanks to work-stealing.
Gotchas: enumeration is chaotic; never rely on order or stable counts mid-operation.

4️⃣ ConcurrentDictionary<TKey, TValue>
Pattern: key-value store with atomic operations.
Use cases: caches, session stores, memoization tables, throttling ledgers.
Features: GetOrAdd, AddOrUpdate, high read concurrency.
Gotchas: values themselves aren't thread-safe - mutating a reference-type value in place can still create races. Prefer replace-by-new or immutable value objects.

5️⃣ BlockingCollection
Pattern: producer-consumer wrapper with optional capacity bounds.
Use cases: classic synchronous pipelines, controlled back-pressure.
Features: pluggable underlying store (ConcurrentQueue, ConcurrentStack, etc.), GetConsumingEnumerable() for graceful drains, CompleteAdding() for shutdown.
Gotchas: synchronous blocking - pair with threads or Tasks, not async/await.

⚠️ Common Concurrency Pitfalls
- ❌ Double-locking around concurrent collections
✅ Trust the collection - remove the outer lock
- ❌ Enumerating while mutating without a snapshot
✅ Copy to array or use GetConsumingEnumerable()
- ❌ Relying on item ordering in Bag or Queue under contention
✅ If order matters, switch to BlockingCollection + custom ordering logic
- ❌ Mutating stored reference types in ConcurrentDictionary
✅ Replace the whole value object or use immutable POCOs
- ❌ Forgetting CompleteAdding() on BlockingCollection
✅ Writers finish, readers hang forever - always signal completion

🚀 Takeaway
Pick the right concurrent collection, and you'll gain throughput and safety without piles of lock statements. Misuse it, and you'll chase phantom bugs for days.
💡 You could find other mechanisms of Thread Safety in .NET in my article:
Thread Safety in .NET: lock, Semaphore, Mutex | Compile Theory