Sorting Maps in JavaScript: Comprehensive guide


So, you wanna be a pro at sorting Maps in JavaScript? Great, in this blog you’ll learn through practical examples how to sort string and numeric keys, handle complex sorting scenarios, and navigate common pitfalls. Perfect for both beginners and seasoned coders, this guide also features a fun coding challenge to apply your skills.

Lets dive in! 👨‍💻


Refresher: Maps, Sorting and Custom Comparators

Map: A Map in JavaScript holds key-value pairs where keys can be any data type. To populate a map, either you can individually set key-value pairs. Or, and importantly for our sorting needs, Map has a constructor which can utilize a 2 dimensional array (an array of pairs of key-values).

Populating Map (JavaScript)
// Individually setting Map values:
let myMap = new Map();
myMap.set("c", 3);
myMap.set("a", 1);
myMap.set("b", 2);
console.log(myMap);
// Output: Map(3) {'c' => 3, 'a' => 1, 'b' => 2}

//   Constructor using 2 dimensional Array (of key -value).
let anotherMap = new Map([
  ["Orange", "Orange"],
  ["Grape", "Purple"],
]);
console.log(anotherMap.get("Grape"));
// Output: Map(2) { 'Orange' => 'Orange', 'Grape' => 'Purple' }

The sort() Method: JavaScript’s sort() method is typically used on arrays. Note that Maps are not directly sortable. However, we can convert a Map into an array, then sort the array, and then reconstruct the Map. And we can sort using default sort method or using custom comparators (explained below).

Spread Syntax in JavaScript: Spread syntax allows an iterable (like an array) to expand in places where arguments or elements are expected. It’s a convenient way to convert Maps into arrays.

map.entries() method gives an iterable of key and value pairs.

Sorting and Spread Syntax (JavaScript)
// Sort() method on Numeric Array (not usable on Map directly):
let numbers = [3, 1, 4, 1, 5, 9];
// Ascending Order of Number Array. ↗
numbers.sort(); // used without a custom comparator.
// Output: [1, 1, 3, 4, 5, 9]

// Using Spread syntax to convert Map to 2-Dimensional Array
let exampleMap = new Map([
  ["c", 3],
  ["a", 1],
  ["b", 2],
]);
let mapToArray = [...exampleMap];
console.log(mapToArray); // [['c', 3], ['a', 1], ['b', 2]]

// Using map.entries() to convert Map to an iterable or a 2-Dimensional Array
let exampleMap2 = new Map([
  ["c", 3],
  ["a", 1],
  ["b", 2],
]);

let mapToArray2 = exampleMap2.entries();
console.log(mapToArray2);
// Output iterable: [Map Entries] { [ 'c', 3 ], [ 'a', 1 ], [ 'b', 2 ] }

console.log([...mapToArray2]);
// Output 2D array: [ [ 'c', 3 ], [ 'a', 1 ], [ 'b', 2 ] ]

Custom Comparators Intro

We can sort an array using a custom comparator function, which specified how two elements being compared can be ordered. For an expression, a - b, we can specify following cases depending upon value of a - b:

  • Negative Value: Indicates that a should come before b.
  • Positive Value: Implies that a should come after b.
  • Zero: Suggests no change in the order of a and b concerning each other. Can be useful in stable sorting algorithms (which don’t jumble left to right order of equal elements on sorting). Optionally, you can also implement tie-breaker logic inside this case.

Lets see some examples, on number and string arrays (we will see Map in coming sections):

Custom Comparator Examples (JavaScript)
// 1️⃣ Sorting Number Array:
let numbers = [3, 1, 4, 1, 5, 9];

// Ascending Order of Number Array. ↗
numbers.sort((a, b) => a - b); // same as numbers.sort()
console.log(numbers);
// Output: [1, 1, 3, 4, 5, 9]

// Descending Sort  of Number Array. ↘
numbers.sort((a, b) => b - a);
console.log(numbers);
// Output: [9, 5, 4, 3, 1, 1]

