Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Coding Interview Patterns  Module 7 – Sorting as a tool

Sort first: what sorting buys you

Why sorting first turns duplicate, closest-pair and pair-sum problems into one scan, how to keep original indices, and which sorts are stable.

  • Beginner
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0 and quickjs 0.32.0
  • By MySmartCoPilot
Free: 30 minutes

What you will learn

  • Recognise problems that become simple once the input is sorted
  • Prove that the two closest values are neighbours after sorting
  • Sort indices or (value, index) pairs to keep the original positions
  • Choose a stable sort in Python, JavaScript, Java, C++ and Go when ties must keep their order

Before you start

What you’ll learn · Before you start

What you will learn

  • Recognise problems that become simple once the input is sorted
  • Prove that the two closest values are neighbours after sorting
  • Sort indices or (value, index) pairs to keep the original positions
  • Choose a stable sort in Python, JavaScript, Java, C++ and Go when ties must keep their order

Before you start

On this page

Sorting costs O(n log n) once, and it buys an order you can reason about. Four things follow from that order, and between them they cover a large share of coding-round problems. Equal values end up side by side, so repeats and groups show up in one scan. Close values end up side by side too, so the two closest values are always neighbours. The order lets two pointers and binary search rule out many candidates with each comparison. And it gives greedy algorithms an order to decide in, such as smallest first or earliest deadline first. The price is the original order. When the answer needs positions, they have to travel through the sort, and when equal items must keep their order, the sort has to be stable. This lesson solves one problem completely, in five languages, and then deals with both costs.

What sorting buys

Each gain turns a question about every pair into a question about neighbours, or about one side of a comparison. The cue is usually in the wording of the problem:

The problem asks What sorting gives Then
Is any value repeated? How many distinct values are there? Equal values side by side One scan, each value against the next
The two closest values, or the smallest difference Close values side by side The n − 1 neighbour gaps
Two values with a given sum, or three Small values at one end, large ones at the other Two pointers moving inwards
How many values lie between a and b? Every value in order Two binary searches
Merge overlapping bookings, or find the most at one time Bookings, or their start and end events, in time order One sweep from left to right
Give tasks to workers, or pair items cheaply Smallest to largest A greedy rule, and a proof that it is safe

Two habits make the move pay. First, say what it costs. The sort is O(n log n): for n = 2 × 10⁵ that is about 3.5 million comparisons, well inside a typical time limit, as the lesson on reading constraints worked out. Second, say what it destroys. A sorted array has lost the order the input came in, which means the positions and the order among equal items. Both can be kept, as the later sections of this lesson show, but only if you plan for them before you sort.

A problem solved in full: the closest departures

The problem

A delivery network logs every van that leaves one of its depots, as the second of the day it left, from 0 to 86,399. The log arrives in the order the depots report, not in time order. Find the two departures that are closest in time: their positions in the log, and the gap in seconds. If several pairs share the smallest gap, any one of them is a right answer. A busy day has up to 50,000 departures.

Two logs serve as examples. The morning log, positions 0 to 7, is [30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700], and its answer is positions 0 and 3, 75 seconds apart. The noon log, [43200, 43260, 43200], has two vans that left in the same second, so its answer is positions 0 and 2, 0 seconds apart.

The brute force, and why it is too slow

Compare every pair. That is n(n − 1)/2 gaps, and it is right by construction, because no pair is skipped: it is the plan the faster one will be checked against. For 50,000 departures it computes 1,249,975,000 gaps. The lesson on constraints budgets CPython at roughly 10⁷ simple steps a second, which makes this about two minutes of work; compiled code, at roughly 10⁸ a second, would still take more than ten seconds.

Sort, then compare neighbours

Put the times in order, t0≤t1≤⋯≤tn−1t_0 \le t_1 \le \dots \le t_{n-1}, and take any pair tit_i and tjt_j with i<ji < j. Their gap is the sum of the gaps between the neighbours from position i to position j:

tj−ti=(ti+1−ti)+(ti+2−ti+1)+⋯+(tj−tj−1)≥ti+1−tit_j - t_i = (t_{i+1} - t_i) + (t_{i+2} - t_{i+1}) + \cdots + (t_j - t_{j-1}) \ge t_{i+1} - t_i

No term of the sum is negative, so the sum is at least its first term. Every pair therefore has a pair of neighbours that is at least as close, and the smallest of the n − 1 neighbour gaps is the smallest gap of all. That short argument is the proof, and it is worth saying in a round, because it is what makes skipping every other pair safe. The cost is the sort, O(n log n), and one scan, O(n).

The answer needs positions, and sorting the times on their own would lose them. So sort the positions instead, with each position’s time as its sort key. The sorted list then reads “position 7 first, then 1, then 4”, and every entry still says where it was in the log:

Eight departures as reported and sorted by time, with the gap to each neighbour; the closest two, 75 s apart, end up side by side.As reportedSorted by time#030125#129830#231400#330050#429955#531310#630990#729700#729700#129830#429955#330050#030125#630990#531310#231400130125957586532090Closest: #0 and #3, 75 s apart, now neighbours

The morning log before and after sorting

Text description of the diagram

The diagram has two columns of eight boxes. Each box shows a departure's position in the log (#0 to #7) and its time in seconds after midnight.

  1. The left column, "As reported", is the log in the order the depots reported it: #0 30125, #1 29830, #2 31400, #3 30050, #4 29955, #5 31310, #6 30990 and #7 29700. The two departures that turn out to be closest, #0 and #3, are highlighted; two other departures sit between them in the log.
  2. The right column, "Sorted by time", holds the same eight departures from earliest to latest: #7 29700, #1 29830, #4 29955, #3 30050, #0 30125, #6 30990, #5 31310 and #2 31400.
  3. Beside the right column, a bracket joins each departure to the next one and is labelled with the gap between them in seconds: 130, 125, 95, 75, 865, 320 and 90. These seven gaps are the only ones the sorted plan computes.
  4. The smallest gap, 75 seconds, joins #3 (30050) and #0 (30125), which are now neighbours; their bracket is drawn thicker. The line under the columns says: closest, #0 and #3, 75 s apart, now neighbours.

A dry run on the morning log

