Why Do Chrome and Firefox Sort Arrays Differently? Uncovering Their Algorithms
Although both browsers receive identical data, Chrome and Firefox display sorted results differently due to their distinct underlying sorting algorithms—Firefox uses merge sort while Chrome employs a hybrid of insertion and quick sort—this article explains the issue, demonstrates implementations, and compares performance and optimization techniques.
Problem Description
Both Chrome and Firefox receive the same data from the API. After sorting, the same page shows different results in the two browsers.
Problem Investigation
Initially suspected front‑end sorting logic or other factors. After reviewing, the misuse of
sort()was discovered.
sort()without a comparator sorts elements as strings (lexicographically). Providing a comparator function allows custom ordering. The comparator must accept two arguments
aand
band return a negative number if
ashould precede
b, zero if equal, or a positive number otherwise.
Even after fixing the comparator, debugging showed that each iteration of the sorting process produced different intermediate results in the two browsers, although the final sorted array was the same. The discrepancy originates from the browsers’ internal sorting algorithms: Firefox uses merge sort, while Chrome uses a hybrid of insertion sort and quick sort (arrays with fewer than 10 elements use insertion sort, larger ones use quick sort).
Algorithm Implementations and Comparison
Merge Sort
Idea: recursively split the array into halves, sort each half, then merge the sorted halves.
<code>const merge = (left, right) => {
const result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length) result.push(left.shift());
while (right.length) result.push(right.shift());
return result;
};
const mergeSort = arr => {
const len = arr.length;
if (len < 2) return arr;
const middle = Math.floor(len / 2);
const left = arr.slice(0, middle);
const right = arr.slice(middle);
return merge(mergeSort(left), mergeSort(right));
};
</code>Quick Sort
Idea: choose a pivot, partition the array into elements smaller and larger than the pivot, then recursively sort the sub‑arrays.
<code>const quickSort1 = arr => {
if (arr.length <= 1) return arr;
const midIndex = Math.floor(arr.length / 2);
const valArr = arr.splice(midIndex, 1);
const midIndexVal = valArr[0];
const left = [];
const right = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < midIndexVal) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return quickSort1(left).concat(midIndexVal, quickSort1(right));
};
</code>Insertion Sort
Suitable when most elements are already sorted. Idea: build a sorted sub‑array by inserting each unsorted element into its correct position.
<code>const insertionSort = (nums) => {
for (let i = 1; i < nums.length; ++i) {
let preIndex = i - 1;
const temp = nums[i];
while (preIndex >= 0 && nums[preIndex] > temp) {
nums[preIndex + 1] = nums[preIndex];
preIndex--;
}
nums[preIndex + 1] = temp;
}
return nums;
};
</code>Performance Analysis
Time complexity:
Merge Sort – O(n log n)
Quick Sort – O(n log n)
Insertion Sort – O(n²)
Stability:
Merge Sort – stable
Quick Sort – unstable
Insertion Sort – stable
Optimization suggestions:
Insertion Sort – use binary search to find the insertion point (binary insertion).
<code>const binarySearch = (arr, maxIndex, value) => {
let min = 0;
let max = maxIndex;
while (min <= max) {
const mid = Math.floor((min + max) / 2);
if (arr[mid] <= value) {
min = mid + 1;
} else {
max = mid - 1;
}
}
return min;
};
const insertionSort2 = (arr) => {
for (let i = 1, len = arr.length; i < len; i++) {
const temp = arr[i];
const insertIndex = binarySearch(arr, i - 1, arr[i]);
for (let preIndex = i - 1; preIndex >= insertIndex; preIndex--) {
arr[preIndex + 1] = arr[preIndex];
}
arr[insertIndex] = temp;
}
return arr;
};
</code>Further optimizations for merge sort include using
spliceinstead of
sliceto reduce memory usage and switching to insertion sort for small sub‑arrays. Quick sort can be implemented in‑place, and a typical partition function is shown below.
<code>const swap = (arr, i, j) => {
let temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
};
const partition = (arr, left, right) => {
let pivot = left;
let index = pivot + 1;
for (let i = index; i <= right; i++) {
if (arr[i] < arr[pivot]) {
swap(arr, i, index);
index++;
}
}
swap(arr, pivot, index - 1);
return index - 1;
};
const quickSort = (arr, left, right) => {
const len = arr.length;
left = typeof left !== 'number' ? 0 : left;
right = typeof right !== 'number' ? len - 1 : right;
if (left < right) {
const index = partition(arr, left, right);
quickSort(arr, left, index - 1);
quickSort(arr, index + 1, right);
}
return arr;
};
</code>MaoDou Frontend Team
Open-source, innovative, collaborative, win‑win – sharing frontend tech and shaping its future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.