---
title: 'Map and Set'
source: 'https://academia.sh/en/courses/javascript-object-model/map-and-set'
course: 'Objects and Functions in JavaScript'
language: en
updated: '2026-08-23T07:00:59+00:00'
license: 'CC BY-SA 4.0'
---

# Map and Set

The limits of using an object as a dictionary; a mapping type that preserves key identity, sets of unique values, and a comparison of the two setups on grouping.

The prototype-less object appeared in this course's first lesson, and the same
precaution came up again in later lessons: in the calibration table, in the memoization
cache, in the grouping accumulator. The reason was the same every time — in a mapping
whose keys come from outside the program, names coming from the object's chain corrupted
the result.

This is not the only limit of using an object as a dictionary. Keys are converted to
strings, the ordering rule is different from what you'd expect, and element count cannot
be read directly. The language offers two types for this job: `Map` for key–value pairs,
`Set` for unique values. The lesson first shows the limits, then introduces the two
types.

## The Limits of an Object as a Dictionary

An object property's key can only be a string or a symbol. When a key of another type is
given, it is converted to a string.

```js
const dictionary = {};
const keyObject = { sensor: "S-01" };
const otherObject = { sensor: "S-02" };

dictionary[1] = "number one";
dictionary["1"] = "string one";
dictionary[true] = "boolean";
dictionary[keyObject] = "first object";
dictionary[otherObject] = "second object";

console.log(Object.keys(dictionary).join(" | "));
console.log(dictionary[1]);
console.log(dictionary[keyObject]);
console.log(Object.keys(dictionary).length);

const ordered = {};
ordered["S-02"] = 1;
ordered[10] = 2;
ordered["S-01"] = 3;
ordered[2] = 4;
console.log(Object.keys(ordered).join(","));

const dirty = { sensor: "S-01" };
console.log("toString" in dirty);
console.log(Object.hasOwn(dirty, "toString"));
console.log(typeof dirty["toString"]);
```

```
1 | true | [object Object]
string one
second object
3
2,10,S-02,S-01
true
false
function
```

Three limits are visible. First, `1` and `"1"` are the same key; the second assignment
overwrote the first. The second is heavier: two different objects convert to the same
`"[object Object]"` string, so they collapse into a single key — objects cannot be used
as keys. The third is ordering: keys that look like integers are moved to the front in
ascending numeric order, others keep insertion order.

The last three lines repeat a familiar problem. There is no key named `toString` in the
dictionary, but the query finds it; whenever keys come from outside, `Object.hasOwn` or a
prototype-less object has to be used.

## A Mapping That Preserves Key Identity

`Map` does not convert keys. A value of any type can be a key, and comparison is done by
identity.

```js
const keyObject = { sensor: "S-01" };
const otherObject = { sensor: "S-02" };

const ledger = new Map();
ledger.set(1, "number one");
ledger.set("1", "string one");
ledger.set(keyObject, "first object");
ledger.set(otherObject, "second object");
ledger.set(NaN, "undefined measurement");

console.log(ledger.size);
console.log(ledger.get(1));
console.log(ledger.get("1"));
console.log(ledger.get(keyObject));
console.log(ledger.get({ sensor: "S-01" }));
console.log(ledger.get(NaN));
console.log(ledger.has(otherObject));

ledger.delete(otherObject);
console.log(ledger.size);

const keyTypes = [...ledger.keys()].map((a) => typeof a).join(",");
console.log(keyTypes);

const ordered = new Map();
ordered.set("S-02", 1);
ordered.set(10, 2);
ordered.set("S-01", 3);
ordered.set(2, 4);
console.log([...ordered.keys()].join(","));
```

```
5
number one
string one
first object
undefined
undefined measurement
true
4
number,string,object,number
S-02,10,S-01,2
```

`1` and `"1"` are separate keys. An object key is stored by its **identity**: querying
with another object of the same content finds nothing. The identity–equality distinction
from the Variables and Binding lesson in the Programming Fundamentals course applies
directly here.

The `NaN` key being findable points to a detail: comparison is not done with strict
equality, it is done with a rule that counts `NaN` as equal to itself. The same rule
applies to `Set` too.

The last line shows the ordering rule: `Map` preserves only insertion order, it does not
move numeric keys to the front. The `size` property gives the element count directly; the
object needs all its keys listed for the same information.

Its relationship to hash tables is set up here too. The average constant-cost access
examined in the Hash Tables lesson of the Data Structures course is the behavior
underlying both object properties and the `Map` structure; `Map` is its form that
preserves key identity.

## Conversions and Traversal

Two-way conversion between `Map` and an object is built in. In serialization, though, the
difference matters.

```js
const recordObject = { "S-01": 21.4, "S-02": 19.8, "S-03": 18.2 };

const mapFromObject = new Map(Object.entries(recordObject));
console.log(mapFromObject.size);
console.log(mapFromObject.get("S-02"));

const backToObject = Object.fromEntries(mapFromObject);
console.log(JSON.stringify(backToObject));

console.log(JSON.stringify(mapFromObject));
console.log(JSON.stringify([...mapFromObject]));

const fromPairs = new Map([
  ["S-01", 21.4],
  ["S-02", 19.8],
]);
console.log([...fromPairs.entries()].map(([a, d]) => `${a}=${d}`).join(" "));

let total = 0;
for (const [, value] of mapFromObject) total += value;
console.log(total.toFixed(1));

mapFromObject.forEach((value, key) => {
  if (key === "S-01") console.log(`forEach: ${key} -> ${value}`);
});
```