Sorted by time, the positions are 7, 1, 4, 3, 0, 6, 5 and 2, as in the right-hand column above. The scan looks at each pair of neighbours once and keeps the smallest gap so far, with the two positions that have it:

Pair Gap (s) Best so far
#7 and #1 130 130, #1 and #7
#1 and #4 125 125, #1 and #4
#4 and #3 95 95, #3 and #4
#3 and #0 75 75, #0 and #3
#0 and #6 865 75
#6 and #5 320 75
#5 and #2 90 75

Seven gaps instead of 28, and the answer is positions 0 and 3, 75 seconds apart. In the log those two were three places apart; after sorting they sit side by side.

The code in five languages

Each version holds both plans, the brute force and sort first, and prints both answers for both logs:

Every pair and sort first, in Python and JavaScript

Python · closest/closest.py

"""The two van departures closest in time: every pair, or sort first and compare neighbours only."""

# Seconds after midnight of each departure, in the order the depots reported them: positions 0 to 7.
MORNING = [30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700]
# Two vans that left in the same second (positions 0 and 2).
NOON = [43200, 43260, 43200]


def closest_brute(times):
    """Every pair of positions, n(n - 1)/2 gaps. Returns (gap, first position, second position)."""
    best = None
    for i in range(len(times)):
        for j in range(i + 1, len(times)):
            gap = abs(times[i] - times[j])
            if best is None or gap < best[0]:
                best = (gap, i, j)
    return best


def closest_sorted(times):
    """Sort the positions by time, then compare each departure with the next one only: n - 1 gaps."""
    order = sorted(range(len(times)), key=lambda i: times[i])
    best = None
    for a, b in zip(order, order[1:]):
        gap = times[b] - times[a]
        if best is None or gap < best[0]:
            best = (gap, min(a, b), max(a, b))
    return best


def show(name, times):
    print(f"{name}, {len(times)} departures")
    for plan, find in (("every pair", closest_brute), ("sorted first", closest_sorted)):
        gap, i, j = find(times)
        print(f"  {plan}: positions {i} and {j}, {gap} s apart")


if __name__ == "__main__":
    show("Morning", MORNING)
    show("Noon", NOON)

Output

Morning, 8 departures
  every pair: positions 0 and 3, 75 s apart
  sorted first: positions 0 and 3, 75 s apart
Noon, 3 departures
  every pair: positions 0 and 2, 0 s apart
  sorted first: positions 0 and 2, 0 s apart

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 closest.py

JavaScript · closest/closest.mjs

// The two van departures closest in time: every pair, or sort first and compare neighbours only.

// Seconds after midnight of each departure, in the order the depots reported them: positions 0 to 7.
const MORNING = [30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700];
// Two vans that left in the same second (positions 0 and 2).
const NOON = [43200, 43260, 43200];

// Every pair of positions, n(n - 1)/2 gaps. Returns [gap, first position, second position].
function closestBrute(times) {
  let best = null;
  for (let i = 0; i < times.length; i++) {
    for (let j = i + 1; j < times.length; j++) {
      const gap = Math.abs(times[i] - times[j]);
      if (best === null || gap < best[0]) best = [gap, i, j];
    }
  }
  return best;
}

// Sort the positions by time, then compare each departure with the next one only: n - 1 gaps.
// Without the comparator, sort() would compare the numbers as strings.
function closestSorted(times) {
  const order = times.map((_, i) => i).sort((a, b) => times[a] - times[b]);
  let best = null;
  for (let k = 0; k + 1 < order.length; k++) {
    const [a, b] = [order[k], order[k + 1]];
    const gap = times[b] - times[a];
    if (best === null || gap < best[0]) best = [gap, Math.min(a, b), Math.max(a, b)];
  }
  return best;
}

function show(name, times) {
  console.log(`${name}, ${times.length} departures`);
  for (const [plan, find] of [["every pair", closestBrute], ["sorted first", closestSorted]]) {
    const [gap, i, j] = find(times);
    console.log(`  ${plan}: positions ${i} and ${j}, ${gap} s apart`);
  }
}

show("Morning", MORNING);
show("Noon", NOON);

Output

Morning, 8 departures
  every pair: positions 0 and 3, 75 s apart
  sorted first: positions 0 and 3, 75 s apart
Noon, 3 departures
  every pair: positions 0 and 2, 0 s apart
  sorted first: positions 0 and 2, 0 s apart

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node closest.mjs

The same two plans in Java, C++ and Go

Java · compiled/Closest.java

import java.util.Arrays;
import java.util.Comparator;

// The two van departures closest in time: every pair, or sort first and compare neighbours only.
public class Closest {
    record Answer(int gap, int first, int second) {}

    // Every pair of positions, n(n - 1)/2 gaps.
    static Answer closestBrute(int[] times) {
        Answer best = null;
        for (int i = 0; i < times.length; i++) {
            for (int j = i + 1; j < times.length; j++) {
                int gap = Math.abs(times[i] - times[j]);
                if (best == null || gap < best.gap()) best = new Answer(gap, i, j);
            }
        }
        return best;
    }

    // Sort the positions by time, then compare each departure with the next one only: n - 1 gaps.
    // An int[] cannot be sorted with a comparator, so the positions are boxed as Integer.
    static Answer closestSorted(int[] times) {
        Integer[] order = new Integer[times.length];
        for (int i = 0; i < order.length; i++) order[i] = i;
        Arrays.sort(order, Comparator.comparingInt(i -> times[i]));
        Answer best = null;
        for (int k = 0; k + 1 < order.length; k++) {
            int a = order[k], b = order[k + 1];
            int gap = times[b] - times[a];
            if (best == null || gap < best.gap()) best = new Answer(gap, Math.min(a, b), Math.max(a, b));
        }
        return best;
    }

    static void show(String name, int[] times) {
        System.out.println(name + ", " + times.length + " departures");
        Answer[] answers = {closestBrute(times), closestSorted(times)};
        String[] plans = {"every pair", "sorted first"};
        for (int p = 0; p < 2; p++) {
            Answer x = answers[p];
            System.out.println("  " + plans[p] + ": positions " + x.first() + " and " + x.second() + ", " + x.gap() + " s apart");
        }
    }

