Freeze Your Lookups: FrozenDictionary & FrozenSet for Hot-Path Reads

If you’ve ever built a Dictionary or HashSet at startup, say for config values, feature flags, or permission sets, and then hammered it with reads for the lifetime of your app, .NET has a gift for you: System.Collections.Frozen.

FrozenDictionary<TKey, TValue> and FrozenSet<T> are immutable, read-optimized collections. You build them once from an existing collection, and in return the runtime produces an internal layout that’s specifically tuned for the keys you gave it. The trade-off is simple: construction is slower than a normal dictionary (the runtime analyzes your keys to pick an optimal strategy), but every subsequent lookup is faster, often dramatically on hot paths.

The API couldn’t be easier. You already have the extension methods you need:

using System.Collections.Frozen;
// Built once at startup from config, a database, etc.
var configMap = new Dictionary<string, string>
{
["FeatureX:Enabled"] = "true",
["FeatureX:MaxRetries"] = "3",
["Cache:SlidingExpiration"] = "00:05:00",
["Logging:MinLevel"] = "Warning"
};
var enabledFeatures = new HashSet<string>
{
"dark-mode", "beta-search", "new-checkout-flow"
};
// One call. Every read after this is faster
FrozenDictionary<string, string> frozenConfig = configMap.ToFrozenDictionary();
FrozenSet<string> frozenFeatures = enabledFeatures.ToFrozenSet();

Once frozen, the read API is exactly what you’re used to with ContainsKey, TryGetValue, indexer access, and Contains. So, it’s a drop-in replacement anywhere you have a build-once, read-many collection:

string retries = frozenConfig["FeatureX:MaxRetries"];
if (frozenConfig.TryGetValue("Cache:SlidingExpiration", out var expiration))
Console.WriteLine($"Sliding expiration: {expiration}");
bool betaSearchOn = frozenFeatures.Contains("beta-search");

How much faster? In a quick micro-benchmark doing 10 million lookups against a small collection, the frozen variants were roughly 2× faster than their mutable counterparts:

Collection10M Lookups
Dictionary<string, string>~90 ms
FrozenDictionary<string, string>~41 ms
HashSet<string>~72 ms
FrozenSet<string>~30 ms

The key thing to remember: these collections are immutable after creation. There’s no Add, Remove, or Clear. If you need to mutate, you’re back to a regular Dictionary or HashSet. But for the very common pattern of “load once, read forever”, FrozenDictionary and FrozenSet are a one-line upgrade that turns a hot path cold.