Using Objects as Keys in JavaScript Maps: Short Notes on Key Equality

Published: April 17, 2023

JavaScript Maps are powerful data structures that store key-value pairs, allowing keys to be of any data type, including objects. However, using objects as keys in Maps can lead to unexpected results due to how JavaScript handles object comparisons. In this post, I explore the nuances of using objects as keys in Maps and provide best practices to achieve desired behavior.

The Challenge with Objects as Keys

A Map holds key-value pairs where the keys can be any datatype (including objects), so being able to use objects as keys is an important Map feature. Let's consider the following TypeScript code snippet that attempts to use objects as keys in a Map:

let testMap = new Map<{ a: number; b: number }, number>();
testMap.set({ a: 1, b: 2 }, 1);
testMap.set({ a: 1, b: 2 }, 2);
testMap.set({ a: 1, b: 2 }, 3);
console.log(testMap);

This unfortunately does not work as expected... testMap logs as:

Map(3) {
  { a: 1, b: 2 } => 1,
  { a: 1, b: 2 } => 2,
  { a: 1, b: 2 } => 3
}

It seems that the Map has ended up with duplicate keys. The reason for this is because, even though primitives are passed by value (such as strings), Objects are passed by "copy of a reference".

Objects in JavaScript are Compared by Reference

Unlike primitives (e.g., strings, numbers), objects in JavaScript are compared by reference, not by value. This means that when you use an object as a key in a Map, JavaScript compares the references of the objects, not their actual key-values. As a result, two objects with identical key-values are considered different if they have different references.

{ a: 1, b: 2 } === { a: 1, b: 2 }

will always returns false, because the two Objects are not the same two Objects, despite having the same values.

To ensure the correct behavior when using objects as keys in Maps, it's essential to use a single reference to the object as the key, rather than creating multiple objects with the same key-values:

let testMap2 = new Map<{ a: number; b: number }, number>();
 
let key = { a: 1, b: 2 };
 
testMap2.set(key, 1);
testMap2.set(key, 2);
testMap2.set(key, 3);
 
console.log(testMap2);

Which now happily returns:

Map(1) {
     { a: 1, b: 2 } => 3
}

By using a single object reference (key) as the key in the Map, we ensure that all three key-value pairs are associated with the same object.

Conclusion

When using objects as keys in JavaScript Maps, it's essential to be aware of how JavaScript handles object comparisons by reference. Always use a single object reference as the key to avoid unexpected behavior and ensure key equality.