    public static void main(String[] args) {
        // Seconds after midnight of each departure, in the order the depots reported them: positions 0 to 7.
        show("Morning", new int[] {30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700});
        // Two vans that left in the same second (positions 0 and 2).
        show("Noon", new int[] {43200, 43260, 43200});
    }
}

C++ · compiled/closest.cpp

// The two van departures closest in time: every pair, or sort first and compare neighbours only.
#include <algorithm>
#include <cstdlib>
#include <numeric>
#include <print>
#include <string_view>
#include <vector>

struct Answer {
    int gap = -1;  // -1: nothing compared yet
    int first = 0;
    int second = 0;
};

// Every pair of positions, n(n - 1)/2 gaps.
Answer closest_brute(const std::vector<int>& times) {
    Answer best;
    for (int i = 0; i < std::ssize(times); ++i) {
        for (int j = i + 1; j < std::ssize(times); ++j) {
            int gap = std::abs(times[i] - times[j]);
            if (best.gap < 0 || gap < best.gap) best = {gap, i, j};
        }
    }
    return best;
}

// Sort the positions by time, then compare each departure with the next one only: n - 1 gaps.
Answer closest_sorted(const std::vector<int>& times) {
    std::vector<int> order(times.size());
    std::iota(order.begin(), order.end(), 0);  // 0, 1, 2, ...
    std::sort(order.begin(), order.end(), [&](int a, int b) { return times[a] < times[b]; });
    Answer best;
    for (std::size_t k = 0; k + 1 < order.size(); ++k) {
        int a = order[k], b = order[k + 1];
        int gap = times[b] - times[a];
        if (best.gap < 0 || gap < best.gap) best = {gap, std::min(a, b), std::max(a, b)};
    }
    return best;
}

void show(std::string_view name, const std::vector<int>& times) {
    std::println("{}, {} departures", name, times.size());
    for (auto [plan, answer] : {std::pair{"every pair", closest_brute(times)}, std::pair{"sorted first", closest_sorted(times)}}) {
        std::println("  {}: positions {} and {}, {} s apart", plan, answer.first, answer.second, answer.gap);
    }
}

int main() {
    // Seconds after midnight of each departure, in the order the depots reported them: positions 0 to 7.
    show("Morning", {30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700});
    // Two vans that left in the same second (positions 0 and 2).
    show("Noon", {43200, 43260, 43200});
}

Go · compiled/closest.go

// The two van departures closest in time: every pair, or sort first and compare neighbours only.
package main

import (
	"cmp"
	"fmt"
	"slices"
)

type answer struct{ gap, first, second int }

// closestBrute compares every pair of positions: n(n - 1)/2 gaps.
func closestBrute(times []int) answer {
	best := answer{gap: -1}
	for i := range times {
		for j := i + 1; j < len(times); j++ {
			gap := max(times[i]-times[j], times[j]-times[i])
			if best.gap < 0 || gap < best.gap {
				best = answer{gap, i, j}
			}
		}
	}
	return best
}

// closestSorted sorts the positions by time, then compares each departure with the next one only: n - 1 gaps.
func closestSorted(times []int) answer {
	order := make([]int, len(times))
	for i := range order {
		order[i] = i
	}
	slices.SortFunc(order, func(a, b int) int { return cmp.Compare(times[a], times[b]) })
	best := answer{gap: -1}
	for k := 0; k+1 < len(order); k++ {
		a, b := order[k], order[k+1]
		if gap := times[b] - times[a]; best.gap < 0 || gap < best.gap {
			best = answer{gap, min(a, b), max(a, b)}
		}
	}
	return best
}

func show(name string, times []int) {
	fmt.Printf("%s, %d departures\n", name, len(times))
	for _, plan := range []struct {
		name   string
		answer answer
	}{{"every pair", closestBrute(times)}, {"sorted first", closestSorted(times)}} {
		fmt.Printf("  %s: positions %d and %d, %d s apart\n", plan.name, plan.answer.first, plan.answer.second, plan.answer.gap)
	}
}

func main() {
	// Seconds after midnight of each departure, in the order the depots reported them: positions 0 to 7.
	show("Morning", []int{30125, 29830, 31400, 30050, 29955, 31310, 30990, 29700})
	// Two vans that left in the same second (positions 0 and 2).
	show("Noon", []int{43200, 43260, 43200})
}

Build and run all three · compiled/closest_session.py

from session import run, versions

# Closest.java runs as a single source file, closest.cpp is built with the sanitizers on, and go run builds closest.go.
versions("java -version", "clang++ --version", "go version")
run("""
java Closest.java
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined closest.cpp -o closest && ./closest
go run closest.go
""")

Output

# openjdk version "25.0.4.1" LTS
# Apple clang version 21.0.0 (clang-2100.3.34.2)
# go version go1.27.1 darwin/arm64
$ java Closest.java
Morning, 8 departures
  every pair: positions 0 and 3, 75 s apart
  sorted first: positions 0 and 3, 75 s apart
Noon, 3 departures
  every pair: positions 0 and 2, 0 s apart
  sorted first: positions 0 and 2, 0 s apart
$ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined closest.cpp -o closest && ./closest
Morning, 8 departures
  every pair: positions 0 and 3, 75 s apart
  sorted first: positions 0 and 3, 75 s apart
Noon, 3 departures
  every pair: positions 0 and 2, 0 s apart
  sorted first: positions 0 and 2, 0 s apart
$ go run closest.go
Morning, 8 departures
  every pair: positions 0 and 3, 75 s apart
  sorted first: positions 0 and 3, 75 s apart
Noon, 3 departures
  every pair: positions 0 and 2, 0 s apart
  sorted first: positions 0 and 2, 0 s apart

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 closest_session.py

All five print the same six lines. The differences are in how each language sorts positions by their times:

  • Python sorts range(len(times)) with key=lambda i: times[i], and sorted() leaves the log as it was.
  • JavaScript needs the comparator (a, b) => times[a] - times[b]. Without one, sort() converts the elements to strings and compares those, so 10 would come before 9.
  • Java has no Arrays.sort that takes an int[] and a comparator, so the positions go into an Integer[] and are sorted with Comparator.comparingInt.
  • C++ fills a vector with the positions using std::iota, then sorts it with a lambda that compares their times.
  • Go sorts a slice of positions with slices.SortFunc, comparing their times with cmp.Compare.

