RizTech Academy logo
RizTech Academy
Objects and ArraysLesson 6 of 720 min

Map and Set, and when a plain object is wrong

The objects lesson ended on a problem: obj[1] and obj['1'] are the same property, because object keys are always strings. That is one of three jobs a plain object does badly, and Map and Set are the tools for them.

Set: a collection with no duplicates

const items = new Set(['dal', 'rice', 'dal']);

console.log(items.size);
console.log([...items]);
2
[ 'dal', 'rice' ]

The duplicate was dropped on the way in. A Set holds each value once, and that is essentially its entire purpose.

Operation Set Array
Add add(x) push(x)
Remove delete(x) splice after indexOf
Is it there has(x) includes(x)
How many size length
Duplicates Impossible Allowed
Order Insertion order Insertion order
Access by position No arr[0]

size, not length. Mixing them up gives undefined, and undefined in arithmetic gives NaN.

The one-line use you will reach for most is removing duplicates from an array:

const plates = [1, 2, 2, 3, 1];
console.log([...new Set(plates)]);
[ 1, 2, 3 ]

Into a Set to deduplicate, straight back out with spread.

has is the other reason to use one. array.includes(x) checks every item in turn; set.has(x) goes straight to it. For a handful of items that is irrelevant, but for a few thousand — checking each of 5,000 orders against a list of 5,000 delivered ids — the array version does 25 million comparisons and the Set version does 5,000 lookups. That is the difference between instant and a frozen page on a mid-range phone.

A Set compares with ===, so it deduplicates values but not equal-looking objects:

const objects = new Set([{ id: 1 }, { id: 1 }]);
console.log(objects.size);
2

Two different objects that look the same, exactly as {} === {} was false.

Map: a dictionary with real keys

A Map is object-like — keys to values — with three differences that matter.

const counts = new Map();

counts.set('dal', 2);
counts.set('rice', 1);

console.log(counts.get('dal'));
console.log(counts.size);
console.log(counts.has('ghee'));
2
2
false

Difference one: keys keep their type.

const m = new Map();
m.set(1, 'number key');
m.set('1', 'string key');

console.log(m.get(1));
console.log(m.get('1'));
console.log(m.size);
number key
string key
2

Two separate entries. The plain object in the objects lesson collapsed these into one. Any value can be a key — a number, a boolean, even an object:

const priya = { id: 1 };
const notes = new Map([[priya, 'no chilli']]);

console.log(notes.get(priya));
console.log(notes.get({ id: 1 }));
no chilli
undefined

Keyed by the object itself, not by what it contains. An identical-looking object is a different key.

Difference two: no inherited keys. A plain object inherits properties from its prototype, so 'toString' in {} is true and a user-supplied key called constructor can cause real trouble. A Map starts genuinely empty.

Difference three: it is built to be iterated.

for (const [item, count] of counts) {
  console.log(`${item}: ${count}`);
}
dal: 2
rice: 1

Directly iterable, in insertion order, no Object.entries needed.

Which to use

Use a plain object when Use a Map when
Keys are fixed and known as you write Keys arrive at run time
It is a record — a thing with fields It is a lookup table
You need JSON.stringify Keys are not strings
You add and remove often
You need .size

The JSON row is the one that decides most arguments. A Map does not survive JSON.stringify — it comes out as {} — so anything you send to a server or save to storage is usually better as a plain object. That is the next lesson.

Converting between them:

const obj = Object.fromEntries(counts);
console.log(obj);

const backToMap = new Map(Object.entries(obj));
console.log(backToMap.get('dal'));
{ dal: 2, rice: 1 }
2

A worked example

Counting items with each:

const sold = ['dal', 'rice', 'dal', 'atta', 'dal'];

const counts = new Map();
for (const item of sold) {
  counts.set(item, (counts.get(item) ?? 0) + 1);
}

console.log([...counts]);
[ [ 'dal', 3 ], [ 'rice', 1 ], [ 'atta', 1 ] ]

counts.get(item) ?? 0 handles the first sighting — ?? rather than || because a stored count of 0 must not be replaced. Last lesson's rule, doing real work.

Check your work

new Set(['dal', 'rice', 'dal']).size is 2. Duplicates are dropped on entry.

[...new Set(array)] removes duplicates and is the idiomatic one-liner.

A Set uses size, an array uses length. Using the wrong one gives undefined.

new Set([{ id: 1 }, { id: 1 }]).size is 2. Sets compare with ===, and two separate objects are never equal.

In a Map, 1 and '1' are different keys — size is 2. In a plain object they are the same key.

A Map keyed by an object only matches that same object. notes.get({ id: 1 }) is undefined even though it looks identical.

A Map iterates directly in insertion order, giving [key, value] pairs.

Object.fromEntries(map) converts to an object; new Map(Object.entries(obj)) converts back.

A Map does not survive JSON.stringify. Convert to an object first.

The counting example gives dal: 3, rice: 1, atta: 1. The ?? 0 is what handles an item being seen for the first time.

The 5,000-against-5,000 check. Measured on one run under Node: includes took about 2.5ms, set.has about 0.2ms — roughly twelve times faster, and the gap widens with size, because includes does more work for every extra item while has does not.

Both answers were identical, which is the point: this is a change you make for speed, not correctness. And 2.5ms is still imperceptible — so for a few hundred items, reach for whichever reads better. Build the Set when the list is large and you are checking against it repeatedly; building one to check a single value is wasted work.

Practice

  1. Build a Set from an array with duplicates and print its size and contents.
  2. Deduplicate an array of pincodes in one line.
  3. Use size on a Set and then deliberately use length. Note what you get, and what it does in arithmetic.
  4. Put two identical-looking objects in a Set and explain the size.
  5. Build a Map with both 1 and '1' as keys and confirm you get two entries. Then do the same with a plain object and confirm you get one.
  6. Key a Map by an object. Retrieve with the same variable, then with a fresh object that looks identical, and explain the undefined.
  7. Iterate a Map with for...of and destructuring.
  8. Convert a Map to an object and back.
  9. Count the items in ['dal','rice','dal','atta','dal'] with a Map. Then do it again with reduce into a plain object, and decide which you prefer.
  10. Harder. You have 5,000 order ids and a list of 5,000 delivered ids. Write the "which are undelivered" check twice — once with includes on an array, once with has on a Set — and time both with console.time. Do not guess the difference; measure it, then decide when the plain array is still the right choice.

Next: JSON — how all of this data travels to a server and back, and what it loses on the way.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship