Set
// This array has two copies of its first item
let myList = [1, 1, 2, 3, 5, 8, 13, "fibonacci"];
// The same thing using the Set API
let mySet = new Set();
mySet.add(1);
mySet.add(1); // this won't change mySet, since 1 is already in there
mySet.add(2);
mySet.add(3);
// ... this gets tedious
// An array can be turned into a set
// If you want to quickly remove all duplicates from an array, this is a good tool!
let mySet2 = new Set(myList);
mySet2.has(3); // true
mySet2.has(12); // false
// For...of loop iteration works
for (let item of mySet2) {
console.log("mySet contains", item);
}Map
// This object is a bird
var bird = {
genus: "corvus",
species: "corvax",
commonName: "raven",
};
// Here is a map using the same structure
var birdMap = new Map();
birdMap.set("genus", "corvus");
birdMap.set("species", "corvax");
birdMap.set("commonName", "raven");
birdMap.get("genus"); // 'corvus'
birdMap.has("genus"); // true
birdMap.has("corvus"); // false (keys only)
// for...of loops work on Maps, with key and value returned
for (let value of birdMap) {
console.log(value); // returns a series of arrays with key and value pairs
}
birdMap.entries(); // same as using for of
Object.entries(bird); // an object can be converted to a map using Object.entries method
birdMap2 = new Map(Object.entries(bird));They also have entries(), keys(), and values() methods.
let myMap = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
for (let [key, value] of myMap.entries()) {
console.log(`Key: ${key}, Value: ${value}`);
}
for (let key of myMap.keys()) {
console.log(`Key: ${key}`);
}
for (let value of myMap.values()) {
console.log(`Value: ${value}`);
}Map vs plain objects
Here are the main differences between a Map object and a plain object:
- Key types: In a plain object, keys are always strings (or symbols). In a
Map, keys can be any type of value, including objects, arrays, and even otherMaps. - Key ordering: In a plain object, the order of keys is not guaranteed. In
a
Map, the order of keys is preserved, and you can iterate over them in the order they were inserted. - Iteration: A
Mapis iterable, which means you can usefor...ofloops to iterate over its key-value pairs. A plain object is not iterable by default, but you can useObject.keys()orObject.entries()to iterate over its properties. - Performance:
Mapobjects are generally faster and more efficient than plain objects, especially when dealing with large datasets. - Methods: A
Mapobject provides additional methods, such asget,set,has, anddelete, which make it easier to work with key-value pairs. - Serialization: When serializing a
Mapobject to JSON, it will be converted to an object but the existingMapproperties might be lost in the conversion. A plain object, on the other hand, is serialized to a JSON object with the same structure.
Use a plain object (POJO) when:
- You need a simple, lightweight object with string keys.
- You’re working with a small dataset.
- You need to serialize the object to JSON (e.g. to send over the network).
Use a Map object when:
- You need to store key-value pairs with non-string keys (e.g., objects, arrays).
- You need to preserve the order of key-value pairs.
- You need to iterate over the key-value pairs in a specific order.
- You’re working with a large dataset and need better performance.
In summary, while both plain objects and Map objects can be used to store
key-value pairs, Map objects offer more advanced features, better performance,
and additional methods, making them a better choice for more complex use cases.
Map/Set vs WeakMap/WeakSet
The primary difference between Map/Set and WeakMap/WeakSet in JavaScript
lies in how they handle keys. Here’s a breakdown:
Map vs. WeakMap
Maps allows any data type (strings, numbers, objects) as keys. The key-value
pairs remain in memory as long as the Map object itself is referenced. Thus
they are suitable for general-purpose key-value storage where you want to
maintain references to both keys and values. Common use cases include storing
user data, configuration settings, or relationships between objects.
WeakMaps only allows objects as keys. However, these object keys are held
weakly. This means the garbage collector can remove them from memory even if the
WeakMap itself still exists, as long as there are no other references to those
objects. WeakMaps are ideal for scenarios where you want to associate data
with objects without preventing those objects from being garbage collected. This
can be useful for things like:
- Caching data based on objects without preventing garbage collection of the objects themselves.
- Storing private data associated with DOM nodes without affecting their lifecycle.
Set vs. WeakSet
Similar to Map, Sets allow any data type as keys. The elements within a
Set must be unique. Sets are useful for storing unique values and checking
for membership efficiently. Common use cases include removing duplicates from
arrays or keeping track of completed tasks.
On the other hand, WeakSet only allows objects as elements, and these object
elements are held weakly, similar to WeakMap keys. WeakSets are less
commonly used, but applicable when you want a collection of unique objects
without affecting their garbage collection. This might be necessary for:
- Tracking DOM nodes that have been interacted with without affecting their memory management.
- Implementing custom object weak references for specific use cases.
Here’s a table summarizing the key differences:
| Feature | Map | WeakMap | Set | WeakSet |
|---|---|---|---|---|
| Key Types | Any data type | Objects (weak references) | Any data type (unique) | Objects (weak references, unique) |
| Garbage Collection | Keys and values are not garbage collected | Keys can be garbage collected if not referenced elsewhere | Elements are not garbage collected | Elements can be garbage collected if not referenced elsewhere |
| Use Cases | General-purpose key-value storage | Caching, private DOM node data | Removing duplicates, membership checks | Object weak references, custom use cases |
Choosing between them
- Use
MapandSetfor most scenarios where you need to store key-value pairs or unique elements and want to maintain references to both the keys/elements and the values. - Use
WeakMapandWeakSetcautiously in specific situations where you want to associate data with objects without affecting their garbage collection. Be aware of the implications of weak references and potential memory leaks if not used correctly.
Symbol
Symbols in JavaScript are a unique and immutable data type used primarily for object property keys to avoid name collisions.
- Uniqueness: Each Symbol value is unique, even if they have the same description.
- Immutability: Symbol values are immutable, meaning their value cannot be changed.
- Non-enumerable: Symbol properties are not included in for…in loops or Object.keys().
Creating `Symbol`s using the `Symbol()` function
const sym1 = Symbol();
const sym2 = Symbol("uniqueKey");
console.log(typeof sym1); // "symbol"
console.log(sym1 === sym2); // false, because each symbol is uniqueThe
Symbol(..)function must be called without thenewkeyword.
`Symbol`s can be used to add properties to an object without risk of name collision
const obj = {};
const sym = Symbol("uniqueKey");
obj[sym] = "value";
console.log(obj[sym]); // "value"`Symbol`s are not enumerable
Symbolproperties are not included infor...inloops orObject.keys().- This makes them suitable for creating private/internal object state.
- Use
Object.getOwnPropertySymbols(obj)to get all symbol properties on an object.
const mySymbol = Symbol("privateProperty");
const obj = {
name: "John",
[mySymbol]: 42,
};
console.log(Object.keys(obj)); // Output: ['name']
console.log(obj[mySymbol]); // Output: 42Global `Symbol` registry
You can create global Symbols using Symbol.for('key'), which creates a new
Symbol in the global registry if it doesn’t exist, or returns the existing
one. This allows you to reuse Symbols across different parts of your code base
or even across different code bases.
const globalSym1 = Symbol.for("globalKey");
const globalSym2 = Symbol.for("globalKey");
console.log(globalSym1 === globalSym2); // true
const key = Symbol.keyFor(globalSym1);
console.log(key); // "globalKey"Well-known `Symbol`
JavaScript includes several built-in Symbols, referred as well-known
Symbols.
Symbol.iterator: Defines the defaultiteratorfor an object.Symbol.toStringTag: Used to create a string description for an object.Symbol.hasInstance: Used to determine if an object is an instance of a constructor.
`Symbol.iterator`
let iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 5) {
return { value: step, done: false };
}
return { done: true };
},
};
},
};
for (let value of iterable) {
console.log(value); // 1, 2, 3, 4, 5
}`Symbol.toStringTag`
let myObj = {
[Symbol.toStringTag]: "MyCustomObject",
};
console.log(Object.prototype.toString.call(myObj)); // "[object MyCustomObject]"Array methods
-
print a list of users from an object
// works // btw, forEach doesn't return anything const names = []; users.forEach((user) => { names.push(user.name); }); // best way const names = users.map((user) => user.name); -
filtering
// conditions const names = users.filter((user) => user.isActive).map((user) => user.name); -
sorting
// alphabetically users.sort((a, b) => a.firstname.localeCompare(b.firstname)); -
write a function which gets an array and an element and returns an array with this element at the end
const numbers = [1, 2]; const append = (arr, el) => { // don't use arr.push(el) because push is not safe (it doesn't create a new array) return [...arr, el]; // this is pure because it will return the same result every time, and it doesn't modify any variables outside of the function }; -
write a function which can concatenate 2 arrays
const mergeArrays = (arr1, arr2) => { // WRONG: push mutates array arr1.push(...arr2); return arr1; // RIGHT: concat doesn't mutate the array return arr1.concat(...arr2); // concat can even take multiple arguments return arr1.concat(...arr2, ...arr3); // OR return [...arr1, ...arr2]; }; -
check if an object exists
const isNameExists = (name, arr) => arr.some((el) => el.name === name); const isNameExists = (name, arr) => { const el = arr.find((el) => el.name === name); return Boolean(el); }; -
remove all duplicates
const uniqueArr = (arr) => { return [...new Set(arr)]; }; const uniqueArr = (arr) => { const result = []; arr.forEach((item) => { if (!result.includes(item)); result.push(item); }); return result; }; // more functional const uniqueArr = (arr) => { return arr.reduce((acc, el) => { return acc.includes(el) ? acc : [...acc, el]; }, []); }; -
sorting an array
// mutates arr arr.sort((a, b) => a - b); // for string books.sort((book1, book2) => { const authorLasttName1 = book1.author.split(" ")[1]; const authorLastName2 = book2.author.split(" ")[1]; return authorLastName1 < authorLastName2 ? -1 : 1; }); -
array reduce function
-
syntax:
array.reduce( callback(accumulator, currentValue, currentIndex, array), initialValue ); -
sum of array elements:
const numbers = [1, 2, 3, 4, 5]; const sum = numbers.reduce((accumulator, currentValue) => { return accumulator + currentValue; }, 0); console.log(sum); // Output: 15 -
flattening an array of arrays:
const nestedArray = [ [1, 2], [3, 4], [5, 6], ]; const flattenedArray = nestedArray.reduce((accumulator, currentValue) => { return accumulator.concat(currentValue); }, []); console.log(flattenedArray); // Output: [1, 2, 3, 4, 5, 6] -
counting instances of values in an object:
const fruits = [ "apple", "banana", "orange", "apple", "orange", "banana", "banana", ]; const countFruits = fruits.reduce((accumulator, currentValue) => { if (!accumulator[currentValue]) { accumulator[currentValue] = 0; } accumulator[currentValue]++; return accumulator; }, {}); console.log(countFruits); // Output: { apple: 2, banana: 3, orange: 2 }
-
-
check if user with given name exists
function userExists(users, name) { return users.some((user) => user.name === name); } // Example usage const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" }, { id: 3, name: "Charlie" }, ]; console.log(userExists(users, "Alice")); // Output: true console.log(userExists(users, "David")); // Output: false -
Find the number of occurrences of minimum value in list
function countMinOccurrences(arr) { if (arr.length === 0) { return 0; // Return 0 if the array is empty } const minValue = Math.min(...arr); const minOccurrences = arr.filter((num) => num === minValue).length; return minOccurrences; } // Example usage const numbers = [3, 1, 4, 1, 5, 1, 2, 1]; const occurrences = countMinOccurrences(numbers); console.log(occurrences); // Output: 4 -
Initialize a static array
With five elements, each initialized to its index + 1
const staticArray = Array.from({ length: 5 }, (value, index) => index + 1); console.log(staticArray); // Output: [1, 2, 3, 4, 5]With 5 elements, each initialized to 0
const staticArray = Array.from({ length: 5 }, () => 0);
Iterators and generators
In JavaScript, iterators and generators are powerful tools for managing sequences of data and controlling the flow of execution in a more flexible way.
Iterators are objects that define a sequence and potentially a return value upon its termination. It adheres to a specific interface:
- An iterator object must implement a
next()method. - The
next()method returns an object with two properties:value: The next value in the sequence.done: A boolean that istrueif the iterator has finished its sequence, otherwisefalse.
Example
const iterator = {
current: 0,
last: 5,
next() {
if (this.current <= this.last) {
return { value: this.current++, done: false };
} else {
return { value: undefined, done: true };
}
},
};
let result = iterator.next();
while (!result.done) {
console.log(result.value); // Logs 0, 1, 2, 3, 4, 5
result = iterator.next();
}Generators are a special functions that can pause execution and resume at
a later point. It uses the function* syntax and the yield keyword to
control the flow of execution. When you call a generator function, it doesn’t
execute completely like a regular function. Instead, it returns an iterator
object. Calling the next() method on the returned iterator advances the
generator to the next yield statement, and the value after yield becomes the
return value of next().
Example
function* numberGenerator() {
let num = 0;
while (num <= 5) {
yield num++;
}
}
const gen = numberGenerator();
console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: 4, done: false }
console.log(gen.next()); // { value: 5, done: false }
console.log(gen.next()); // { value: undefined, done: true }Generators are powerful for creating iterators on-demand, especially for infinite sequences or complex iteration logic. They can be used for:
- Lazy evaluation – processing elements only when needed, improving memory efficiency for large datasets.
- Implementing iterators for custom data structures.
- Creating asynchronous iterators for handling data streams.
Priority Queue / Heap
A Priority Queue is a data structure where each element has a priority. Elements with higher priority are dequeued before elements with lower priority. If two elements have the same priority, they are dequeued according to their order in the queue. JavaScript does not have a built-in Priority Queue, so you need to implement it manually or use a third-party library.
const { PriorityQueue } = require("@datastructures-js/priority-queue");Creating a Min-Heap (smallest element at the top)
const minHeap = new PriorityQueue({ compare: (a, b) => a - b });Creating a Max-Heap (largest element at the top)
const maxHeap = new PriorityQueue({ compare: (a, b) => b - a });enqueue(value): Add value to the heap.
minHeap.enqueue(10); // Adds 10 to minHeapdequeue(): Remove and return the top element.
minHeap.dequeue(); // Removes and returns smallest elementfront(): Get the top element without removing it.
minHeap.front(); // Returns smallest element without removingsize(): Get the number of elements in the heap.
minHeap.size(); // Returns number of elements in minHeapisEmpty(): Check if the heap is empty.
minHeap.isEmpty(); // Returns true if heap is emptyExample usage
const minHeap = new PriorityQueue({ compare: (a, b) => a - b });
minHeap.enqueue(10);
minHeap.enqueue(5);
minHeap.enqueue(20);
console.log(minHeap.front()); // Output: 5
console.log(minHeap.size()); // Output: 3
minHeap.dequeue(); // Removes 5
console.log(minHeap.front()); // Output: 10| Operation | Time Complexity |
|---|---|
| Insert | |
| Extract Min | |
| Peek |