Check it against the brute force

The brute force is slow, but it is right, which makes it the referee. This program runs the sorted plan on 1,000 random logs of 2 to 9 departures, with small times so that repeats are common, and puts two plausible slips through the same check:

1,000 random logs: the sorted plan and two slips Python · closest/check.py
"""1,000 random logs: the sorted plan against the brute force, and two plausible slips the same check catches."""
import random

from closest import closest_brute, closest_sorted


def slip_sorted_places(times):
    """Slip 1: compares neighbours of the sorted times, but reports their places in the sorted list."""
    ordered = sorted(times)
    best = None
    for k in range(len(ordered) - 1):
        gap = ordered[k + 1] - ordered[k]
        if best is None or gap < best[0]:
            best = (gap, k, k + 1)
    return best


def slip_index_lookup(times):
    """Slip 2: sorts the times, then finds each one's position with list.index(), which returns the first match."""
    ordered = sorted(times)
    best = None
    for k in range(len(ordered) - 1):
        gap = ordered[k + 1] - ordered[k]
        if best is None or gap < best[0]:
            i, j = times.index(ordered[k]), times.index(ordered[k + 1])
            best = (gap, min(i, j), max(i, j))
    return best


def is_right(times, answer):
    """Right means: two different positions, really that far apart, and no pair of the log is closer."""
    gap, i, j = answer
    return i != j and abs(times[i] - times[j]) == gap and gap == closest_brute(times)[0]


rng = random.Random(3)
logs = [[rng.randrange(40) for _ in range(rng.randint(2, 9))] for _ in range(1_000)]
logs.sort(key=len)  # shortest logs first, so the first failure found is a small one
for plan in (closest_sorted, slip_sorted_places, slip_index_lookup):
    wrong = next((log for log in logs if not is_right(log, plan(log))), None)
    verdict = "right on all 1,000 logs" if wrong is None else f"wrong on {wrong}: it says {plan(wrong)}"
    print(f"{plan.__name__}: {verdict}")

Output

closest_sorted: right on all 1,000 logs
slip_sorted_places: wrong on [13, 36, 17]: it says (4, 0, 1)
slip_index_lookup: wrong on [25, 25]: it says (0, 0, 0)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check.py

The sorted plan is right on every log. The first slip compares the right neighbours but reports their places in the sorted list instead of their positions in the log: on [13, 36, 17] it names positions 0 and 1, which are 23 seconds apart, not 4. The second sorts the times and then asks index() where each one came from, and on [25, 25] it names position 0 twice, because index() always returns the first match. The check accepts an answer when it names two different positions, their times really differ by the reported gap, and no pair of the log is closer, so any of several equally close pairs passes. The logs are sorted by length first, so the first failure found is a short one that you can trace by hand. The lesson on stress testers turns this kind of check into a harness you can reuse.

How much work sorting saves

Counting comparisons gives numbers that are the same on every computer. Python’s sort compares items only with <, as its documentation states, so a class that defines __lt__ and counts its calls sees every comparison:

Comparisons on random logs of three sizes Python · counts/counts.py
"""Comparisons made by every pair and by sort first, on random departure logs (a fixed seed: the same counts each run)."""
import random


class Departure:
    """A departure time that counts how often the sort compares two of them."""

    comparisons = 0

    def __init__(self, seconds):
        self.seconds = seconds

    def __lt__(self, other):
        Departure.comparisons += 1
        return self.seconds < other.seconds


rng = random.Random(8)
print(f"{'n':>6}  {'every pair':>13}  {'sort':>7}  {'scan':>6}  {'fewer':>6}")
for n in (1_000, 10_000, 50_000):
    log = [Departure(rng.randrange(86_400)) for _ in range(n)]
    Departure.comparisons = 0
    log.sort()
    pairs, sort, scan = n * (n - 1) // 2, Departure.comparisons, n - 1
    print(f"{n:>6,}  {pairs:>13,}  {sort:>7,}  {scan:>6,}  {pairs // (sort + scan):>5,}x")

Output

     n     every pair     sort    scan   fewer
 1,000        499,500    8,641     999     51x
10,000     49,995,000  120,216   9,999    383x
50,000  1,249,975,000  715,850  49,999  1,632x

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 counts.py

At 50,000 departures the sort made 715,850 comparisons and the scan 49,999 subtractions, against 1,249,975,000 gaps for the brute force: about 1,600 times fewer. The difference widens as n grows, because the brute force grows with n² and the sort only with about n log₂ n. Side by side, with the sorted list of positions as the sorted plan’s extra space:

The two plans for n departures
Operation Time Extra space
Every pair O(n²) O(1)
Sort, then scan O(n log n) O(n)

Keeping the original positions

Positions travel through a sort in two ways, and there is one common way to lose them:

Two ways to keep positions, and a slip Python · positions/positions.py
"""Three ways to sort departure times and say where each one was in the log; the last one is a slip."""

times = [300, 120, 300, 45]  # seconds; positions 0 to 3; positions 0 and 2 share a time

# 1. Sort (time, position) pairs. Tuples compare item by item, so equal times come out in position order.
pairs = sorted((t, i) for i, t in enumerate(times))
print("pairs:     ", pairs)

# 2. Sort the positions, using each position's time as the key. The sort is stable: equal times keep log order.
order = sorted(range(len(times)), key=lambda i: times[i])
print("positions: ", order)

# The slip: sort the times, then look each one up again. index() returns the first match, every time.
looked_up = [times.index(t) for t in sorted(times)]
print("index():   ", looked_up)

Output

pairs:      [(45, 3), (120, 1), (300, 0), (300, 2)]
positions:  [3, 1, 0, 2]
index():    [3, 1, 0, 0]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 positions.py

  • Sort (value, position) pairs. Tuples compare item by item, so pairs sort by value and then by position. In Python and C++ (std::pair) that needs no comparator at all.
  • Sort the positions by key. This is what the five versions above do. Each position is sorted by its own value, and with a stable sort, equal values keep their order from the log, so ties come out in log order too.
  • Never sort the values and look them up again. index() finds the first match, so both 300s report position 0 and position 2 is lost. Each lookup is also a scan of the list, which makes the whole thing O(n²).