// 2️⃣ Sorting Strings (Lexicographically) :
let fruits = ["banana", "apple", "cherry"];

// Default Ascending Order of String Array. ↗
fruits.sort();
console.log(fruits);
// Output: ['apple', 'banana', 'cherry']

// Descending Sort  of Number Array. ↘
fruits.sort((a, b) => b.localeCompare(a));
console.log(fruits);
// Output: ['cherry', 'banana', 'apple']

Ways to Sort JavaScript Maps

Sorting a Map in JavaScript requires a two-step process: first, converting the Map into an array of [key, value] pairs, and then sorting this array using various criteria. We’ll explore different sorting techniques including sorting by keys, sorting by values, and using custom sorting functions.

Sorting by Keys

Understanding Map Entries and Sorting:

  • JavaScript Maps are collections of key-value pairs, but they don’t have a built-in sorting method.
  • To sort a Map, we use Map.prototype.entries() to get an iterable of [key, value] pairs.
  • The spread syntax (...) is used to transform this iterable into an array.
  • Once we have an array, the Array.prototype.sort() method can be applied to it.

Example – Sorting String Keys in Ascending Order:

  • Here, the sorting is based on the string keys of the Map.
  • The default behavior of the sort() method, without a comparator, sorts elements as strings in ascending order.
  • The result is a Map sorted alphabetically by its keys.
Sort String Keys Ascending (JavaScript)
let stringKeyMap = new Map([
  ["c", 3],
  ["a", 1],
  ["b", 2],
]);

let sortedByKey = new Map([...stringKeyMap.entries()].sort());
console.log(sortedByKey);
// Map { 'a' => 1, 'b' => 2, 'c' => 3 }

Example – Sorting Numeric Keys in Ascending Order:

  • Numeric keys require a custom comparator function because sort() treats elements as strings by default.
  • The comparator (a, b) => a[0] - b[0] ensures that the keys are compared numerically.
Sort Numeric Keys Ascending (JavaScript)
let numericKeyMap = new Map([
  [3, "c"],
  [1, "a"],
  [2, "b"],
]);

let sortedNumericMap = new Map(
  [...numericKeyMap.entries()].sort((a, b) => a[0] - b[0]),
);

console.log(sortedNumericMap);
// Map { 1 => 'a', 2 => 'b', 3 => 'c' }

Example – Custom-Comparator: Sorting by Key Length

  • Custom sorting based on the length of keys demonstrates the flexibility of the sort() method.
  • The keys are sorted according to their length, from shortest to longest.
Custom Comparator (JavaScript)
let keyLengthMap = new Map([
  ["apple", 1],
  ["fig", 2],
  ["banana", 3],
]);

let sortedByKeyLength = new Map(
  [...keyLengthMap.entries()].sort((a, b) => a[0].length - b[0].length),
);

console.log(sortedByKeyLength);
// Map { 'fig' => 2, 'apple' => 1, 'banana' => 3 }

Sorting by Values

Sorting by values involves targeting the second element of each [key, value] pair, which we obtained from map.entries().

Example: Sorting Map by Numerical Values in Ascending Order:

  • Target the second element of each [key, value] pair to get the numerical values stored in following map.
  • Then the comparator function (a, b) => a[1] - b[1] sorts the Map based on its values in ascending numerical order.
Sort by Numerical Value Ascending (JavaScript)
let valueMap = new Map([
  ["c", 3],
  ["a", 1],
  ["b", 2],
]);
let sortedByValue = new Map(
  [...valueMap.entries()].sort((a, b) => a[1] - b[1]),
);
console.log(sortedByValue);
// Output: Map { 'a' => 1, 'b' => 2, 'c' => 3 }

Example – Sorting by Numerical Values in Descending Order:

  • Reversing the logic of the comparator function allows for descending order sorting.
  • The function (a, b) => b[1] - a[1] places higher values before lower ones.
  • Similarly we can use ascending or descending sort on Date, String, or other data types of Map Values.
