Sorting — The Complete Guide
Master sorting as a problem-solving tool from first principles. Learn to recognize when sorting unlocks simpler solutions, build custom comparators, and confidently tackle interview questions.
Table of Contents
Intuition from Scratch
Why does this pattern exist?
Sorting is rarely the final answer to a problem. But it's often the first step that makes the real algorithm possible. Think of it this way: an unsorted array is chaos — you can't make assumptions about where anything is. A sorted array is structured — duplicates are adjacent, neighbors are meaningful, and you can use binary search, two pointers, or greedy strategies that would be impossible on random data.
The cost of sorting is O(n log n). That sounds expensive, but it's almost always worth it when the alternative is O(n²) brute force. Sorting is the "unlock" that drops your overall complexity. And in many interview problems, the interviewer expects you to sort first — it's the natural starting move.
Beyond standard sorting, custom comparators are the secret weapon. When the default numeric or alphabetical order isn't what you need, you define your own ordering rule. Problems like "Largest Number" or "Custom Sort String" are impossible without this skill.
Analogies to Build Intuition
The Library Shelf
Imagine a library where books are thrown randomly on shelves. Finding a specific book means scanning every shelf. But if books are sorted by title, you can jump straight to the right section. Sorting is the librarian's first job — it makes every future lookup fast.
Sorting a Hand of Cards
When you pick up a hand of cards, the first thing you do is sort them by suit and rank. You don't play with a random hand — sorting lets you instantly see what you have, spot pairs, find straights, and make decisions. That's exactly what sorting does for algorithms.
The Priority Queue at a Hospital
Patients aren't treated in arrival order — they're sorted by severity. A custom comparator decides the order: 'compare by severity first, then by arrival time.' That's a custom sort — you define what 'comes first' means for your specific problem.
What kind of problems does it solve?
Sorting is your go-to when:
- You need to merge or detect overlapping intervals
- You need to find duplicates or closest neighbors
- You need a greedy strategy that requires processing in a specific order
- You need to enable Two Pointers or Binary Search on unsorted data
- You need a custom ordering (largest number, custom sort string, relative sort)
- You want to reduce O(n²) brute force to O(n log n)
The core insight
Sorting transforms chaos into structure. Once data is ordered, patterns emerge: duplicates cluster together, neighbors become meaningful, and greedy choices become provably correct. The O(n log n) cost is almost always a worthwhile investment.
Pattern Recognition
Recognizing when sorting is the right first step is the most important skill. Here are the signals to look for and the traps to avoid.
Keywords & Signals in the Problem Statement
Use Sorting when you see...
- ✅"Merge overlapping intervals"
- ✅"Find the largest number formed by array elements"
- ✅"Meeting rooms" — sort by start or end time
- ✅"Sort colors" (Dutch National Flag)
- ✅Greedy problems that need a specific processing order
- ✅"Kth largest/smallest element" (partial sort or heap)
- ✅"Closest pair" or "minimum difference" between elements
- ✅"Group anagrams" — sort each string as a key
- ✅Problems where sorted order reveals duplicates or neighbors
- ✅"Assign tasks/jobs" — sort by deadline, weight, or priority
Don't use Sorting when...
- ❌You need to preserve original indices — sorting destroys them (use Hashing)
- ❌The problem requires O(n) time and sorting is O(n log n) — consider counting sort or hashing
- ❌Data is already sorted — skip straight to Binary Search or Two Pointers
- ❌You need online processing (streaming data) — sorting requires all data upfront
- ❌The problem is about contiguous subarrays — Sliding Window or Prefix Sum fits better
- ❌Sorting would break the problem's structure (e.g., subarray order matters)
Sorting vs. Similar Patterns
| Pattern | When to Use | Key Difference |
|---|---|---|
| Sorting | Need ordered data for greedy, intervals, or enabling other patterns | O(n log n) preprocessing step; unlocks simpler algorithms |
| Hashing | Need O(1) lookups, frequency counting, or must preserve indices | O(n) time, O(n) space; no ordering, just fast lookups |
| Heap / Priority Queue | Need top-K elements or streaming min/max | Partial sort — O(n log k) instead of full O(n log n) sort |
| Counting Sort | Small value range (e.g., 0-2 in Sort Colors) | O(n + k) time where k is the range; beats comparison sort |
| Two Pointers | Sorted data + pair/triplet search | Requires sorted input — sorting is the prerequisite step |
The sorting question to always ask
When you see an array problem, ask yourself: "Would sorting this make the problem easier?" If the answer is yes and you don't need original indices, sort first. This single question unlocks a huge number of interview problems.
Brute Force → Optimized Thinking
Let's walk through the thinking evolution using a concrete example: Merge Intervals — given a collection of intervals, merge all overlapping intervals.
Brute Force — Compare Every Pair
For each interval, check every other interval to see if they overlap. If they do, merge them and restart. This works but is painfully slow — each merge might create new overlaps, so you keep rescanning.
function mergeBrute(intervals: number[][]): number[][] { const result = [...intervals]; let merged = true; while (merged) { merged = false; for (let i = 0; i < result.length; i++) { for (let j = i + 1; j < result.length; j++) { if (result[i][0] <= result[j][1] && result[j][0] <= result[i][1]) { // Overlap — merge j into i result[i] = [ Math.min(result[i][0], result[j][0]), Math.max(result[i][1], result[j][1]), ]; result.splice(j, 1); merged = true; break; } } if (merged) break; } } return result; } // Problem: O(n²) per pass, and we might need multiple passes.
Optimal — Sort by Start Time, Then Single Pass
The key insight: if intervals are sorted by start time, overlapping intervals are always adjacent. You only need to compare each interval with the previous one. Sort first, then a single linear scan merges everything.
function merge(intervals: number[][]): number[][] { // Sort by start time intervals.sort((a, b) => a[0] - b[0]); const merged: number[][] = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const prev = merged[merged.length - 1]; const curr = intervals[i]; if (curr[0] <= prev[1]) { // Overlap — extend the previous interval prev[1] = Math.max(prev[1], curr[1]); } else { // No overlap — start a new interval merged.push(curr); } } return merged; } // Sort: O(n log n). Merge pass: O(n). Total: O(n log n). // After sorting, overlapping intervals are ALWAYS adjacent.
Why Does the Optimization Work?
Sorting groups overlapping intervals together
When intervals are sorted by start time, any interval that overlaps with interval[i] must come right after it (its start is between interval[i]'s start and end). No need to check distant intervals.
One pass is enough after sorting
Since overlapping intervals are adjacent, you just compare each interval with the last merged one. If they overlap, extend. If not, start fresh. No backtracking needed.
The general principle
Sorting converts a 'compare everything with everything' problem (O(n²)) into a 'compare with your neighbor' problem (O(n)). The O(n log n) sort cost is dominated by the original O(n²), so you come out ahead.
The thinking process matters more than the code
In interviews, walk through this progression: "The brute force compares every pair of intervals — O(n²). But if I sort by start time, overlapping intervals become adjacent, so I only need one linear pass. Total: O(n log n)." This shows the interviewer you understand WHY sorting helps, not just that it does.
Core Templates
Sorting-based problems follow a few recurring templates. Memorize these structures — most problems are variations of one of them.
Template 1: Sort + Linear Scan
Sort the data, then make a single pass to solve the problem. Used for interval merging, finding closest pairs, and detecting duplicates.
function sortAndScan(arr: number[][]): Result { // 1. Sort by the relevant key arr.sort((a, b) => a[0] - b[0]); // 2. Linear scan — compare each element with the previous const result = [arr[0]]; for (let i = 1; i < arr.length; i++) { const prev = result[result.length - 1]; const curr = arr[i]; if (overlaps(prev, curr)) { merge(prev, curr); // extend/update the previous } else { result.push(curr); // start a new group } } return result; } // When to use: merge intervals, remove covered intervals, // minimum number of arrows, meeting rooms
Template 2: Custom Comparator Sort
Define a custom ordering rule when the default sort doesn't match what the problem needs. The comparator returns negative (a before b), positive (b before a), or zero (equal).
function customSort(arr: Item[]): Item[] { return arr.sort((a, b) => { // Primary sort key if (a.primary !== b.primary) { return a.primary - b.primary; } // Secondary sort key (tiebreaker) return a.secondary - b.secondary; }); } // String concatenation comparator (Largest Number): strs.sort((a, b) => (b + a).localeCompare(a + b)); // Multi-key sort (sort by frequency, then by value): arr.sort((a, b) => freq[a] - freq[b] || a - b); // When to use: largest number, custom sort string, // sort by frequency, relative sort array
Template 3: Sort + Two Pointers / Binary Search
Sort first to enable a more efficient algorithm. Sorting is the prerequisite that makes Two Pointers or Binary Search possible.
function sortThenSearch(arr: number[], target: number): Result { // 1. Sort the array arr.sort((a, b) => a - b); // 2. Apply Two Pointers on sorted data let left = 0, right = arr.length - 1; while (left < right) { const sum = arr[left] + arr[right]; if (sum === target) return [arr[left], arr[right]]; if (sum < target) left++; else right--; } return notFound; } // When to use: 3Sum, 4Sum, two sum (when indices don't matter), // closest pair, minimum absolute difference
Template 4: Dutch National Flag (3-Way Partition)
When you need to partition an array into exactly 3 groups in a single pass with O(1) space. Uses three pointers: low, mid, high.
function threeWayPartition(arr: number[], pivot: number): void { let low = 0, mid = 0, high = arr.length - 1; while (mid <= high) { if (arr[mid] < pivot) { [arr[low], arr[mid]] = [arr[mid], arr[low]]; low++; mid++; } else if (arr[mid] === pivot) { mid++; } else { [arr[mid], arr[high]] = [arr[high], arr[mid]]; high--; // Don't advance mid — swapped element is unchecked } } } // When to use: Sort Colors (0/1/2), partition around pivot, // move negatives to left / positives to right
Which template to pick?
Ask yourself: (1) Do I need to process sorted data linearly? → Template 1. (2) Do I need a non-standard ordering? → Template 2. (3) Do I need sorting to enable another pattern? → Template 3. (4) Do I need to partition into 3 groups in O(n)? → Template 4.
Variations & Sub-Patterns
Sorting isn't a single trick — it's a family of techniques. Here are the most common variations and how the approach changes for each.
| Variation | Sorting Strategy | Classic Example |
|---|---|---|
| Interval Merging | Sort by start time, merge adjacent overlaps | Merge Intervals, Insert Interval |
| Interval Scheduling (Greedy) | Sort by end time, greedily pick non-overlapping | Meeting Rooms II, Non-overlapping Intervals |
| Custom Comparator | Define problem-specific ordering rule | Largest Number, Custom Sort String |
| Sort + Two Pointers | Sort to enable pair/triplet search | 3Sum, 3Sum Closest, Valid Triangle Number |
| Dutch National Flag | 3-way partition with three pointers in O(n) | Sort Colors, Move Zeroes (2-way variant) |
| Counting Sort | Count occurrences when value range is small | Sort Colors, H-Index, Relative Sort Array |
| Bucket Sort / Frequency Sort | Group by frequency, then collect buckets | Top K Frequent Elements, Sort Characters by Frequency |
| Sort by Multiple Keys | Primary key + tiebreaker secondary key | Reorder Data in Log Files, Queue Reconstruction by Height |
Problems mentioned above
Deep Dive: Custom Comparators — Largest Number
The classic custom comparator problem. Given [3, 30, 34, 5, 9], form the largest number. The trick: compare two numbers by which concatenation order produces a bigger result. "9" + "34" = "934" vs "34" + "9" = "349" → 9 should come first.
function largestNumber(nums: number[]): string { const strs = nums.map(String); // Custom comparator: which concatenation order is bigger? strs.sort((a, b) => { const ab = a + b; const ba = b + a; return ba.localeCompare(ab); // descending — bigger first }); // Edge case: all zeros → "000..." should be "0" if (strs[0] === "0") return "0"; return strs.join(""); } // Time: O(n log n) — sorting with string comparisons // Space: O(n) — storing string representations // Key: the comparator (a, b) => (b+a).localeCompare(a+b) is transitive, // which guarantees the sort produces the globally optimal order.
Deep Dive: Interval Scheduling — Meeting Rooms II
Given meeting time intervals, find the minimum number of conference rooms required. The trick: separate start and end times, sort them independently, and sweep through with two pointers.
function minMeetingRooms(intervals: number[][]): number { const starts = intervals.map(i => i[0]).sort((a, b) => a - b); const ends = intervals.map(i => i[1]).sort((a, b) => a - b); let rooms = 0, maxRooms = 0; let s = 0, e = 0; while (s < starts.length) { if (starts[s] < ends[e]) { rooms++; // a meeting starts before the earliest one ends s++; } else { rooms--; // a meeting ends — free up a room e++; } maxRooms = Math.max(maxRooms, rooms); } return maxRooms; } // Time: O(n log n) for sorting // Space: O(n) for the separate arrays // Key insight: we don't need to track WHICH meeting ends — // just that SOME meeting ends before the next one starts.
Deep Dive: Bucket Sort — Top K Frequent Elements
When you need the top K elements by frequency, bucket sort gives you O(n) instead of O(n log n). Create buckets indexed by frequency, then collect from the highest bucket down.
function topKFrequent(nums: number[], k: number): number[] { // 1. Count frequencies const freq = new Map<number, number>(); for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1); // 2. Bucket sort: index = frequency, value = list of numbers const buckets: number[][] = Array.from({ length: nums.length + 1 }, () => []); for (const [num, count] of freq) { buckets[count].push(num); } // 3. Collect from highest frequency bucket const result: number[] = []; for (let i = buckets.length - 1; i >= 0 && result.length < k; i--) { result.push(...buckets[i]); } return result.slice(0, k); } // Time: O(n) — no comparison sort needed // Space: O(n) for frequency map and buckets // Why bucket sort? Frequency is bounded by n, so bucket index fits in [0, n].
Visual Walkthrough
Let's trace through two classic sorting problems step by step to see the pattern in action.
Merge Intervals — Step by Step
Input: [[1,3], [2,6], [8,10], [15,18]] Step 1: Sort by start time → [[1,3], [2,6], [8,10], [15,18]] (already sorted) Step 2: Initialize merged = [[1,3]] Step 3: Process [2,6] prev = [1,3], curr = [2,6] curr.start (2) ≤ prev.end (3) → OVERLAP Merge: prev.end = max(3, 6) = 6 merged = [[1,6]] Step 4: Process [8,10] prev = [1,6], curr = [8,10] curr.start (8) > prev.end (6) → NO OVERLAP Push new interval merged = [[1,6], [8,10]] Step 5: Process [15,18] prev = [8,10], curr = [15,18] curr.start (15) > prev.end (10) → NO OVERLAP Push new interval merged = [[1,6], [8,10], [15,18]] Answer: [[1,6], [8,10], [15,18]] Key: After sorting by start, we only compare with the LAST merged interval. Overlapping intervals are always adjacent in sorted order.
Sort Colors (Dutch National Flag) — Step by Step
Input: [2, 0, 2, 1, 1, 0] low=0, mid=0, high=5 Step 1: arr[mid]=2 → swap with high, high-- [0, 0, 2, 1, 1, 2] low=0, mid=0, high=4 Step 2: arr[mid]=0 → swap with low, low++, mid++ [0, 0, 2, 1, 1, 2] low=1, mid=1, high=4 Step 3: arr[mid]=0 → swap with low, low++, mid++ [0, 0, 2, 1, 1, 2] low=2, mid=2, high=4 Step 4: arr[mid]=2 → swap with high, high-- [0, 0, 1, 1, 2, 2] low=2, mid=2, high=3 Step 5: arr[mid]=1 → mid++ [0, 0, 1, 1, 2, 2] low=2, mid=3, high=3 Step 6: arr[mid]=1 → mid++ [0, 0, 1, 1, 2, 2] low=2, mid=4, high=3 mid > high → STOP Answer: [0, 0, 1, 1, 2, 2] Invariant maintained at every step: [0..low-1] = all 0s [low..mid-1] = all 1s [high+1..end] = all 2s [mid..high] = unprocessed
Largest Number — Custom Comparator Trace
Input: [3, 30, 34, 5, 9] Convert to strings: ["3", "30", "34", "5", "9"] Custom comparator: (a, b) => (b+a).localeCompare(a+b) Comparisons during sort: "3" vs "30": "303" vs "330" → "330" > "303" → 3 before 30 ✓ "3" vs "34": "343" vs "334" → "343" > "334" → 34 before 3 ✓ "5" vs "3": "35" vs "53" → "53" > "35" → 5 before 3 ✓ "9" vs "5": "59" vs "95" → "95" > "59" → 9 before 5 ✓ "9" vs "34": "349" vs "934" → "934" > "349" → 9 before 34 ✓ Sorted order: ["9", "5", "34", "3", "30"] Join: "9534330" Answer: "9534330" Key: The comparator (b+a) vs (a+b) is transitive — if a > b and b > c, then a > c. This guarantees the sort produces the globally optimal order.
Practice Problems
These 7 problems are carefully ordered from easy to hard. Each one teaches a different aspect of sorting as a problem-solving tool. Solve them in order.
LC 75. Sort Colors
Why this pattern:
Dutch National Flag — three pointers (low, mid, high) partition the array into 0s, 1s, and 2s in a single pass. This is the foundational in-place partitioning problem.
Key idea: low tracks where the next 0 goes, high tracks where the next 2 goes, mid scans. Swap 0s to low (advance both), skip 1s (advance mid), swap 2s to high (only decrement high — the swapped value is unchecked).
LC 56. Merge Intervals
Why this pattern:
Sort + linear scan. Sort intervals by start time, then iterate and merge overlapping intervals by comparing the current interval's start with the previous interval's end.
Key idea: After sorting by start, if current.start ≤ prev.end, merge by extending prev.end = max(prev.end, current.end). Otherwise, start a new interval. One pass after sorting.
LC 179. Largest Number
Why this pattern:
Custom comparator. Convert numbers to strings and sort with a comparator that compares concatenation orders: (a+b) vs (b+a). The comparator is transitive, guaranteeing global optimality.
Key idea: Sort strings by (b+a).localeCompare(a+b) for descending order. Join the result. Handle the edge case where the largest element is '0' (all zeros).
LC 435. Non-overlapping Intervals
Why this pattern:
Greedy interval scheduling. Sort by end time, then greedily keep intervals that don't overlap with the last kept one. Count the removed intervals.
Key idea: Sort by end time. Track the end of the last kept interval. If current.start ≥ lastEnd, keep it (update lastEnd). Otherwise, remove it (increment count). Sorting by end time is key — it maximizes the number of non-overlapping intervals.
LC 406. Queue Reconstruction by Height
Why this pattern:
Sort by multiple keys + greedy insertion. Sort by height descending, then by k ascending. Insert each person at index k in the result — taller people are already placed, so inserting at index k is always correct.
Key idea: Sort: tallest first (descending height), break ties by ascending k. Then insert each [h, k] at position k in the result array. Taller people don't care about shorter people in front of them.
LC 253. Meeting Rooms II
Why this pattern:
Sort + sweep line. Separate start and end times, sort each independently. Sweep through events: a start adds a room, an end frees a room. Track the maximum concurrent rooms.
Key idea: Sort starts and ends separately. Two pointers sweep through: if next event is a start (starts[s] < ends[e]), increment rooms. Otherwise, decrement. The peak is the answer.
LC 452. Minimum Number of Arrows to Burst Balloons
Why this pattern:
Greedy interval scheduling. Sort balloons by end coordinate. Shoot an arrow at the end of the first balloon. Skip all balloons that this arrow bursts (start ≤ arrow position). Repeat.
Key idea: Sort by end coordinate. Place arrow at first balloon's end. For each subsequent balloon: if its start > arrow position, it needs a new arrow (place at its end). Otherwise, the current arrow already bursts it.
Practice strategy
For each problem: (1) Identify which sorting template applies before coding. (2) Ask "what should I sort by?" — start time? end time? custom rule? (3) After solving, write one sentence: "Sorting by [key] works because [reason]."
Common Mistakes
These are the traps that catch people on sorting-based problems. Learn them here so you don't learn them in an interview.
Using default sort on numbers
In JavaScript/TypeScript, [10, 9, 2, 1].sort() gives [1, 10, 2, 9] because the default sort is lexicographic (string comparison), not numeric.
✅Always provide a comparator for numbers: arr.sort((a, b) => a - b) for ascending, arr.sort((a, b) => b - a) for descending. Never rely on the default sort for numeric arrays.
Sorting when you need original indices
You sort the array to find a pair, but the problem asks for the original indices (like Two Sum). Sorting destroys index information.
✅If you need original indices, either: (1) use a hash map instead of sorting, or (2) store [value, originalIndex] pairs and sort those. Always check if the problem asks for indices or values.
Sorting by the wrong key in interval problems
You sort intervals by start time when the problem needs end time (or vice versa). Merge Intervals needs sort-by-start, but Non-overlapping Intervals needs sort-by-end.
✅Ask: 'Am I merging intervals or selecting non-overlapping ones?' Merging → sort by start. Greedy selection → sort by end. The sort key determines whether the greedy choice is correct.
Not advancing mid after swapping with high in Dutch Flag
In Sort Colors, you swap nums[mid] with nums[high] and also advance mid. But the value swapped from high hasn't been checked yet — it could be a 0 that needs to go to low.
✅When swapping with high, only decrement high — don't advance mid. The swapped value needs to be examined. When swapping with low, advance both low and mid (the value from low is already processed).
Forgetting edge cases in custom comparators
In Largest Number, you sort correctly but return '000' instead of '0' when all elements are zero. Or your comparator isn't transitive, leading to undefined sort behavior.
✅Always check: (1) all-zeros edge case, (2) single element, (3) comparator transitivity (if a > b and b > c, then a > c). Test your comparator with a few examples before coding the full solution.
Using O(n log n) sort when O(n) is possible
The problem has a small value range (like Sort Colors with only 0, 1, 2) but you use comparison sort instead of counting sort or the Dutch National Flag algorithm.
✅When the value range is small and bounded (k values), counting sort gives O(n + k). When k = 3, Dutch National Flag gives O(n) with O(1) space. Always check if the value range is constrained.
Interview Strategy
Knowing when and how to sort is half the battle. Here's how to present it in an interview setting to maximize your score.
The 5-Step Interview Flow
Clarify & Identify the Sort Key
'These are intervals with start and end times, correct? Can intervals share endpoints? Are they already sorted?' — Clarifying the input structure tells you what to sort by and whether sorting is even needed.
State the Brute Force
'The brute force would compare every pair of intervals — O(n²). But I notice that if I sort by start time, overlapping intervals become adjacent.' — This shows you understand the baseline.
Justify the Sort
'Sorting by start time costs O(n log n) but reduces the merge step to O(n), giving O(n log n) total — much better than O(n²).' — Explicitly state WHY sorting helps and what it costs.
Code It Clean
Write the sort with a clear comparator. Use descriptive variable names (prev, curr, not a, b). Comment the sort key. Keep the post-sort logic simple and linear.
Test & Analyze
Trace through an example: 'For [[1,3],[2,6],[8,10],[15,18]]: after sorting (already sorted), merge [1,3] and [2,6] → [1,6]. [8,10] doesn't overlap. [15,18] doesn't overlap. Result: [[1,6],[8,10],[15,18]].' State complexity: O(n log n) time, O(n) space.
Common Follow-Up Questions
| Follow-Up | How to Handle |
|---|---|
| "Can you do it without sorting?" | For some problems, yes — counting sort for small ranges, bucket sort for frequencies, or hash maps. But for general comparison-based problems, O(n log n) is the lower bound. |
| "What if the data is streaming?" | You can't sort streaming data upfront. Use a heap/priority queue for online processing, or a balanced BST for maintaining sorted order incrementally. |
| "What if intervals are already sorted?" | Skip the sort step — go straight to the linear scan. Mention this optimization: 'If the input is pre-sorted, the solution is O(n) instead of O(n log n).' |
| "Sort by start or end time?" | Merging → sort by start (you need to see overlaps in order). Greedy selection → sort by end (earliest deadline first maximizes selections). Explain the reasoning. |
| "Is your sort stable?" | JavaScript's Array.sort is stable (guaranteed since ES2019). Stability matters when you sort by multiple keys — equal elements maintain their relative order from the previous sort. |
| "What's the space complexity of your sort?" | Merge sort: O(n). Quicksort: O(log n) stack space. JavaScript engines typically use TimSort (O(n) space). Mention this if the interviewer cares about space. |
The meta-skill
Interviewers love sorting problems because they test multiple skills at once: (1) recognizing that sorting helps, (2) choosing the right sort key, (3) writing a correct comparator, and (4) building the post-sort algorithm. Practice explaining your sort key choice out loud — it's the most important decision in these problems.
Summary & Cheat Sheet
Pin this section. Review it before mock interviews and contests.
Quick Revision Cheat Sheet
Core idea: Sorting transforms chaos into structure — O(n log n) preprocessing that unlocks O(n) algorithms
When to sort: Ask: 'Would sorted order make this easier?' If yes and indices don't matter, sort first
Sort + scan: Sort by start → merge intervals, detect overlaps, find closest neighbors
Sort by end: Greedy interval scheduling — earliest deadline first maximizes non-overlapping selections
Custom comparator: Define (a, b) => rule when default order doesn't match the problem's needs
Concatenation trick: Largest Number: sort by (b+a).localeCompare(a+b) — compare concatenation orders
Dutch National Flag: 3-way partition with low/mid/high pointers — O(n) time, O(1) space for 3 groups
Counting sort: O(n + k) when value range k is small — beats O(n log n) comparison sort
Bucket sort: Group by frequency → collect from highest bucket. O(n) for top-K frequency problems
Multi-key sort: Primary key first, tiebreak with secondary: sort((a,b) => a.x - b.x || a.y - b.y)
Stability: JS sort is stable (ES2019+). Equal elements keep their relative order from previous sort
Complexity: Comparison sort: O(n log n) time. Space: O(n) merge sort, O(log n) quicksort
Mental trigger: "Intervals" or "arrange elements in custom order" or "enable two pointers" → Sort first
Decision Flowchart
Does the problem involve intervals or ranges? ├── YES → Sort by start (merging) or end (greedy selection) └── NO → Does the problem need a custom ordering? ├── YES → Custom comparator sort └── NO → Would sorted order enable Two Pointers or Binary Search? ├── YES → Sort first, then apply the pattern └── NO → Is the value range small and bounded? ├── YES → Counting sort or Dutch National Flag (O(n)) └── NO → Do you need top-K by frequency? ├── YES → Bucket sort or Heap └── NO → Sorting probably isn't the right pattern → Consider Hashing, Sliding Window, or DP
Sorting Algorithm Quick Reference
| Algorithm | Time (avg) | Time (worst) | Space | Stable? | When to Use |
|---|---|---|---|---|---|
| Merge Sort | O(n log n) | O(n log n) | O(n) | Yes | Default choice; guaranteed performance |
| Quick Sort | O(n log n) | O(n²) | O(log n) | No | In-place needed; good cache performance |
| Tim Sort | O(n log n) | O(n log n) | O(n) | Yes | JS/Python default; great on real-world data |
| Counting Sort | O(n + k) | O(n + k) | O(k) | Yes | Small integer range (k ≪ n) |
| Bucket Sort | O(n + k) | O(n²) | O(n + k) | Depends | Uniformly distributed data; frequency grouping |
| Radix Sort | O(d × n) | O(d × n) | O(n + k) | Yes | Fixed-length integers or strings |
One last thing
Sorting is the most underrated pattern in DSA interviews. It's not flashy like DP or graph algorithms, but it appears in a huge number of medium-level problems. The key skill isn't knowing how to implement merge sort — it's recognizing when sorting is the right first step and choosing the correct sort key. Master that, and you'll unlock a whole category of problems.