Q1With const ids = new Set(["u-1", "u-2"]);, run ids.add("u-1");. What's ids.size?
Map and Set — Removing Duplicates and Keyed Tallies
Learn Set, which holds each value only once, and Map, which keeps key-value pairs in insertion order.
Collect the tags attached to a product into an array, and the same tag keeps turning up — knowing how many distinct ones there actually are means checking with includes every time. Wanting to hold a key-value pair, like a stock count keyed by product ID, hits the same wall with plain arrays: you'd need two separate arrays for IDs and quantities lined up by position, and an object's keys are limited to strings and symbol, so a number can't be used as a key as-is.
This article covers Set, which never holds duplicates, and Map, which can use more than just strings as keys.
Removing Duplicates — Building a Set
Say you want to know how many distinct tags are actually in use across a list of tagged products. In a plain array, the same tag shows up more than once, so counting it as-is inflates the total. You need a data structure that holds duplicates removed from the start.
A Set (a data structure holding each value only once) is built by passing an array, as in new Set(tags). The new at the front is syntax you put before creating a new instance of a fixed kind of data structure, like Set or Map.
Since the same value never adds a duplicate, reading size alone tells you the number of distinct values. Whether two values count as the same follows the same rule as ===.
A Set isn't an array, so an index like uniqueTags[0] won't read anything back. When you need it as an array, convert it with spread syntax, as in [...uniqueTags].
Order stays exactly as items were first added, so the original array's order of first appearance carries through unchanged.
const tags = ["Sale", "New", "Sale", "Free Shipping"];
// Pass an array, and you get a Set with duplicates removed
const uniqueTags = new Set(tags);
console.log(uniqueTags.size); // 3
// Convert back to an array with spread syntax (order of addition preserved)
console.log([...uniqueTags].join(", ")); // Sale, New, Free Shipping
// The original array is untouched
console.log(tags.length); // 4
// You can also start from an empty Set
const empty = new Set();
console.log(empty.size); // 0
Adding and Removing Values — add, has, and delete
Sometimes you want to log visitor IDs and count the same person as 1 no matter how many times they come back. Keep this in an array, and every addition means checking with includes before push — the more records pile up, the longer that check takes.
A Set has add for adding a value, has for checking whether it has one, delete for removing one, and size for the count. add does nothing when the same value comes in, so you never need to check before adding.
has returns true or false, and delete returns whether it actually removed something.
adding the same value any number of times never grows the count. Only removing shrinks it.const visitors = new Set(["u-102", "u-337"]);
// Check whether it has one
console.log(visitors.has("u-102")); // true
console.log(visitors.has("u-999")); // false
// Add one. The same value doesn't change the count
visitors.add("u-541");
console.log(visitors.size); // 3
visitors.add("u-541");
console.log(visitors.size); // 3
// Remove one. Whether it actually removed something comes back
console.log(visitors.delete("u-102")); // true
console.log(visitors.delete("u-102")); // false (already gone)
console.log(visitors.size); // 2
Holding Key-Value Pairs — Map
Sometimes you want to hold a key and value as a pair, like a stock count keyed by product ID. An object can do this too, but for tallying keys that are decided at runtime — like IDs — and adjusting them over time, a data structure with dedicated methods is better suited.
A Map (a data structure holding key-value pairs in the order they were registered) is built with new Map(), and you register entries with set(key, value). To read one, use get(key); to check whether one exists, has(key); for the count, size.
set the same key again, and the value gets overwritten. To start with entries already filled in, pass an array of pairs, as in new Map([["P-001", 12], ["P-002", 5]]).
set registers a pair, and get looks up a value by key. A key with no entry returns undefined.const stock = new Map();
// Register a pair with set
stock.set("P-001", 12);
stock.set("P-002", 5);
console.log(stock.size); // 2
// Look up a value from a key with get
console.log(stock.get("P-002")); // 5
console.log(stock.get("P-999")); // undefined
console.log(stock.has("P-999")); // false
// set the same key again, and it overwrites
stock.set("P-001", 8);
console.log(stock.get("P-001")); // 8
console.log(stock.size); // 2 (the count doesn't grow)
// You can also build it with pairs already filled in
const prices = new Map([["P-001", 3980], ["P-002", 2480]]);
console.log(prices.get("P-001")); // 3980
A Map's Values Can't Be Read with Dot Notation
Write stock["P-001"] instead of stock.get("P-001"), and you won't get the value back. That syntax looks for a property on the Map object itself, which has nothing to do with the pairs you registered, so it returns undefined. Route reading and writing through get and set.
Choosing Between Map and Object — Key Type and Order
Since it holds keys and values just like an object does, you choose between them. When the keys are fixed at the time you write the code, like settings fields, an object works; when you're tallying keys that grow at runtime, like IDs or dates, Map is easier to work with.
Key type and ordering are what you base that decision on.
An object's keys get converted to strings, so even writing record[2026], the actual key is "2026". A Map uses whatever value you pass as the key exactly as it is, so the number 2026 and the string "2026" are treated as different keys.
Order works differently too — a Map always keeps the order things were registered in.
| Aspect | Object | Map |
|---|---|---|
| Types allowed as keys | Converted to strings | Any type, kept as-is |
| Ordering | Integer-like keys sort first | Stays in registration order |
| Counting entries | Object.keys' length | size |
| Reading & writing | obj.key / obj[key] | get and set |
| Best suited for | A fixed collection of fields | Tallying keys that grow at runtime |
To list out a Map's contents, write [...stock.entries()] to convert it into an array of [key, value] pairs, each a 2-element array. That lets you pull out one pair at a time, as in entries[0]. Repeating the same step once per pair, however many there are, is covered again in the article on loops.
When you need just the keys, use stock.keys() — but since what it returns isn't an array, you can't chain join onto it directly. Write [...stock.keys()] to convert it to an array first.
const scores = new Map([["math", 80]]);
// A Map keeps a number key as a number
scores.set(2026, "this year's tally");
console.log(scores.get(2026)); // this year's tally
console.log(scores.get("2026")); // undefined (a different key)
// An object's keys get converted to strings
const record = {};
record[2026] = "this year's tally";
console.log(Object.keys(record)[0]); // 2026
console.log(typeof Object.keys(record)[0]); // string
console.log(record["2026"]); // this year's tally (treated as the same key)
// Convert keys() / entries() to an array with spread syntax before using them
console.log([...scores.keys()].join(", ")); // math, 2026
const pairs = [...scores.entries()];
console.log(pairs[0].join(": ")); // math: 80
// An object sorts integer-like keys first
const ranking = {};
ranking[3] = "c";
ranking[1] = "a";
console.log(Object.keys(ranking).join(", ")); // 1, 3
Knowledge Check
Answer each question one by one.
Q2With stock as a Map, what does stock.get("P-999") return for an unregistered key?
Q3Register the number 2026 as a key. What's different between Map and an object?