Sort by Numerical Value Descending (JavaScript)
let valueDescMap = new Map(
  [...valueMap.entries()].sort((a, b) => b[1] - a[1]),
);
console.log(valueDescMap);
// Output: Map { 'c' => 3, 'b' => 2, 'a' => 1 }

Custom Sorting Function for Sorting JavaScript Maps

With custom comparator and map.entries(), we have full flexibility to create complex sorts (using single or multiple criteria):

Sorting JavaScript Maps Based on Object Properties:

  • Custom sorting functions offer a significant level of flexibility.
  • In this example, the Map is sorted based on the ‘score’ property of object values.
Sort using Object Properties (JavaScript)
let objectMap = new Map([
  ["first", { score: 2 }],
  ["second", { score: 1 }],
  ["third", { score: 3 }],
]);

let sortedObjectMap = new Map(
  [...objectMap.entries()].sort(
    (a, b) => a[1].score - b[1].score,
  ),
);

console.log(sortedObjectMap);
// Output: Map { 'second' => { score: 1 }, 'first' => { score: 2 }, 'third' => { score: 3 } }

Complex Sorting with Multiple Criteria Sorting JavaScript Maps

  • Complex sorting scenarios can involve multiple criteria.
  • Here, the Map is first sorted by ‘score’ and then by ‘age’ in cases where the ‘score’ is equal.
Sort using Multiple Criterina (JavaScript)
let complexMap = new Map([
  ["a", { score: 10, age: 20 }],
  ["b", { score: 10, age: 15 }],
  ["c", { score: 5, age: 30 }],
]);

let complexSortedMap = new Map(
  [...complexMap.entries()].sort((a, b) => {
    if (a[1].score === b[1].score) {
      // Secondary sorting by age (Tie-Breaker)
      return a[1].age - b[1].age;
    }
    // Primary sorting by score
    return a[1].score - b[1].score;
  }),
);

console.log(complexSortedMap);
// Output:
// Map { 'b' => { score: 10, age: 15 },
//		 'a' => { score: 10, age: 20 },
//		 'c' => { score: 5, age: 30 }
// }

Tips & Mistakes in Sorting JavaScript Maps

Using Incorrect Comparator Logic

  • A common mistakes when sorting in JavaScript is the misuse of the comparator function. The comparator function needs to return a negative number, zero, or a positive number, based on the comparison of two elements, a and b.
  • A negative return value means a should come before b, zero means their order doesn’t matter, and a positive return value means a should come after b.
  • Misunderstanding this logic can lead to incorrect sorting results. For example, simply returning true or false from a comparator function (as one might do using relational operators for a boolean result) will not work as intended because true is coerced to 1 and false to 0 in JavaScript.
Using Incorrect Comparator Logic
let numericMapForComparison = new Map([
  [3, "c"],
  [1, "a"],
  [2, "b"],
]);
// Incorrect comparator logic using boolean expressions
let incorrectlySortedNumericMap = new Map(
  [...numericMapForComparison.entries()].sort(
    (a, b) => a[0] < b[0],
  ),
);
console.log(incorrectlySortedNumericMap);
// Sorting results might be incorrect
// Output: Map(3) { 3 => 'c', 1 => 'a', 2 => 'b' }

Note the Data type in Custom Comparator

In your custom comparator functions, you need to be mindful of the data type used inside the function and whether the operation done on them is supported. Like, subtraction operation doesn’t work on Strings, but you can use them on numbers. Lets see some examples:

  • Numbers:
    • Use subtraction (a - b) for sorting.
    • Suitable for straightforward numerical comparison.
  • Strings:
    • Relational Operators (<, >): Can be used, but not ideal for sorting since they compare based on Unicode values.
    • So preferably, use localeCompare for sorting strings. Handles differences in case and special characters effectively.
    • Note whether case sensitivity and locales comparison is also required.
  • Dates:
    • Treat like numbers; subtraction (a - b) works since dates are converted to timestamps.
    • Ensures chronological sorting.
  • Objects with Numeric Properties:
    • Access the numeric property and use subtraction for sorting.
    • Sorts objects based on a specific numeric criterion.
  • Objects with String Properties:
    • Use localeCompare on the string property.
    • Ideal for sorting objects based on string-based criteria like names.