```
3
19.8
{"S-01":21.4,"S-02":19.8,"S-03":18.2}
{}
[["S-01",21.4],["S-02",19.8],["S-03",18.2]]
S-01=21.4 S-02=19.8
59.4
forEach: S-01 -> 21.4
```

The fourth line is critical: `JSON.stringify` serializes a `Map` object as an empty
object. The reason was established in the first lesson — serialization works with own
**enumerable properties**, and a `Map`'s content is not held as a property. If the data
needs to travel, it has to be converted to an array of pairs. The general solution to the
same problem is taken up in the course's last lesson.

The `for...of` loop gives one pair per step; destructuring can separate the key and
value. Because the `forEach` method's argument order is reversed — value first, then key
— it is open to being mixed up.

## Unique Values

`Set` is a collection that does not hold the same value more than once. It is the
language's counterpart to the set introduced in the Sets and Multisets lesson of the Data
Structures course.

```js
const readSensors = ["S-01", "S-02", "S-01", "S-03", "S-02", "S-01"];

const unique = new Set(readSensors);
console.log(unique.size);
console.log([...unique].join(","));
console.log(unique.has("S-02"));
console.log(unique.has("S-99"));

const numberSet = new Set([1, 1, NaN, NaN, 0, -0]);
console.log(numberSet.size);
console.log([...numberSet].join(","));

const objectSet = new Set([{ sensor: "S-01" }, { sensor: "S-01" }]);
console.log(objectSet.size);

const underMaintenance = new Set(["S-02", "S-04"]);
const intersection = [...unique].filter((s) => underMaintenance.has(s));
const difference = [...unique].filter((s) => !underMaintenance.has(s));
const union = [...new Set([...unique, ...underMaintenance])];

console.log(intersection.join(","));
console.log(difference.join(","));
console.log(union.join(","));
```

```
3
S-01,S-02,S-03
true
false
3
1,NaN,0
2
S-02
S-01,S-03
S-01,S-02,S-03,S-04
```

Deduplication reduces to a single line, and insertion order is preserved. The numeric
example clarifies the comparison rule: two `NaN` values are counted as the same — a
behavior that does not hold under strict equality — and, likewise, `0` and `-0` are also
counted as the same. Objects are still compared by identity; two objects with the same
content are separate elements.

Set operations benefit from the `has` query being average constant cost. Doing the same
operation with arrays requires a linear search for every element and pushes the cost up
to the product of the two sets' sizes.

## Comparison in Grouping

The grouping example from the Higher-Order Functions lesson is a suitable test for
placing the two setups side by side. Since sensor names come from a data source, a name
like `constructor` could well be among them.

```js
const records = [
  { sensor: "S-01", value: 21.4 },
  { sensor: "S-02", value: 19.8 },
  { sensor: "S-01", value: 25.1 },
  { sensor: "constructor", value: 0.5 },
];

function naiveGroup(list) {
  const group = {};
  for (const record of list) {
    if (!group[record.sensor]) group[record.sensor] = [];
    group[record.sensor].push(record.value);
  }
  return group;
}

function groupWithObject(list) {
  const group = {};
  for (const record of list) {
    if (!Array.isArray(group[record.sensor])) group[record.sensor] = [];
    group[record.sensor].push(record.value);
  }
  return group;
}

function groupWithMap(list) {
  const group = new Map();
  for (const record of list) {
    if (!group.has(record.sensor)) group.set(record.sensor, []);
    group.get(record.sensor).push(record.value);
  }
  return group;
}

try {
  naiveGroup(records);
} catch (error) {
  console.log(`naive version: ${error.constructor.name}`);
}

const objectGroup = groupWithObject(records);
const mapGroup = groupWithMap(records);

console.log(Object.keys(objectGroup).join(","));
console.log([...mapGroup.keys()].join(","));
console.log(objectGroup["S-01"].join(","));
console.log(mapGroup.get("S-01").join(","));
console.log(objectGroup["constructor"].join(","));
console.log(mapGroup.get("constructor").join(","));
console.log(Object.keys(objectGroup).length === mapGroup.size);
```

```
naive version: TypeError
S-01,S-02,constructor
S-01,S-02,constructor
21.4,25.1
21.4,25.1
0.5
0.5
true
```

The naive version throws an error: the expression `group["constructor"]` finds the
function coming from the chain, this value is counted as true so no array is ever
created, and `push` is called on the function. The fixed version works because it
validates with a type check — but you have to know the fix is needed.

The version written with `Map` has no such trap; the `has` query does not look at the
chain, because `Map` content is not held as a property. As a rule: **use an object if
keys are fixed and written by the programmer, use `Map` if they come from a data
source.**

## Summary

- Object keys are converted to strings; different objects fall onto the same key, and
  keys that look like integers are moved to the front in ordering.
- `Map` does not convert keys, stores them by identity, preserves insertion order, and
  gives element count directly with `size`.
- The value `NaN` is counted as equal to itself in both `Map` and `Set`; objects are
  compared by identity.
- `JSON.stringify` serializes a `Map` object as an empty object; its content has to be
  converted to an array of pairs.
- `Set` reduces deduplication to a single step; set operations benefit from constant-cost
  queries.
- Use `Map` if keys come from a data source, an object if they are fixed and written in
  the program.

## Next Step

As long as a `Map` holds a key, that key object stays in memory. Tables set up to attach
extra information to an object therefore extend those objects' lifetime: every entry not
removed from the table keeps a no-longer-used object alive. The next lesson takes up weak
collections, which do not prevent the objects they hold from being collected, and the
role they play in private data storage.
