JavaScript Maps

Maps in JavaScript are a mutable collection of key-value pairs, where keys can be of any type. Maps are particularly useful for managing collections of items with unique keys.

Concept and Use Cases

Definition: A JavaScript Map is a collection of key-value pairs, where each key can be of any type (primitive or object). Maps maintain the order of insertion and provide a range of methods to manage and manipulate key-value pairs.

Common Use Cases:

  • Storing and managing collections of items with unique keys.
  • Implementing caches and lookup tables.
  • Efficiently managing dynamic collections with non-string keys.
  • Associating metadata with objects.

When to Use

  • When you need to store key-value pairs and require keys that are not limited to strings.
  • When you need to preserve the insertion order of entries.
  • When performing frequent updates and lookups on a dynamic collection of data.

Time and Space Complexity

Time Complexity:

  • Insertion: O(1)
  • Deletion: O(1)
  • Search: O(1)
  • Iteration: O(n)

Space Complexity:

  • O(n), where n is the number of key-value pairs in the map.

Map Operations and Methods

Creating Maps

Example:

let emptyMap = new Map();
let numberMap = new Map([[1, 'one'], [2, 'two'], [3, 'three']]);
let mixedMap = new Map([
    ['stringKey', 'value1'],
    [42, 'value2'],
    [{key: 'object'}, 'value3']
]);

Adding and Removing Entries

Example:

let map = new Map();
map.set('name', 'John');
map.set('age', 30);
map.set('age', 31);  // Updates the value for 'age'
console.log(map);  // Output: Map { 'name' => 'John', 'age' => 31 }

map.delete('name');
console.log(map);  // Output: Map { 'age' => 31 }

Checking for Existence

Example:

let map = new Map([['name', 'John'], ['age', 30]]);
console.log(map.has('name'));  // Output: true
console.log(map.has('address'));  // Output: false

Getting the Size of a Map

Example:

let map = new Map([['name', 'John'], ['age', 30]]);
console.log(map.size);  // Output: 2

Iterating Over Maps

Example:

let map = new Map([['name', 'John'], ['age', 30]]);

for (let [key, value] of map) {
    console.log(`${key}: ${value}`);
}

map.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

Clearing a Map

Example:

let map = new Map([['name', 'John'], ['age', 30]]);
map.clear();
console.log(map.size);  // Output: 0

Converting Maps to Arrays

Example:

let map = new Map([['name', 'John'], ['age', 30]]);
let keysArray = Array.from(map.keys());
let valuesArray = Array.from(map.values());
let entriesArray = Array.from(map.entries());

console.log(keysArray);  // Output: ['name', 'age']
console.log(valuesArray);  // Output: ['John', 30]
console.log(entriesArray);  // Output: [['name', 'John'], ['age', 30]]

Practical Tips and Tricks

  • Using Maps for Object Metadata: Maps can be used to associate metadata with objects without modifying the objects.

    Example:

    let obj1 = {id: 1};
    let obj2 = {id: 2};
    
    let metadataMap = new Map();
    metadataMap.set(obj1, {role: 'admin'});
    metadataMap.set(obj2, {role: 'user'});
    
    console.log(metadataMap.get(obj1));  // Output: { role: 'admin' }
    
  • Using Complex Keys: Maps allow for keys that are not limited to strings, such as objects or functions.

    Example:

    let map = new Map();
    let key1 = {};
    let key2 = () => {};
    
    map.set(key1, 'value1');
    map.set(key2, 'value2');
    
    console.log(map.get(key1));  // Output: 'value1'
    console.log(map.get(key2));  // Output: 'value2'
    
  • Efficient Lookup Table: Maps provide efficient O(1) lookups, making them suitable for implementing lookup tables.

    Example:

    let lookup = new Map([['a', 1], ['b', 2], ['c', 3]]);
    console.log(lookup.get('b'));  // Output: 2
    

Common Gotchas

  • Using Objects as Keys: When using objects as keys, the same object reference must be used for lookups.

    Example:

    let map = new Map();
    let obj = {id: 1};
    
    map.set(obj, 'value');
    console.log(map.get({id: 1}));  // Output: undefined (different object reference)
    console.log(map.get(obj));  // Output: 'value'
    
  • Order of Insertion: Maps maintain the order of insertion, which can affect iteration and operations.

    Example:

    let map = new Map([['a', 1], ['b', 2], ['c', 3]]);
    console.log(Array.from(map.keys()));  // Output: ['a', 'b', 'c']
    
  • Not Using clear Correctly: The clear method removes all entries from a map but doesn't return the map itself.

    Example:

    let map = new Map([['a', 1], ['b', 2]]);
    map.clear();
    console.log(map.size);  // Output: 0
    

