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.

Map vs Object: Which Should You Use?

Short answer: use a Map for a dynamic key-value collection, especially when keys are objects or their original types matter. Use a plain object for a record with named fields, such as a user profile or JSON payload.

QuestionMapPlain object
Which keys can I use?Any JavaScript valueStrings and symbols; other keys are converted
How do I iterate?Directly with for...ofUse an appropriate API such as Object.entries()
How do I count entries?.sizeFor own enumerable string keys, Object.keys(obj).length
What is the order?Key insertion orderDefined rules depend on key kind and enumeration API
How do I serialize to JSON?Convert to a suitable representation firstJSON.stringify() supports ordinary records directly

Do not choose Map simply because someone says objects are unordered or maps are always faster. Explain the data model and operations first.

const map = new Map()
map.set(1, 'number key')
map.set('1', 'string key')
console.log(map.size) // Output: 2

const record = {}
record[1] = 'number key'
record['1'] = 'string key'
console.log(Object.keys(record).length) // Output: 1

Interview check: why do these collections have different sizes? The object converts the numeric property key to a string; the Map retains two distinct keys. For object properties and prototypes, see Objects. This lesson owns Map operations and their trade-offs.

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: typically O(1) average in a hash-table implementation
  • Deletion: typically O(1) average in a hash-table implementation
  • Search: typically O(1) average in a hash-table implementation
  • Iteration: O(n)

These are common implementation assumptions, not universal worst-case guarantees. ECMAScript requires average sublinear access, not a particular hash-table representation. See MDN's Map reference.

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 lookups under the implementation assumptions above, making them useful for lookup tables. Use has(key) when you must distinguish a missing key from a stored undefined value.

    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 support object and non-registered symbol keys without strongly retaining them. They are not enumerable. See Memory Management for reachability and collection caveats.

Example:

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

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

obj = null;  // Removes this reference; collection timing is not guaranteed

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

Completion marks record your own progress, not an automatically checked result. The task type does not determine whether it is optional.

CodingEasy

Valid Anagram

CodingEasy

Word Pattern

CodingEasy

Two Sum

Lesson completed?

Found a bug, typo, or have feedback?

Let me know