SortingCustom ComparatorsArraysGreedyIntervals

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.

40 min read10 sections
01

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.

02

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

PatternWhen to UseKey Difference
SortingNeed ordered data for greedy, intervals, or enabling other patternsO(n log n) preprocessing step; unlocks simpler algorithms
HashingNeed O(1) lookups, frequency counting, or must preserve indicesO(n) time, O(n) space; no ordering, just fast lookups
Heap / Priority QueueNeed top-K elements or streaming min/maxPartial sort — O(n log k) instead of full O(n log n) sort
Counting SortSmall value range (e.g., 0-2 in Sort Colors)O(n + k) time where k is the range; beats comparison sort
Two PointersSorted data + pair/triplet searchRequires 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.

03

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.

1

Brute Force — Compare Every Pair

O(n²) time · O(n) space

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.

Brute Forcetypescript
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.
2

Optimal — Sort by Start Time, Then Single Pass

O(n log n) time · O(n) space

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.

Sort + Merge — Optimaltypescript
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?

1

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.

2

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.

3

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.

04

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.

Sort + Linear Scan Templatetypescript
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).

Custom Comparator Templatetypescript
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.

Sort + Two Pointers Templatetypescript
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.

Dutch National Flag Templatetypescript
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.

05

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.

VariationSorting StrategyClassic Example
Interval MergingSort by start time, merge adjacent overlapsMerge Intervals, Insert Interval
Interval Scheduling (Greedy)Sort by end time, greedily pick non-overlappingMeeting Rooms II, Non-overlapping Intervals
Custom ComparatorDefine problem-specific ordering ruleLargest Number, Custom Sort String
Sort + Two PointersSort to enable pair/triplet search3Sum, 3Sum Closest, Valid Triangle Number
Dutch National Flag3-way partition with three pointers in O(n)Sort Colors, Move Zeroes (2-way variant)
Counting SortCount occurrences when value range is smallSort Colors, H-Index, Relative Sort Array
Bucket Sort / Frequency SortGroup by frequency, then collect bucketsTop K Frequent Elements, Sort Characters by Frequency
Sort by Multiple KeysPrimary key + tiebreaker secondary keyReorder 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.

Largest Number — Custom Comparatortypescript
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.

Meeting Rooms II — Sort + Sweeptypescript
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.

Top K Frequent — Bucket Sorttypescript
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].
06

Visual Walkthrough

Let's trace through two classic sorting problems step by step to see the pattern in action.

Merge Intervals — Step by Step

Merge Intervals — Tracetext
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

Sort Colors — Tracetext
Input: [2, 0, 2, 1, 1, 0]
       low=0, mid=0, high=5

Step 1: arr[mid]=2swap with high, high--
  [0, 0, 2, 1, 1, 2]  low=0, mid=0, high=4

Step 2: arr[mid]=0swap with low, low++, mid++
  [0, 0, 2, 1, 1, 2]  low=1, mid=1, high=4

Step 3: arr[mid]=0swap with low, low++, mid++
  [0, 0, 2, 1, 1, 2]  low=2, mid=2, high=4

Step 4: arr[mid]=2swap with high, high--
  [0, 0, 1, 1, 2, 2]  low=2, mid=2, high=3

Step 5: arr[mid]=1mid++
  [0, 0, 1, 1, 2, 2]  low=2, mid=3, high=3

Step 6: arr[mid]=1mid++
  [0, 0, 1, 1, 2, 2]  low=2, mid=4, high=3

mid > highSTOP

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

Largest Number — Tracetext
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 transitiveif a > b and b > c,
then a > c. This guarantees the sort produces the globally optimal order.
07

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.

1

LC 75. Sort Colors

Medium

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).

2

LC 56. Merge Intervals

Medium

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.

3

LC 179. Largest Number

Medium

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).

4

LC 435. Non-overlapping Intervals

Medium

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.

5

LC 406. Queue Reconstruction by Height

Medium

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.

6

LC 253. Meeting Rooms II

Medium

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.

7

LC 452. Minimum Number of Arrows to Burst Balloons

Medium

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]."

08

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.

09

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

1

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.

2

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.

3

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.

4

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.

5

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-UpHow 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.

10

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

When to Use Sorting — Decision Treetext
Does the problem involve intervals or ranges?
├── YESSort by start (merging) or end (greedy selection)
└── NODoes the problem need a custom ordering?
          ├── YESCustom comparator sort
          └── NOWould sorted order enable Two Pointers or Binary Search?
                    ├── YESSort first, then apply the pattern
                    └── NOIs the value range small and bounded?
                              ├── YESCounting sort or Dutch National Flag (O(n))
                              └── NODo you need top-K by frequency?
                                        ├── YESBucket sort or Heap
                                        └── NOSorting probably isn't the right pattern
Consider Hashing, Sliding Window, or DP

Sorting Algorithm Quick Reference

AlgorithmTime (avg)Time (worst)SpaceStable?When to Use
Merge SortO(n log n)O(n log n)O(n)YesDefault choice; guaranteed performance
Quick SortO(n log n)O(n²)O(log n)NoIn-place needed; good cache performance
Tim SortO(n log n)O(n log n)O(n)YesJS/Python default; great on real-world data
Counting SortO(n + k)O(n + k)O(k)YesSmall integer range (k ≪ n)
Bucket SortO(n + k)O(n²)O(n + k)DependsUniformly distributed data; frequency grouping
Radix SortO(d × n)O(d × n)O(n + k)YesFixed-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.