ConcurrentDictionary
TryRemove
C#
multithreading
.NET

When will ConcurrentDictionary TryRemove return false

Master System Design with Codemia

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

Introduction

ConcurrentDictionary.TryRemove returns false when the key is not present at the moment the removal attempt occurs. In multithreaded code, that usually means the key was never there, was already removed, or changed in a way that no longer matches the lookup.

The Basic Meaning of false

Here is the typical shape of the method:

csharp
1using System.Collections.Concurrent;
2
3var dictionary = new ConcurrentDictionary<string, int>();
4var removed = dictionary.TryRemove("job-42", out var value);
5
6Console.WriteLine(removed);
7Console.WriteLine(value);

If "job-42" is not in the dictionary, removed is false and value receives the default for the value type. For int, that is 0.

That is the first and simplest answer: TryRemove fails when there is no matching key to remove.

Concurrency Makes Timing Matter

Because the dictionary is concurrent, another thread may remove the key before your thread gets there.

csharp
1using System;
2using System.Collections.Concurrent;
3using System.Threading.Tasks;
4
5var dictionary = new ConcurrentDictionary<string, int>();
6dictionary["task"] = 1;
7
8var t1 = Task.Run(() => dictionary.TryRemove("task", out _));
9var t2 = Task.Run(() => dictionary.TryRemove("task", out _));
10
11await Task.WhenAll(t1, t2);
12
13Console.WriteLine($"t1: {t1.Result}, t2: {t2.Result}");

Exactly one task is expected to return true. The other one sees the key as already gone and returns false.

That is normal behavior, not a race-condition bug inside ConcurrentDictionary. The operation is thread-safe, but it does not guarantee that every competing remover succeeds.

Common Real Reasons for false

In practice, TryRemove returns false in these situations:

  • the key was never added
  • the key was already removed by this thread earlier
  • another thread removed it first
  • the key being searched is not equal to the stored key according to the dictionary comparer

The last case is easy to miss. If the dictionary uses a case-sensitive comparer, then "ABC" and "abc" are different keys.

csharp
1var dict = new ConcurrentDictionary<string, int>();
2dict["ABC"] = 123;
3
4Console.WriteLine(dict.TryRemove("abc", out _));

That prints False because the key does not match.

What TryRemove Does Not Mean

false does not mean:

  • the dictionary is broken
  • the operation was partially applied
  • another thread corrupted state

The method is atomic with respect to its own removal attempt. It simply reports that no matching entry was removed.

That makes it safe to use in lock-free workflows where losing the race is expected.

Designing Code Around This Result

Treat false as a valid branch in your control flow.

csharp
1if (jobs.TryRemove(jobId, out var job))
2{
3    Console.WriteLine($"Removed {jobId}");
4}
5else
6{
7    Console.WriteLine($"Job {jobId} was already gone");
8}

That style is better than assuming removal must always succeed. In concurrent systems, absence is often normal.

If you need stronger coordination, the answer is usually a higher-level protocol, not a different interpretation of TryRemove.

Be Careful with Check-Then-Act Logic

A classic mistake is:

csharp
1if (dictionary.ContainsKey(key))
2{
3    dictionary.TryRemove(key, out _);
4}

Between the ContainsKey check and the removal, another thread can remove the key. So the prior check does not guarantee a later TryRemove will succeed.

In concurrent collections, the operation result is more trustworthy than a separate earlier check.

Common Pitfalls

  • Expecting TryRemove to return true just because the key existed a moment ago.
  • Interpreting false as a concurrency failure instead of "no matching key now".
  • Forgetting that key comparers affect what counts as a match.
  • Using ContainsKey as a guarantee before removal. Another thread can change the state immediately afterward.
  • Ignoring the returned value and then assuming the removal happened.

Summary

  • 'ConcurrentDictionary.TryRemove returns false when no matching key exists at removal time.'
  • In multithreaded code, another thread may have removed the key first.
  • The method is still thread-safe; false is often an expected outcome.
  • Key matching depends on the dictionary's comparer, not just the raw text you pass in.
  • Write concurrent code so that a failed remove is a normal branch, not a surprise.

Course illustration
Course illustration

All Rights Reserved.