Data type in Custom Comparator (JavaScript)
// Numbers
let numbers = [3, 1, 4, 1, 5, 9];
numbers.sort((a, b) => a - b);

// Strings
let strings = ['banana', 'apple', 'cherry'];
strings.sort((a, b) => a.localeCompare(b));

// Dates
let dates = [new Date(2020, 1, 1), new Date(2019, 1, 1), new Date(2021, 1, 1)];
dates.sort((a, b) => a - b);

// Objects with Numeric Property
let items = [{ value: 10 }, { value: 5 }, { value: 15 }];
items.sort((a, b) => a.value - b.value);

// Objects with String Property
let itemsWithString = [{ name: 'John' }, { name: 'Alice' }, { name: 'Bob' }];
itemsWithString.sort((a, b) => a.name.localeCompare(b.name));

Mixing Data Types:

  • Maps in JavaScript can have keys and values of different data types, including mixed types within the same Map. However, when it comes to sorting, mixing data types can lead to unpredictable and incorrect results.
  • For example, when sorting a mix of strings and numbers, JavaScript will convert numbers to strings and perform a lexicographic comparison, which may not align with the intended sorting logic.
  • Consistency in data types is key to achieving accurate sorting results. It’s important to either ensure that all keys or all values in a Map are of the same data type or to implement a comparator function that correctly handles mixed data types.
Mixing Data Types (JavaScript)
let mixedTypeMap = new Map([
  ["1", 10],
  ["2", "apple"],
  ["3", 5],
]);

// Attempting to sort mixed types numerically
let sortedMixedMap = new Map(
  [...mixedTypeMap.entries()].sort((a, b) => {
    if (
      typeof a[1] === "number" &&
      typeof b[1] === "number"
    ) {
      // Numerical comparison for numbers
      return a[1] - b[1];
    } else if (
      typeof a[1] === "string" &&
      typeof b[1] === "string"
    ) {
      // Lexicographic comparison for strings
      return a[1].localeCompare(b[1]);
    }
    // Default case if types are mixed or unmatched
    return 0;
  }),
);

console.log(sortedMixedMap);
// Sorted with type-specific logic
// Output: Map(3) { '1' => 10, '2' => 'apple', '3' => 5 }

Mutability

  • In JavaScript, objects (including Maps) are reference types. When you perform sorting operations directly on the original Map, you’re modifying its state. This modification can lead to unintended side effects, especially in larger applications where the same Map might be accessed in different contexts.
  • The recommended approach is to treat the original Map as immutable during sorting. By creating a new Map for the sorted results, you preserve the original Map’s state, ensuring that any other references to this Map remain unaffected by the sorting operation.
  • This approach aligns with functional programming principles, promoting immutability and side-effect-free functions, which lead to more predictable and maintainable code.
Mutability (JavaScript)
let originalMap = new Map([['b', 2], ['a', 1], ['c', 3]]);

// Creating a new sorted map without altering the original
let sortedMap = new Map([...originalMap.entries()].sort(
  (a, b) => a[0].localeCompare(b[0])
));

console.log('Sorted Map:', sortedMap);
// Sorted Map: Map(3) { 'a' => 1, 'b' => 2, 'c' => 3 }

console.log('Original Map:', originalMap);
// Original Map: Map(3) { 'b' => 2, 'a' => 1, 'c' => 3 }

Undefined and Null Values

  • The presence of undefined and null values in a Map can complicate the sorting process. These values often require special handling to ensure they are sorted in a manner consistent with the application’s requirements.
  • In JavaScript, undefined and null are falsy values, but they are not equal to each other. undefined is typically used to indicate that a variable has not been assigned a value, while null is an assignment value that represents the intentional absence of any object value.
  • When sorting, you must decide where these values should appear in the order. For instance, you might want null or undefined values to be sorted to the beginning or end of the Map, or you might want to handle them in a way that they don’t affect the sorting of other elements.
  • Explicit handling in the comparator function is essential to ensure that these values are sorted as per the desired logic.
