Designing a good key

The hard part of hashing is not the function — it is deciding what counts as the same thing.

Once you use a hash map for anything beyond primitives, the design question becomes: what makes two entries equal? Get that wrong and the map silently misbehaves.

Composite keys

Needing to key on two values is common. Three options, in order of preference:

// 1. A delimiter that cannot appear in the parts.
map.set(`${userId}:${date}`, value);

// 2. Nested maps — no string building, and each level is independently useful.
outer.get(userId)?.get(date);

// 3. A canonical JSON string, for keys with many fields.
map.set(JSON.stringify({ a, b }), value);

Option 1's hazard is the delimiter appearing inside a part: ("a:b", "c") and ("a", "b:c") collapse to the same key. Choose a separator the data cannot contain, or escape it.

Option 3's hazard is key order: JSON.stringify({a: 1, b: 2}) and {b: 2, a: 1} produce different strings for the same logical object. Sort the fields first, or build the string explicitly.

Normalise before keying

Two values that should be equal must produce the same key:

const key = email.trim().toLowerCase();          // users type inconsistently
const nameKey = name.normalize('NFC');           // accents have two encodings
const pathKey = path.replace(/\/+$/, '');        // trailing slashes

This is where real bugs live — duplicate accounts, missed cache hits, double-counted metrics — and none of them look like hashing bugs when you meet them.

Object identity

const cache = new Map();
cache.set({ id: 1 }, 'a');
cache.get({ id: 1 });        // undefined — different object, different reference

JavaScript maps compare objects by reference. Key on the id. In Java or C#, override equals and hashCode together — an object whose fields change after insertion becomes unfindable in the map that holds it, which is why keys should be immutable.

Choosing the key is choosing the semantics

In the LRU cache it was the request key; in group-anagrams it was the sorted letters; in a rate limiter it is userId:minute. Each choice defines what the system treats as identical.

Say your key design out loud in interviews: "I will key on userId:hour, so a user's requests bucket per hour, and I will normalise the id to lowercase first." That sentence covers correctness, granularity and the edge case in one go.