.NET
dictionaries
duplicate keys
C# programming
data structures

Duplicate keys in .NET dictionaries?

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

Duplicate dictionary keys in .NET are not a minor edge case. They define whether your code fails fast, silently overwrites data, or merges values intentionally. If your input comes from APIs, CSV files, or message queues, key collision policy should be explicit and tested.

How Dictionary Handles Duplicate Keys

Dictionary<TKey, TValue> guarantees unique keys according to its comparer. Two common write patterns behave differently:

  • Add throws ArgumentException when key already exists.
  • Index assignment dict[key] = value inserts or overwrites.
csharp
1using System;
2using System.Collections.Generic;
3
4var map = new Dictionary<string, int>();
5map.Add("A", 1);
6
7try
8{
9    map.Add("A", 2);
10}
11catch (ArgumentException ex)
12{
13    Console.WriteLine(ex.Message);
14}
15
16map["A"] = 2; // overwrite
17Console.WriteLine(map["A"]); // 2

Neither is universally correct. Choose behavior based on domain rules.

Choosing a Collision Policy

Common policies are:

  • Reject duplicates and surface validation error.
  • Last value wins.
  • First value wins.
  • Merge values using aggregation.

For financial or identity data, fail fast is often safest. For telemetry ingestion, merge or overwrite may be acceptable.

Use a helper that makes policy obvious at the call site.

csharp
1public static class DictionaryExtensions
2{
3    public static void AddOrMerge<TKey, TValue>(
4        this Dictionary<TKey, TValue> dict,
5        TKey key,
6        TValue value,
7        Func<TValue, TValue, TValue> merge)
8        where TKey : notnull
9    {
10        if (dict.TryGetValue(key, out var existing))
11            dict[key] = merge(existing, value);
12        else
13            dict.Add(key, value);
14    }
15}

This avoids hidden overwrite behavior scattered across code.

ToDictionary and External Data

Enumerable.ToDictionary throws on duplicate keys. That is useful for strict validation but dangerous when input is uncontrolled.

csharp
1using System.Linq;
2
3var rows = new[]
4{
5    (UserId: "u1", Score: 10),
6    (UserId: "u1", Score: 12),
7    (UserId: "u2", Score: 7)
8};
9
10var safe = rows
11    .GroupBy(r => r.UserId)
12    .ToDictionary(
13        g => g.Key,
14        g => g.Max(x => x.Score));

Grouping first makes duplicate handling explicit and reproducible.

Comparers and Logical Duplicates

Key uniqueness depends on comparer, not raw text. This matters for case-insensitive identifiers.

csharp
1var users = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
2users["ALICE"] = "Admin";
3users["alice"] = "Reader";
4
5Console.WriteLine(users.Count); // 1
6Console.WriteLine(users["Alice"]); // Reader

If business logic treats keys as case-insensitive, configure comparer at dictionary creation instead of normalizing ad hoc in many places.

Performance Considerations

For high-volume ingestion:

  • Normalize keys once at the boundary.
  • Avoid repeated lookups by using TryGetValue.
  • Use merge functions that avoid allocations where possible.

If duplicates are rare but expensive, count collisions and log rates. A sudden increase in duplicate count usually signals upstream data-quality drift.

Testing Duplicate Behavior

Write tests that cover policy decisions directly:

  • Duplicate rejected.
  • Duplicate overwritten.
  • Duplicate merged correctly.
  • Case-insensitive collisions handled as intended.

These tests are cheap and prevent accidental behavior changes during refactoring.

Pipeline-Friendly Ingestion Example

When ingesting key-value records from files, parse and merge in one pass while tracking collisions for observability.

csharp
1var result = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
2int duplicates = 0;
3
4foreach (var row in new[] { ("A", 1), ("a", 2), ("B", 3) })
5{
6    if (result.ContainsKey(row.Item1)) duplicates++;
7    result.AddOrMerge(row.Item1, row.Item2, (oldV, newV) => oldV + newV);
8}
9
10Console.WriteLine($"duplicates: {duplicates}");

This style keeps behavior deterministic and gives operators a clear signal when upstream data quality changes.

Common Pitfalls

  • Assuming ToDictionary silently handles duplicates.
  • Overwriting values unintentionally with index assignment.
  • Forgetting comparer behavior when handling user-provided keys.
  • Mixing collision policies across modules.
  • Skipping metrics on duplicate rates in data pipelines.

Summary

  • .NET dictionaries enforce unique keys under the configured comparer.
  • Add throws, while index assignment overwrites existing values.
  • External input should use explicit duplicate policies.
  • Comparer choice is part of correctness, not just style.
  • Add tests and metrics so collision behavior stays intentional.

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.