JavaScript Maps, WeakMaps, and Map.groupBy()
Explore how Map
, WeakMap
, and Map.groupBy()
work in JavaScript. Learn the differences between Maps and plain objects,
how WeakMap
handles memory safely, and how to group values using custom groupBy()
.
Map
The Map
object holds key-value pairs and remembers the original insertion order of the keys.
Keys can be any value, including objects, functions, and NaN
. Unlike objects, maps offer consistent
key ordering and better performance for frequent additions and deletions.
Instance methods:
Map.prototype.clear()
Map.prototype.delete()
Map.prototype.entries()
Map.prototype.forEach()
Map.prototype.get()
Map.prototype.has()
Map.prototype.keys()
Map.prototype.set()
Map.prototype.values()
Instance properties:
Map.prototype.size
const map = new Map();
map.set("name", "Alice");
map.set("age", 25);
map.set("country", "USA");
console.log(map);
// Map(3) { 'name' => 'Alice', 'age' => 25, 'country' => 'USA' }
// Size
console.log(map.size); // 3
// Accessing values
console.log(map.get("name")); // Alice
// Checking for a key
console.log(map.has("age")); // true
// Removing a key
map.delete("country");
console.log(map);
// Map(2) { 'name' => 'Alice', 'age' => 25 }
map.set("city", "New York");
map.set("hobby", "Painting");
// Using for...of
console.log("Using for...of:");
for (const [key, value] of map) {
console.log(key, ":", value);
}
// Getting all keys, values, or entries
console.log("Keys:", [...map.keys()]);
// Keys: [ 'name', 'age', 'city', 'hobby' ]
console.log("Values:", [...map.values()]);
// Values: [ 'Alice', 25, 'New York', 'Painting' ]
console.log("Entries:", [...map.entries()]);
// Entries: [
// [ 'name', 'Alice' ],
// [ 'age', 25 ],
// [ 'city', 'New York' ],
// [ 'hobby', 'Painting' ]
// ]
// Clearing the map
map.clear();
console.log(map); // Map(0) {}
// Creating a Map with an array of entries
const mapFromArray = new Map([
["fruit", "Apple"],
["color", "Red"],
["quantity", 10],
]);
console.log(mapFromArray);
// Map(3) { 'fruit' => 'Apple', 'color' => 'Red', 'quantity' => 10 }
// Converting a Map back to an object
const obj = Object.fromEntries(mapFromArray);
console.log(obj);
// { fruit: 'Apple', color: 'Red', quantity: 10 }
// Map vs Object comparison
const objExample = { a: 1, b: 2, c: 3 };
const mapExample = new Map(Object.entries(objExample));
console.log(mapExample);
// Map(3) { 'a' => 1, 'b' => 2, 'c' => 3 }
// Keys of different types
const map2 = new Map();
const keyObj = {};
const keyFunc = function () {};
map2.set("hello", "string value");
map2.set(keyObj, "obj value");
map2.set(keyFunc, "func value");
map2.set(NaN, "NaN value");
console.log(map2);
// Map(4) {
// 'hello' => 'string value',
// {} => 'obj value',
// [Function: keyFunc] => 'func value',
// NaN => 'NaN value'
// }
console.log(map2.get("hello")); // string value
console.log(map2.get(keyObj)); // obj value
console.log(map2.get(keyFunc)); // func value
console.log(map2.get(NaN)); // NaN value
WeakMap
The WeakMap
object is a special kind of map whose keys must be objects, and those keys are weakly referenced — meaning they do not prevent garbage collection.
This makes WeakMap
useful for storing metadata or private data tied to object lifetimes.
However, it does not support iteration or methods like .keys()
or .clear()
.
weakMap.set(key, value)
weakMap.get(key)
weakMap.delete(key)
weakMap.has(key)
If an object has lost all other references, then it is to be garbage-collected automatically. But technically it’s not exactly specified when the cleanup happens
let john = { name: "John" };
const map = new Map();
map.set(john, "...");
john = null;
console.log(map.get(john)); // undefined
console.log(map.has(john)); // false
console.log(map.keys());
// [Map Iterator] { { name: 'John' } }
let doe = { name: "Doe" };
const weakMap = new WeakMap();
weakMap.set(doe, "...");
doe = null;
console.log(weakMap.get(doe)); // undefined
// weakMap.keys() not supported
Map.groupBy
The Map.groupBy()
static method groups elements from an iterable based on the return value of a callback function.
It returns a new Map
where each key is the grouping criterion and each value is an array of items in that group.
This example provides a polyfill-style custom implementation of Map.groupBy()
and demonstrates grouping numbers,
objects by property, and even using objects as keys for advanced use cases.
// Map.groupBy isn't available yet
function groupBy(array, callback) {
const map = new Map();
for (const item of array) {
const key = callback(item);
const group = map.get(key) || [];
group.push(item);
map.set(key, group);
}
return map;
}
// Using Map.groupBy to group numbers by even and odd
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const groupedNumbers = groupBy(numbers, (num) =>
num % 2 === 0 ? "even" : "odd",
);
console.log(groupedNumbers);
// Map(2) {
// 'odd' => [1, 3, 5, 7, 9],
// 'even' => [2, 4, 6, 8]
// }
// Grouping people by their age group
const people = [
{ name: "Alice", age: 25 },
{ name: "Bob", age: 30 },
{ name: "Charlie", age: 25 },
{ name: "David", age: 35 },
];
const groupedByAge = groupBy(people, (person) => person.age);
console.log(groupedByAge);
// Map(3) {
// 25 => [ { name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 } ],
// 30 => [ { name: 'Bob', age: 30 } ],
// 35 => [ { name: 'David', age: 35 } ]
// }
const inventory = [
{ name: "asparagus", type: "vegetables", quantity: 9 },
{ name: "bananas", type: "fruit", quantity: 5 },
{ name: "goat", type: "meat", quantity: 23 },
{ name: "cherries", type: "fruit", quantity: 12 },
{ name: "fish", type: "meat", quantity: 22 },
];
// Using objects as keys
const restock = { restock: true };
const sufficient = { restock: false };
const result = groupBy(inventory, ({ quantity }) =>
quantity < 6 ? restock : sufficient,
);
console.log(result);
// Map(2) {
// { restock: false } => [
// { name: 'asparagus', type: 'vegetables', quantity: 9 },
// { name: 'goat', type: 'meat', quantity: 23 },
// { name: 'cherries', type: 'fruit', quantity: 12 },
// { name: 'fish', type: 'meat', quantity: 22 }
// ],
// { restock: true } => [ { name: 'bananas', type: 'fruit', quantity: 5 } ]
// }
console.log(result.get(restock));
// [{ name: "bananas", type: "fruit", quantity: 5 }]