Either correct way costs n extra entries. That is the price of the answer, and it is worth saying when memory is part of the question.

Stable sorts, language by language

A sort is stable when items with equal keys come out in the order they went in. It matters in three common situations: sorting in two steps (by the second key, then stably by the first), sorting positions by their values (ties keep log order), and any output where the order of ties is visible, to a test or to a user. Each language documents which of its sorts promise it:

  • Python: sorted() and list.sort() are stable, and the built-in sorts have no unstable variant.
  • JavaScript: Array.prototype.sort() and toSorted() are stable; the specification requires it.
  • Java: List.sort, Collections.sort and Arrays.sort on object arrays are stable. Arrays.sort on a primitive array uses Dual-Pivot Quicksort and promises no stability, which cannot matter for integers: equal integers are identical.
  • C++: std::stable_sort and std::ranges::stable_sort are stable. std::sort and std::ranges::sort are not guaranteed to be.
  • Go: sort.SliceStable, sort.Stable and slices.SortStableFunc are stable. sort.Slice, sort.Sort and slices.SortFunc are not guaranteed to be.

Here is what the difference looks like in practice. A class register of 24 students is in alphabetical order, each with a grade from A to C, and every version sorts it by grade only. A stable sort must leave each grade in alphabetical order, which also means it sorts by grade and then by name in one call. Each program counts how many students end up somewhere other than where a stable sort puts them, and prints the students with grade A:

The register sorted by grade, in Python and JavaScript

Python · stability/stability.py

"""A class register in alphabetical order, sorted by grade: does each grade keep the register's order?"""

# (name, grade) in alphabetical order of name, the order of the class register.
REGISTER = [
    ("Aarav", "B"), ("Bea", "A"), ("Chen", "A"), ("Divya", "B"), ("Elif", "B"), ("Farah", "A"),
    ("Gopal", "A"), ("Hana", "C"), ("Ishaan", "C"), ("Jonas", "C"), ("Kavya", "B"), ("Leo", "C"),
    ("Meera", "C"), ("Noor", "B"), ("Omar", "C"), ("Priya", "C"), ("Quinn", "A"), ("Ravi", "B"),
    ("Sana", "B"), ("Tomas", "A"), ("Uma", "B"), ("Vikram", "C"), ("Wen", "A"), ("Zoya", "A"),
]
# What a stable sort by grade must give: grade A in register order, then grade B, then grade C.
STABLE = [s for grade in "ABC" for s in REGISTER if s[1] == grade]


def report(sort_name, students):
    moved = sum(1 for got, want in zip(students, STABLE) if got != want)
    grade_a = " ".join(name for name, grade in students if grade == "A")
    print(f"{sort_name} ({moved} of {len(students)} moved)")
    print(f"  grade A: {grade_a}")


report("sorted()", sorted(REGISTER, key=lambda s: s[1]))

in_place = list(REGISTER)
in_place.sort(key=lambda s: s[1])
report("list.sort()", in_place)

Output

