How to Use Set and Map in JavaScript: An In-Depth Guide
JavaScript developers have access to several capable built-in data structures for storing and organizing information. Arrays and plain Objects cover a wide variety of use cases. However, the Set and Map objects provide additional unique functionality for handling data collections.
Sets excel at ensuring value uniqueness within groups and removing duplicates.
Maps improve over plain Objects by supporting any data type as keys while delivering solid performance.
Knowing when to utilize a Set or Map can significantly enhance what you build. This guide explores Sets and Maps in JavaScript in-depth from an advanced full-stack perspective.
You will learn:
- Real-world use cases where Sets and Maps shine over Arrays and Objects
- Guidelines for choosing the right data structure for your data
- Performance benchmarks and time complexity comparisons
- Initialization best practices and data manipulation methods
- In-depth look at Sets vs Arrays and Maps vs Objects
- Compatibility and adoption info for older browsers
- Examples and statistics illustrating appropriate usage
So whether you are a JavaScript expert looking to round out your data structure skills or building full-stack data-driven web apps, exploring Sets and Maps in depth will help take your code to the next level.
Common Use Cases for JavaScript Sets
While JavaScript Arrays certainly have broad utility, Sets open additional possibilities in key areas:
Removing Duplicate Values
Ensuring uniqueness within groups of values is a common need. With Array variables, you might have:
const fruits = [‘apple‘,‘banana‘,‘apple‘,‘orange‘];
The duplicate "apple" may cause issues down the line. To guarantee value uniqueness:
const uniqueFruits = new Set(fruits); // No duplicates
Much simpler than manually weeding them out!
Union, Intersection, and Difference Operations
Sets also enable set theory operations for things like:
- Finding overlap between data sets
- Combining multiple groups in different ways
For example, given two sets:
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
You can quickly intersect them to find values common to both:
const intersection = new Set([...setA].filter(x => setB.has(x))); // {2, 3}
Other possible combinations are unions (combine all values) or differences (values in first not second).
This is extremely useful functionality in practice when handling Venn diagrams of data sets.
Cached Lookups
Building unique sets allows leveraging faster O(1)
lookup times in many cases vs O(n)
with indexOf scans on arrays:
function contains(set, value) {
return set.has(value); // O(1) ops
}
function contains(array,value) {
return array.indexOf(value) > -1; // O(n) ops
}
So Sets can drastically simplify certain checks.
These are just a few examples where Sets provide elegant solutions and superior performance over array equivalents.
When to Use JavaScript Maps over Objects
While plain Objects are ubiquitous across JavaScript, Maps open additional possibilities:
Multi-Type Keys
One major constraint of plain Objects is keys must be either strings or symbols. This is not the case for Maps, where any value can be used as a key:
const userIDMap = new Map();
userIDMap.set(Date.now(), Math.random()); // Date & number as keys
The flexibility opens entirely new techniques for organizing data.
Key Iteration
It‘s simple to iterate the keys, values or key-value pairs stored in a JavaScript Map thanks to methods like:
map.keys()
map.values()
map.entries()
Combined with for..of
loops, this provides concise ways to access contents that plain Objects can‘t match without Object.keys()
, Object.values()
, Object.entries()
workarounds.
Higher Performance
JavaScript Maps also better optimize frequent insertions and removals of key-value pairs compared to plain Objects.
The difference may be negligible in smaller data sets but becomes pronounced at scale with 1000s or more entries.
So when building anything from caches to metadata stores, Maps will tend to outperform Objects storing equivalent data.
Between these core advantages, Maps become the obvious choice for certain data storage scenarios as you‘ll see through examples next.
Real-World Examples Using JavaScript Maps
Here are some concrete examples of applying JavaScript Maps for common programming objectives:
User Session Storage
Tracking logged in users and metadata using a Map:
const userSessions = new Map();
userSessions.set(‘userName123‘, {
lastActive: Date.now(),
ip: ‘127.0.0.1‘
});
userSessions.get(‘userName123‘).lastActive; // Access last active
Better than using plain objects with username strings as keys.
Data Validation
Validating forms and data types, using anything as keys:
const types = new Map();
types.set(String, val => typeof val === ‘string‘);
types.set(Number, val => typeof val === ‘number‘);
types.set(Boolean, val => typeof val === ‘boolean‘);
function validate(key, val) {
return types.get(key)(val); // Call validator fn
}
validate(String, ‘x‘); // true
validate(Number, ‘x‘); // false
Metadata Storage
Adding metadata to objects while separating concerns:
class User {
// Omit fields
}
const userNotes = new Map();
userNotes.set(user1, ‘Inactive‘); // Add notes by user
userNotes.get(user1); // Check notes
There are many other examples like implementing LRU caches, aggregates based on groups (using anything as a key), and custom collectible registries in games.
The common theme is using keys flexibly leads to simpler and better code organization.
Set and Map Performance Characteristics
So clearly Sets and Maps enable certain techniques that array and object equivalents can‘t match. But how do they actually perform under the hood?
The core performance advantage of Sets and Maps comes from leveraging hash tables (key-value stores) as implemented natively in JavaScript engines.
This contrasts arrays and objects which map sequentially to memory locations and require dynamic resizing at scale.
The result is faster lookups, inserts and removals for Sets and Maps across most operations:
To highlight the speed advantage, here is a benchmark inserting 1000 elements:
As you can see, the Set significantly outperforms equivalent array insertions as the number of elements scales.
Now keep in mind there are still use cases better served by arrays (indexing, splicing, etc) or plain objects (smaller data with string/symbol keys).
But for raw data storage and manipulation, Sets and Maps have superior performance by design.
Understanding these inherent performance advantages allows properly applying the right tool for particular data challenges.
Initializing JavaScript Sets and Maps
Now that you know where Sets and Maps provide value, how do you actually start using them?
First, Sets support initialization in a couple helpful ways:
No Args for Empty Set
const ids = new Set(); // empty
Values as Args
const roles = new Set([‘admin‘,‘manager‘]);
Arrays/Sets as Args
const newSet = new Set([...oldSet]); // Copy set
const noDupeArr = new Set(arrWithDupes); // De-dupe array
Some options for Maps:
No Args for Empty Map
const prices = new Map();
2D Key-Value Pairs
const fruitsCalories = new Map([
[‘apple‘, 95],
[‘orange‘, 45]
]);
Map of Maps
const data = new Map([
[‘key1‘, map1],
[‘key2‘, map2]
]);
Set as Keys
const cache = new Map();
cache.set(new Set([a,b]), v);
So any data types can be passed in Flexibly on initialization.
Now for data manipulation…
Modifying and Accessing Sets and Maps
Core methods allow interacting with data in Sets and Maps:
Sets
add()
/delete()
for writinghas()
checks membershipsize
gets lengthclear()
empties Setkeys()
,values()
,entries()
Maps
set()
/delete()
to writehas()
checks keysget()
accesses valuessize
gets sizeclear()
clearskeys()
,values()
,entries()
You iterate using for..of
loops:
// Set iterations
for (let item of set.values()) {}
// Map iterations
for (let [key, val] of map.entries()) {}
And standard Array methods like map()
or forEach()
work as well:
map.forEach((v, k) => {});
[...set].map(item => {});
So modifying and reading Sets and Maps stays simple and intuitive despite the power under the hood.
Sets vs Arrays: When to Use Each
Now you may be wondering…when should you actually prefer Sets vs simply using arrays in JavaScript?
Here is a breakdown of factors to consider:
Use a Set when you need:
- Unique values (removing duplicates)
- Faster lookups than
Array.prototype.indexOf()
- Set operations like unions and intersections
- Storage without indices or ordering
Use an Array when you require:
- Indexed or ordered access
- Additional Array methods like
.map()
,.filter()
etc - Integrating with other array-based code
- Sorting and comparing values
Other Considerations
- Compatibility: Sets require ES6/ES2015 support
- Performance: Sets faster for lookups, inserts, deletes
- Syntax: Array syntax/coding patterns more common
So if you need uniqueness, Set operations, or hashtable performance, choose Set. Otherwise arrays may be simpler.
Maps vs Plain Objects: Key Differences
Similarly, opting for a Map
over plain Object
comes with tradeoffs:
Use Maps when you need:
- Keys other than strings or symbols
- Faster large scale inserts and deletes
- Iteration methods like
.entries()
and.values()
- Better performance for frequent changes
Use Plain Objects when:
- Keys must be strings or symbols
- Smaller data sizes
- Prototypal inheritance is useful
- Less dynamic size changes
Additional Considerations
- Compatibility: Maps require ES6/ES2015+
- Performance: Maps faster for freq. inserts/deletes
- Syntax: Objects have more familiar syntax
So opt for Maps when leveraging non-string keys or requiring scaling performance. Else plain Objects may be preferable for familiarity and smaller data sizes.
Browser Compatibility and Support
As ES6/ES2015 features, Sets and Maps are not supported in legacy JavaScript environments like old IE without polyfills or transpilation.
However, most modern browsers have implemented native support with >95% global usage as of 01/2023:
So you can utilize Sets and Maps in most situations. But ensure fallback behavior if supporting older browsers.
Transpilers like Babel allow compiling ES6+ features to ES5 equivalents. Polyfill shims also exist for Sets and Maps.
For server-side/backend JavaScript, platforms like Node.js have included native Set/Map implementations since v6+ LTS versions (Oct 2016).
So concerns around compatibility continue fading over time.
Tips for Effective Usage
Here are some best practices for clean and effective usage of Sets and Maps in your code:
Validate values on insertion rather than after to avoid issues. For example with Maps:
function setValue(key, val) { if (!validate(val)) { return; } data.set(key, val); // inserts if valid }
Leverage parameterized constructors to initialize populated Set/Maps cleanly:
function UserSet(users = []) { return new Set(users); } const admins = new UserSet([/* admin users */]);
Clear Sets or Maps fully before re-use to avoid data bleeding:
function fillMap(map) { map.clear(); // set data }
Use object references as keys for retrieval later:
const cache = new Map(); cache.set(obj, ‘Cached data‘); cache.get(obj); // Retrieve via original reference
These tips help avoid common pitfalls when working with Sets and Maps in JavaScript.
Conclusion
Set and Map were once niche features in JavaScript but now enjoy widespread support. They enable:
- Guaranteed unique values with
Set
- Flexible key types beyond strings with
Map
These open entire categories of solutions previously tricky or inefficient in native JavaScript.
Knowing how to properly apply Sets & Maps leads to cleaner code and better runtime performance. Sorting unique data, complex lookups, key-value metadata are all great candidates.
Plain arrays and objects still dominate JavaScript. But augmenting your data structure skills with Sets and Maps provides extremely valuable additions to your programming toolkit.
I encourage you to try integrating Sets and Maps into your projects where applicable to reap the benefits!