.NET
Dictionary
C#
Data Structures
Key-Value Pairs

.NET Dictionary get existing value or create and add new value

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

A common C sharp pattern is reading a value from a dictionary if a key exists, or creating a new value when it does not. This appears in counters, grouping maps, caches, and aggregation code. The best implementation depends on whether the dictionary is single-threaded or shared across threads.

Prefer TryGetValue Over ContainsKey Plus Indexer

ContainsKey followed by indexer access performs two lookups and can be noisy in repeated code.

csharp
1using System;
2using System.Collections.Generic;
3
4var totals = new Dictionary<string, int>();
5string region = "NA";
6
7if (!totals.TryGetValue(region, out var current))
8{
9    current = 0;
10}
11
12current += 1;
13totals[region] = current;
14
15Console.WriteLine(totals[region]);

TryGetValue is clearer and avoids duplicate probing.

Create a Reusable GetOrAdd Extension

When this pattern repeats, a helper improves consistency.

csharp
1using System;
2using System.Collections.Generic;
3
4public static class DictionaryExtensions
5{
6    public static TValue GetOrAdd<TKey, TValue>(
7        this IDictionary<TKey, TValue> map,
8        TKey key,
9        Func<TValue> factory)
10        where TKey : notnull
11    {
12        if (map.TryGetValue(key, out var existing))
13        {
14            return existing;
15        }
16
17        var created = factory();
18        map[key] = created;
19        return created;
20    }
21}
22
23public static class Demo
24{
25    public static void Main()
26    {
27        var groups = new Dictionary<string, List<int>>();
28        var list = groups.GetOrAdd("orders", () => new List<int>());
29        list.Add(1001);
30        list.Add(1002);
31
32        Console.WriteLine(groups["orders"].Count);
33    }
34}

This keeps call sites concise and avoids ad hoc implementations.

Include a Creation Flag When Needed

Sometimes callers need to know whether value existed or was created.

csharp
1public static (TValue value, bool created) GetOrAddWithFlag<TKey, TValue>(
2    this IDictionary<TKey, TValue> map,
3    TKey key,
4    Func<TValue> factory)
5    where TKey : notnull
6{
7    if (map.TryGetValue(key, out var existing))
8    {
9        return (existing, false);
10    }
11
12    var created = factory();
13    map[key] = created;
14    return (created, true);
15}

This is useful for metrics such as cache hit and cache miss counts.

Threaded Code Requires ConcurrentDictionary

Regular Dictionary is not safe for concurrent writes. For shared mutable state, use ConcurrentDictionary APIs.

csharp
1using System;
2using System.Collections.Concurrent;
3
4var counts = new ConcurrentDictionary<string, int>();
5
6for (int i = 0; i < 10_000; i++)
7{
8    counts.AddOrUpdate("hits", 1, (_, oldValue) => oldValue + 1);
9}
10
11Console.WriteLine(counts["hits"]);

For collection values:

csharp
1using System.Collections.Concurrent;
2
3var buckets = new ConcurrentDictionary<string, ConcurrentBag<int>>();
4
5void AddItem(string key, int item)
6{
7    var bag = buckets.GetOrAdd(key, _ => new ConcurrentBag<int>());
8    bag.Add(item);
9}

Keep factories side-effect free because they may run more than once under contention.

Capacity and Allocation Considerations

For large ingestion jobs, initialize dictionary with estimated capacity.

csharp
var map = new Dictionary<string, int>(capacity: 50_000);

This reduces resize overhead and improves throughput. Also avoid repeatedly allocating identical default objects when one immutable value is enough.

API Design Guidance

If helper behavior is shared across services, define one extension library with clear semantics:

  • does factory run lazily
  • does method mutate map in-place
  • is method thread-safe or not

Explicit contracts reduce misuse and simplify code review.

Nullable Value Considerations

If dictionary values may be null references, design helper behavior explicitly so missing keys and present-null keys are distinguishable. Clear null semantics prevent subtle cache and grouping bugs.

Common Pitfalls

  • Using ContainsKey plus indexer in hot paths with repeated lookups.
  • Sharing Dictionary across threads without synchronization.
  • Writing value factories that perform external side effects.
  • Reusing mutable default instances across different keys.
  • Ignoring capacity planning for large key spaces.

Summary

  • Use TryGetValue for efficient get-or-create logic in single-threaded dictionaries.
  • Extract reusable GetOrAdd helpers to reduce boilerplate.
  • Use ConcurrentDictionary for multi-threaded mutation.
  • Keep factories idempotent and side-effect free.
  • Pre-size dictionaries when handling large workloads.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

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

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.