sorted() (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
list.sort() (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stability.py

JavaScript · stability/stability.mjs

// A class register in alphabetical order, sorted by grade: does each grade keep the register's order?

// [name, grade] in alphabetical order of name, the order of the class register.
const REGISTER = [
  ["Aarav", "B"], ["Bea", "A"], ["Chen", "A"], ["Divya", "B"], ["Elif", "B"], ["Farah", "A"],
  ["Gopal", "A"], ["Hana", "C"], ["Ishaan", "C"], ["Jonas", "C"], ["Kavya", "B"], ["Leo", "C"],
  ["Meera", "C"], ["Noor", "B"], ["Omar", "C"], ["Priya", "C"], ["Quinn", "A"], ["Ravi", "B"],
  ["Sana", "B"], ["Tomas", "A"], ["Uma", "B"], ["Vikram", "C"], ["Wen", "A"], ["Zoya", "A"],
];
// What a stable sort by grade must give: grade A in register order, then grade B, then grade C.
const STABLE = ["A", "B", "C"].flatMap((grade) => REGISTER.filter((s) => s[1] === grade));

function report(sortName, students) {
  const moved = students.filter((s, i) => s !== STABLE[i]).length;
  const gradeA = students.filter((s) => s[1] === "A").map((s) => s[0]).join(" ");
  console.log(`${sortName} (${moved} of ${students.length} moved)`);
  console.log(`  grade A: ${gradeA}`);
}

const byGrade = (x, y) => (x[1] === y[1] ? 0 : x[1] < y[1] ? -1 : 1);

report("Array.prototype.sort", [...REGISTER].sort(byGrade));
report("Array.prototype.toSorted", REGISTER.toSorted(byGrade));

Output

Array.prototype.sort (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
Array.prototype.toSorted (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node stability.mjs

The register sorted by grade, in Java, C++ and Go

Java · compiled/Stability.java

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.StringJoiner;

// A class register in alphabetical order, sorted by grade: does each grade keep the register's order?
public class Stability {
    record Student(String name, char grade) {}

    // In alphabetical order of name, the order of the class register.
    static final List<Student> REGISTER = List.of(
        new Student("Aarav", 'B'), new Student("Bea", 'A'), new Student("Chen", 'A'), new Student("Divya", 'B'),
        new Student("Elif", 'B'), new Student("Farah", 'A'), new Student("Gopal", 'A'), new Student("Hana", 'C'),
        new Student("Ishaan", 'C'), new Student("Jonas", 'C'), new Student("Kavya", 'B'), new Student("Leo", 'C'),
        new Student("Meera", 'C'), new Student("Noor", 'B'), new Student("Omar", 'C'), new Student("Priya", 'C'),
        new Student("Quinn", 'A'), new Student("Ravi", 'B'), new Student("Sana", 'B'), new Student("Tomas", 'A'),
        new Student("Uma", 'B'), new Student("Vikram", 'C'), new Student("Wen", 'A'), new Student("Zoya", 'A'));

    static void report(String sortName, List<Student> students) {
        List<Student> stable = new ArrayList<>();  // what a stable sort by grade must give
        for (char grade : "ABC".toCharArray()) {
            for (Student s : REGISTER) if (s.grade() == grade) stable.add(s);
        }
        int moved = 0;
        StringJoiner gradeA = new StringJoiner(" ");
        for (int i = 0; i < students.size(); i++) {
            if (!students.get(i).equals(stable.get(i))) moved++;
            if (students.get(i).grade() == 'A') gradeA.add(students.get(i).name());
        }
        System.out.println(sortName + " (" + moved + " of " + students.size() + " moved)");
        System.out.println("  grade A: " + gradeA);
    }

    public static void main(String[] args) {
        Comparator<Student> byGrade = Comparator.comparingInt(Student::grade);

        List<Student> list = new ArrayList<>(REGISTER);
        list.sort(byGrade);
        report("List.sort", list);

        Student[] array = REGISTER.toArray(new Student[0]);
        Arrays.sort(array, byGrade);
        report("Arrays.sort on objects", Arrays.asList(array));
    }
}

C++ · compiled/stability.cpp

// A class register in alphabetical order, sorted by grade: does each grade keep the register's order?
#include <algorithm>
#include <print>
#include <string>
#include <string_view>
#include <vector>

struct Student {
    std::string name;
    char grade;
    bool operator==(const Student&) const = default;
};

// In alphabetical order of name, the order of the class register.
const std::vector<Student> kRegister = {
    {"Aarav", 'B'}, {"Bea", 'A'},  {"Chen", 'A'},  {"Divya", 'B'},  {"Elif", 'B'},   {"Farah", 'A'},
    {"Gopal", 'A'}, {"Hana", 'C'}, {"Ishaan", 'C'}, {"Jonas", 'C'}, {"Kavya", 'B'},  {"Leo", 'C'},
    {"Meera", 'C'}, {"Noor", 'B'}, {"Omar", 'C'},  {"Priya", 'C'},  {"Quinn", 'A'},  {"Ravi", 'B'},
    {"Sana", 'B'},  {"Tomas", 'A'}, {"Uma", 'B'},  {"Vikram", 'C'}, {"Wen", 'A'},    {"Zoya", 'A'},
};

void report(std::string_view sort_name, const std::vector<Student>& students) {
    std::vector<Student> stable;  // what a stable sort by grade must give
    for (char grade : {'A', 'B', 'C'}) {
        for (const Student& s : kRegister) {
            if (s.grade == grade) stable.push_back(s);
        }
    }
    int moved = 0;
    std::string grade_a;
    for (std::size_t i = 0; i < students.size(); ++i) {
        if (students[i] != stable[i]) ++moved;
        if (students[i].grade == 'A') grade_a += (grade_a.empty() ? "" : " ") + students[i].name;
    }
    std::println("{} ({} of {} moved)", sort_name, moved, students.size());
    std::println("  grade A: {}", grade_a);
}

int main() {
    auto by_grade = [](const Student& x, const Student& y) { return x.grade < y.grade; };

    auto a = kRegister;
    std::sort(a.begin(), a.end(), by_grade);
    report("std::sort", a);

    auto b = kRegister;
    std::stable_sort(b.begin(), b.end(), by_grade);
    report("std::stable_sort", b);
}

Go · compiled/stability.go

// A class register in alphabetical order, sorted by grade: does each grade keep the register's order?
package main

import (
	"cmp"
	"fmt"
	"slices"
	"sort"
	"strings"
)

type student struct {
	name  string
	grade byte
}

// In alphabetical order of name, the order of the class register.
var register = []student{
	{"Aarav", 'B'}, {"Bea", 'A'}, {"Chen", 'A'}, {"Divya", 'B'}, {"Elif", 'B'}, {"Farah", 'A'},
	{"Gopal", 'A'}, {"Hana", 'C'}, {"Ishaan", 'C'}, {"Jonas", 'C'}, {"Kavya", 'B'}, {"Leo", 'C'},
	{"Meera", 'C'}, {"Noor", 'B'}, {"Omar", 'C'}, {"Priya", 'C'}, {"Quinn", 'A'}, {"Ravi", 'B'},
	{"Sana", 'B'}, {"Tomas", 'A'}, {"Uma", 'B'}, {"Vikram", 'C'}, {"Wen", 'A'}, {"Zoya", 'A'},
}

func report(sortName string, students []student) {
	var stable []student // what a stable sort by grade must give
	for _, grade := range []byte("ABC") {
		for _, s := range register {
			if s.grade == grade {
				stable = append(stable, s)
			}
		}
	}
	moved := 0
	var gradeA []string
	for i, s := range students {
		if s != stable[i] {
			moved++
		}
		if s.grade == 'A' {
			gradeA = append(gradeA, s.name)
		}
	}
	fmt.Printf("%s (%d of %d moved)\n", sortName, moved, len(students))
	fmt.Printf("  grade A: %s\n", strings.Join(gradeA, " "))
}

func main() {
	a := slices.Clone(register)
	sort.Slice(a, func(i, j int) bool { return a[i].grade < a[j].grade })
	report("sort.Slice", a)

	b := slices.Clone(register)
	sort.SliceStable(b, func(i, j int) bool { return b[i].grade < b[j].grade })
	report("sort.SliceStable", b)

	byGrade := func(x, y student) int { return cmp.Compare(x.grade, y.grade) }

	c := slices.Clone(register)
	slices.SortFunc(c, byGrade)
	report("slices.SortFunc", c)

	d := slices.Clone(register)
	slices.SortStableFunc(d, byGrade)
	report("slices.SortStableFunc", d)
}

Build and run all three · compiled/stability_session.py

from session import run, versions

# The same register sorted by grade with each language's sorts: Java, then C++, then Go.
versions("java -version", "clang++ --version", "go version")
run("""
java Stability.java
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined stability.cpp -o stability && ./stability
go run stability.go
""")

Output

# openjdk version "25.0.4.1" LTS
# Apple clang version 21.0.0 (clang-2100.3.34.2)
# go version go1.27.1 darwin/arm64
$ java Stability.java
List.sort (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
Arrays.sort on objects (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
$ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined stability.cpp -o stability && ./stability
std::sort (23 of 24 moved)
  grade A: Zoya Bea Chen Wen Tomas Farah Gopal Quinn
std::stable_sort (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
$ go run stability.go
sort.Slice (23 of 24 moved)
  grade A: Quinn Bea Chen Zoya Wen Farah Gopal Tomas
sort.SliceStable (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya
slices.SortFunc (23 of 24 moved)
  grade A: Quinn Bea Chen Zoya Wen Farah Gopal Tomas
slices.SortStableFunc (0 of 24 moved)
  grade A: Bea Chen Farah Gopal Quinn Tomas Wen Zoya

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stability_session.py

Every stable sort printed grade A as Bea, Chen, Farah, Gopal, Quinn, Tomas, Wen and Zoya, the register’s order. std::sort moved 23 of the 24 students. So did Go’s sort.Slice and slices.SortFunc, which printed the same scrambled order as each other. “Not guaranteed” cuts both ways: on another input an unstable sort can happen to keep the order, so one passing test proves nothing. When the order of ties matters, use a stable sort, or make the comparison decide every tie. Comparing on several keys at once is a topic of its own, later in this module.

When sorting first is the wrong move

Sorting is a strong default, not a rule. Four signs point elsewhere:

  • The question is about the original sequence: a stretch of consecutive days, a window of the last k events, the first day on which something happens. Sorting destroys the sequence, so these need scans of the input as it is, such as prefix sums and sliding windows.
  • One scan with a hash map does the job and keeps the positions: two values with a given sum whose positions you must return, or how often each value occurs. Both take O(n) on average that way.
  • The values come from a small range, such as exam marks from 0 to 100. Counting them takes O(n + range), which a later lesson of this module covers.
  • Only a few values are needed, such as the k smallest or the median. A heap or a selection algorithm does less work than a full sort.

Exercise

The depot now wants to know how crowded its busiest moment was, which turns the worked problem into a counting question. Repeated times are the trap: work out [5, 5, 5] by hand before you start.

Exercise · Easy · Python

Count the pairs at the closest gap

The depot wants to know how crowded its busiest moment was: not only the smallest gap between two departures, but how many pairs of departures are exactly that close. Write closest_gap(times) in gaps.py. It returns a tuple (gap, pairs): the smallest difference between two of the times, and the number of pairs of positions i < j whose times differ by exactly that much.

  • 2 <= len(times) <= 100,000, and each time is a second of the day, 0 <= times[i] <= 86,399.
  • Two vans can leave in the same second, and then the smallest gap is 0.
  • Leave the caller's list as it was.
closest_gap([40, 7, 25, 31])  # (6, 1): only 25 and 31 are 6 apart
closest_gap([10, 20, 30])     # (10, 2): 10 and 20, and 20 and 30
closest_gap([5, 5, 5])        # (0, 3): each of the three pairs is 0 apart

The sample tests check these cases and a few more, run 300 random lists against a brute force of their own (they print the first list where your answer differs), and give closest_gap 20,000 times twice, stopping it after 200,000 executed lines of Python.

Starter code · gaps.py

def closest_gap(times):
    """Return (smallest gap, number of pairs exactly that far apart) for a list of at least two times."""
    # Replace this line with your code.
    return 0, 0
The sample tests · test_gaps.py
import random
import sys
from collections import Counter

from gaps import closest_gap

LINE_LIMIT = 200_000


def every_pair(times):
    """A brute force the tests trust: the gap of every pair, then the smallest and how often it occurs."""
    gaps = [abs(times[i] - times[j]) for i in range(len(times)) for j in range(i + 1, len(times))]
    smallest = min(gaps)
    return smallest, gaps.count(smallest)


class TooSlow(Exception):
    pass


def within_line_limit(times):
    """Call closest_gap, but stop it after LINE_LIMIT executed lines: a time limit that is the same everywhere."""
    executed = 0

    def count(frame, event, arg):
        nonlocal executed
        if event == "line":
            executed += 1
            if executed > LINE_LIMIT:
                raise TooSlow
        return count

    sys.settrace(count)
    try:
        return closest_gap(times)
    except TooSlow:
        raise AssertionError(f"too slow: more than {LINE_LIMIT:,} lines ran; sort first, then one pass") from None
    finally:
        sys.settrace(None)


def test_prompt_examples():
    """the three examples of the prompt"""
    assert closest_gap([40, 7, 25, 31]) == (6, 1)
    assert closest_gap([10, 20, 30]) == (10, 2)
    assert closest_gap([5, 5, 5]) == (0, 3)


def test_two_times():
    """two times make one pair, in either order"""
    assert closest_gap([86_399, 0]) == (86_399, 1)
    assert closest_gap([600, 600]) == (0, 1)


def test_runs_of_equal_times():
    """a run of k equal times holds k(k - 1)/2 pairs at gap 0"""
    assert closest_gap([7, 1, 7, 4, 7, 4]) == (0, 4)
    assert closest_gap([9, 9, 9, 9, 9]) == (0, 10)


def test_input_unchanged():
    """the caller's list keeps its order"""
    times = [30, 10, 20, 10]
    closest_gap(times)
    assert times == [30, 10, 20, 10]


def test_against_brute_force():
    """300 random lists: the same answer as a trusted brute force"""
    rng = random.Random(17)
    for _ in range(300):
        times = [rng.randrange(25) for _ in range(rng.randint(2, 9))]
        assert closest_gap(times) == every_pair(times), f"closest_gap({times})"


def test_fast_enough():
    """20,000 times, twice, each within the line limit"""
    rng = random.Random(4)
    spaced = list(range(0, 60_000, 3))  # 20,000 times, every neighbour 3 apart
    rng.shuffle(spaced)
    assert within_line_limit(spaced) == (3, 19_999)
    crowded = [rng.randrange(100) for _ in range(20_000)]  # only 100 different seconds
    expected = (0, sum(k * (k - 1) // 2 for k in Counter(crowded).values()))
    assert within_line_limit(crowded) == expected
A hint

Sort a copy first, as in the lesson. Then work [5, 5, 5] by hand: how many pairs are 0 apart, and how many of them are neighbours in the sorted list? The answer is different when the smallest gap is above 0.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Paste closest.py and try logs of your own, with repeated times and with only two departures. JavaScript & TypeScript Online Compiler Run closest.mjs, then remove the comparator from sort() and see which answers change. Text Sorter Paste numbers one per line and compare numeric order with character-code order, the order JavaScript's sort() uses without a comparator.

Check yourself

6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 6 After sorting the times t₀ ≤ t₁ ≤ … ≤ tₙ₋₁, why is it enough to compare each time with the next one to find the smallest gap?

    Choose one answer.

    Show the answer to question 1

    Answer: Because the gap of any pair is the sum of the neighbour gaps between them, so some neighbour gap is at least as small

    For i < j, tⱼ − tᵢ adds up every neighbour gap from position i to position j, and none of them is negative, so the first of those gaps, tᵢ₊₁ − tᵢ, is never larger than tⱼ − tᵢ. Every pair has a neighbour pair at least as close, and the smallest of the n − 1 neighbour gaps is the answer.

  2. Question 2 of 6 Which of these problems become one scan, or two pointers, once the input is sorted?

    Choose every answer that is right.

    Show the answer to question 2

    Answer:

    • Do two values add up to a given target?
    • The two closest values
    • Is any value in the list repeated?

    Sorting puts equal values next to each other, puts close values next to each other, and lets two pointers walk in from both ends for a target sum. A stretch of consecutive days is about the original order, which sorting destroys, so it needs a scan of the days as they are.

  3. Question 3 of 6 positions.py sorts [300, 120, 300, 45] three ways. What does it print?

    What does this program print? Choose one answer.

    """Three ways to sort departure times and say where each one was in the log; the last one is a slip."""
    
    times = [300, 120, 300, 45]  # seconds; positions 0 to 3; positions 0 and 2 share a time
    
    # 1. Sort (time, position) pairs. Tuples compare item by item, so equal times come out in position order.
    pairs = sorted((t, i) for i, t in enumerate(times))
    print("pairs:     ", pairs)
    
    # 2. Sort the positions, using each position's time as the key. The sort is stable: equal times keep log order.
    order = sorted(range(len(times)), key=lambda i: times[i])
    print("positions: ", order)
    
    # The slip: sort the times, then look each one up again. index() returns the first match, every time.
    looked_up = [times.index(t) for t in sorted(times)]
    print("index():   ", looked_up)
    Show the answer to question 3

    Answer: it prints

    pairs:      [(45, 3), (120, 1), (300, 0), (300, 2)]
    positions:  [3, 1, 0, 2]
    index():    [3, 1, 0, 0]

    Pairs compare by time and then by position, so the two 300s come out as (300, 0) and then (300, 2). Sorting the positions by their time is stable, so position 0 stays ahead of position 2. index() returns the first match for each 300, position 0 both times, and position 2 is lost.

  4. Question 4 of 6 Which of these sorts does its language's documentation or standard promise to be stable?

    Choose every answer that is right.

    Show the answer to question 4

    Answer:

    • Go's slices.SortStableFunc
    • JavaScript's Array.prototype.sort
    • Python's sorted()
    • Java's List.sort

    Python's sort, JavaScript's Array.prototype.sort, Java's List.sort and Go's slices.SortStableFunc keep equal items in their original order. C++ std::sort and Go's sort.Slice make no such promise, and in this lesson's run both moved 23 of the 24 students of the register.

  5. Question 5 of 6 How many pairs of positions are 0 apart in the times [4, 4, 4, 4]?

    Type a number.

    Show the answer to question 5

    Answer: 6

    Each of the 4 times pairs with each of the other 3, and every pair is counted twice that way: 4 × 3 / 2 = 6. A scan of sorted neighbours sees only 3 pairs, which is the trap in this lesson's exercise.

  6. Question 6 of 6 In JavaScript, what does [10, 9, 1].sort() return?

    Choose one answer.

    Show the answer to question 6

    Answer: [1, 10, 9]

    Without a comparator, sort() converts the elements to strings and compares the strings, and "10" comes before "9" because its first character, "1", does. Pass (a, b) => a - b to sort numbers by value, as the lesson's JavaScript version does.

Interview questions

What does it mean for a sort to be stable, and when does it matter?

A stable sort keeps items whose keys are equal in the order they had before the sort. It matters whenever that order carries information: sort a register that is already alphabetical by grade, and a stable sort leaves each grade alphabetical, which is how two sorts in a row, the second key first, give a two-key order. It also matters when ties must come out in a fixed, predictable order. Python’s sort, JavaScript’s Array.prototype.sort, Java’s List.sort and its sort for object arrays, C++ std::stable_sort and Go’s sort.SliceStable and slices.SortStableFunc are stable. C++ std::sort and Go’s sort.Slice and slices.SortFunc are not. For plain integers the question does not arise, because equal integers cannot be told apart.

How do you sort an array but still report each value’s original index?

Make the index travel with the value. Either sort the indices, using each index’s value as the key, such as sorted(range(n), key=lambda i: a[i]) in Python or an Integer[] of indices with Comparator.comparingInt in Java, or sort (value, index) pairs, which also breaks ties by index. Each item of the sorted order then still says where it came from. Do not sort the values and look each one up again with index() or a search: every copy of a repeated value maps to its first position, and each lookup is another scan, O(n²) in all.

Key takeaways

  • Sorting costs O(n log n) once and buys order: equal and close values become neighbours, two pointers and binary search can discard candidates, and greedy rules get an order to work in.
  • After sorting, the two closest values are neighbours, because the gap of any pair is a sum of neighbour gaps; the n − 1 neighbour gaps decide the answer.
  • Keep positions by sorting the positions by their values or by sorting (value, position) pairs. Never sort the values and look them up again.
  • Stable: Python’s and JavaScript’s sorts, Java’s List.sort and object sorts, std::stable_sort, sort.SliceStable and slices.SortStableFunc. Not guaranteed: std::sort, sort.Slice and slices.SortFunc.
  • Check a sorted plan against the brute force on many small random inputs, with a check that accepts every correct answer.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.