Undefined and Null Values (JavaScript)
let mapWithNullUndefined = new Map([['a', undefined], ['b', 2], ['c', null]]);

// Sorting while handling null and undefined explicitly
let sortedMapWithNullUndefined = new Map([...mapWithNullUndefined.entries()].sort((a, b) => {
    if (a[1] === null || a[1] === undefined) {
        // Place null or undefined at the start
        return -1;
    } else if (b[1] === null || b[1] === undefined) {
        // Place null or undefined at the start
        return 1;
    }
    // Default numerical sorting for other values
    return a[1] - b[1];
}));

console.log(sortedMapWithNullUndefined);
// Sorted considering null and undefined
// Map(3) { 'c' => null, 'a' => undefined, 'b' => 2 }

Incorrect Assumptions About Sort Stability

  • Sort stability is a concept in sorting algorithms where equal elements retain their original relative order post-sorting. This feature is particularly important when sorting complex data structures or when the sorting criteria might result in equal sort keys.
  • In some JavaScript environments, especially older ones, the sort() method did not guarantee stability. This means that if two elements are considered equal by the comparator function, their order after sorting might not be the same as their order before sorting.
  • As of ECMAScript 2019 (ES10), sort stability is guaranteed in JavaScript. However, when working in environments that may not be fully up-to-date, it’s important to be aware that sort stability might not be guaranteed.
  • When relying on sort stability, be cautious and verify the behavior in the specific JavaScript environment you’re targeting.
Sort Stability (JavaScript)
let stableSortExample = new Map([
  ["b", 1],
  ["a", 1],
  ["c", 2],
]);

// Assuming stable sort is supported in the environment
let sortedStably = new Map(
  [...stableSortExample.entries()].sort(
    (a, b) => a[1] - b[1],
  ),
);

console.log(sortedStably);
// 'b' and 'a' should retain their original order
// Output: Map(3) { 'b' => 1, 'a' => 1, 'c' => 2 }

🧪Practice Coding Problem: Sorted Map Inversion

In the spirit of Test Driven Development ( 😁), lets test our understanding by solving a problem.

Create a function invertAndSortMap that takes a Map with string keys and numeric values. The function should return a new Map where each original value becomes a key, and the original key becomes part of the value, sorted in ascending order based on the original numeric values.

Problem (JavaScript)
function invertAndSortMap(inputMap) {
  // > > > 👉 Write code here 👈 < < <
}

// Example usage
const inputMap = new Map([['apple', 3], ['banana', 1], ['cherry', 2]]);
const outputMap = invertAndSortMap(inputMap);
console.log(outputMap);
// Expected output: Map { 1 => 'banana', 2 => 'cherry', 3 => 'apple' }
Please attempt before seeing the Answer:
Solution (JavaScript)
function invertAndSortMap(inputMap) {
    let invertedArray = [...inputMap.entries()].map(([key, value]) => [value, key]);
    invertedArray.sort((a, b) => a[0] - b[0]);
    return new Map(invertedArray);
}

Explanation:

  • The invertAndSortMap function starts by converting the input Map into an array of [key, value] pairs using the spread syntax and Map.prototype.entries().
  • It then uses the Array.prototype.map() method to invert each pair, making the value the new key and the original key the new value.
  • Next, the function sorts this array using the Array.prototype.sort() method, comparing the numeric keys (original values) to sort them in ascending order.
  • Finally, it converts the sorted array back into a Map and returns it.
  • The result is a new Map where the original values are now keys, sorted in ascending order, and the original keys are part of the values.

Now you are an expert in sorting your JavaScript maps.

Keep learning, keep experimenting and keep coding! 🚀👨‍💻

Scroll to Top