Advanced Topics

WeakMaps

WeakMaps are similar to maps but only contain object keys and do not prevent garbage collection of keys.

Example:

let weakMap = new WeakMap();
let obj = {};

weakMap.set(obj, 'value');
console.log(weakMap.get(obj));  // Output: 'value'

obj = null;  // The entry in the WeakMap will be garbage collected

Map Operations Using Utility Functions

Example:

function mergeMaps(map1, map2) {
    return new Map([...map1, ...map2]);
}

function mapKeysToArray(map) {
    return Array.from(map.keys());
}

function mapValuesToArray(map) {
    return Array.from(map.values());
}

let map1 = new Map([['a', 1], ['b', 2]]);
let map2 = new Map([['c', 3], ['d', 4]]);

let mergedMap = mergeMaps(map1, map2);
console.log(mergedMap);  // Output: Map { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4 }

console.log(mapKeysToArray(mergedMap));  // Output: ['a', 'b', 'c', 'd']
console.log(mapValuesToArray(mergedMap));  // Output: [1, 2, 3, 4]

Map Algorithms

Below are some common map algorithms you should be familiar with:

Counting Frequencies

Counting frequencies involves determining how often each element appears in an array.

Example:

function countFrequencies(arr) {
    let frequencyMap = new Map();
    for (let item of arr) {
        if (frequencyMap.has(item)) {
            frequencyMap.set(item, frequencyMap.get(item) + 1);
        } else {
            frequencyMap.set(item, 1);
        }
    }
    return frequencyMap;
}

let array = ['a', 'b', 'a', 'c', 'b', 'a'];
console.log(countFrequencies(array));  // Output: Map { 'a' => 3, 'b' => 2, 'c' => 1 }

Finding Unique Keys

Finding unique keys identifies elements that appear exactly once in an array.

Example:

function findUniqueKeys(arr) {
    let map = new Map();
    for (let item of arr) {
        map.set(item, (map.get(item) || 0) + 1);
    }
    let uniqueKeys = [];
    for (let [key, value] of map) {
        if (value === 1) {
            uniqueKeys.push(key);
        }
    }
    return uniqueKeys;
}

let array = ['a', 'b', 'a', 'c', 'b', 'd'];
console.log(findUniqueKeys(array));  // Output: ['c', 'd']

Merging Multiple Maps

Merging multiple maps combines the key-value pairs of multiple maps into one.

Example:

function mergeMultipleMaps(...maps) {
    return maps.reduce((merged, map) => {
        for (let [key, value] of map) {
            merged.set(key, value);
        }
        return merged;
    }, new Map());
}

let map1 = new Map([['a', 1], ['b', 2]]);
let map2 = new Map([['c', 3], ['d', 4]]);
let map3 = new Map([['e', 5], ['f', 6]]);

let mergedMap = mergeMultipleMaps(map1, map2, map3);
console.log(mergedMap);  // Output: Map { 'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4, 'e' => 5, 'f' => 6 }

Interview Tips and Tricks

  • Understand Map Methods: Be familiar with methods like set, get, has, delete, and clear.

  • Practice Map Use Cases: Work on problems involving counting frequencies, managing caches, and performing efficient lookups.

  • Handle Edge Cases: Consider scenarios with empty maps, maps with non-string keys, and maps with nested structures.

  • Use WeakMaps for Efficient Memory Management: Utilize WeakMaps when you need keys to be garbage collected.

Common Mistakes

  • Misusing Object Keys: Remember that objects used as keys must be the same reference for lookups to work.

  • Forgetting Order of Insertion: Maps maintain the order of insertion, which may affect iteration and operations.

  • Not Using clear Correctly: The clear method removes all entries but does not return the map itself.

By mastering JavaScript maps and understanding their intricacies, you will be well-equipped to handle a variety of interview questions and real-world problems involving key-value pairs. Regular practice and a solid grasp of advanced topics will deepen your understanding and improve your problem-solving skills.

Practice Problems

CodingEasy

Two Sum

CodingEasy

Valid Anagram

CodingEasy

Word Pattern

Let's continue exploring the next page. Take your time, and proceed when you're ready.

Lesson completed?

Found a bug, typo, or have feedback?

Let me know