You’ll eventually hit a moment where a fancy library sort isn’t the right tool: you’re debugging a ranking bug, you’re teaching a junior teammate how ordering actually works, you’re working in a constrained environment, or you simply need a sorting routine you can reason about line-by-line.\n\nSelection sort is one of the cleanest algorithms for that job. It’s not the fastest for big inputs, but it’s predictable, easy to trace, and has a surprisingly practical property: it performs a small, bounded number of writes (swaps), which can matter when writes are expensive.\n\nI’m going to walk you through a modern Java implementation of selection sort, explain the invariants I keep in my head while reviewing it, show you how to trace it on a real dataset, and point out the mistakes I still see in production code reviews. By the end, you’ll be able to implement it from memory, test it with confidence, and know exactly when you should choose something else.\n\n## Selection sort as a simple mental model\nI explain selection sort like this: imagine a row of books on a desk. You want them ordered by title.\n\n1) You scan the whole row and find the book that should be first.\n2) You move that book to the first position.\n3) You repeat the process for the remaining books, starting from the next position.\n\nThat’s selection sort. Each pass “selects” the minimum (for ascending order) from the unsorted region and places it at the boundary.\n\nTwo things are worth noticing:\n\n- The boundary is explicit. After the i-th pass, positions 0..i are guaranteed sorted.\n- The algorithm is comparison-heavy, write-light. You do many comparisons, but you only swap at most once per outer loop iteration.\n\nWhen I’m working with junior developers, this boundary idea is the key. If they can state what part is already correct after each pass, they can debug the code without guessing.\n\n## The algorithm, step-by-step (with invariants I rely on)\nSelection sort is usually expressed with two loops:\n\n- Outer loop i marks the start of the unsorted part.\n- Inner loop j scans the remaining elements to find the smallest value.\n\nThe invariant I use when reviewing or debugging:\n\n- Before iteration i, the prefix a[0..i-1] is sorted and contains the i smallest elements from the original array.\n- During iteration i, minIndex always points to the smallest value found so far in a[i..j].\n- After swapping at the end of iteration i, the value at a[i] is the smallest of the unsorted region.\n\nThat sounds academic, but it’s the quickest way I know to catch subtle bugs:\n\n- If your inner loop starts at j = i instead of i + 1, you’ll still “work,” but you’ll waste comparisons.\n- If your outer loop runs to i < n instead of i < n - 1, you risk extra work or even out-of-bounds mistakes if the inner loop is wrong.\n- If you swap inside the inner loop (instead of after the minimum search), you’ve changed the algorithm into something else and often made it slower.\n\nIn plain terms: for each position i, find the best candidate for that position, then place it there.\n\n## A runnable Java implementation I’m comfortable shipping\nHere’s a complete, runnable program. I keep it intentionally straightforward, with small defensive choices that help in real projects:\n\n- It handles null input with a clear exception (instead of a confusing NullPointerException later).\n- It avoids unnecessary swaps when the minimum is already in place.\n- It separates sorting from printing.\n\n import java.util.Arrays;\n\n public class SelectionSortDemo {\n\n public static void selectionSortAscending(int[] values) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int minIndex = i;\n\n // Find index of smallest element in values[i..n-1]\n for (int j = i + 1; j < n; j++) {\n if (values[j] < values[minIndex]) {\n minIndex = j;\n }\n }\n\n // Swap only if we found a smaller element elsewhere\n if (minIndex != i) {\n int tmp = values[minIndex];\n values[minIndex] = values[i];\n values[i] = tmp;\n }\n }\n }\n\n public static void main(String[] args) {\n int[] temperaturesCelsius = { 64, 25, 12, 22, 11 };\n\n selectionSortAscending(temperaturesCelsius);\n\n System.out.println(Arrays.toString(temperaturesCelsius));\n }\n }\n\nIf you run it, you’ll see the sorted array printed.\n\nA quick note on naming: I avoid a[] and n in real code unless it’s a whiteboard session. Names like values, temperaturesCelsius, or scores reduce mental load during maintenance.\n\n## Tracing one full run (so you can debug without a debugger)\nWhen selection sort “looks wrong,” I don’t start by staring at code. I start by tracing the state transitions.\n\nLet’s trace this array:\n\n- Start: [64, 25, 12, 22, 11]\n\nPass i = 0 (place the smallest into position 0):\n- Scan indexes 0..4. Minimum is 11 at index 4.\n- Swap index 0 and 4.\n- Array becomes: [11, 25, 12, 22, 64]\n- Invariant check: prefix [11] is sorted.\n\nPass i = 1 (place the smallest into position 1):\n- Scan indexes 1..4 in [25, 12, 22, 64]. Minimum is 12 at index 2.\n- Swap index 1 and 2.\n- Array becomes: [11, 12, 25, 22, 64]\n- Invariant check: prefix [11, 12] is sorted.\n\nPass i = 2:\n- Scan [25, 22, 64]. Minimum is 22 at index 3.\n- Swap index 2 and 3.\n- Array becomes: [11, 12, 22, 25, 64]\n\nPass i = 3:\n- Scan [25, 64]. Minimum already at index 3.\n- No swap.\n- Array remains: [11, 12, 22, 25, 64]\n\nThat’s the whole run.\n\nWhen I debug, I specifically watch these values per pass:\n\n- i (boundary)\n- minIndex after the inner scan\n- whether a swap happened\n\nIf any of those don’t match your trace, you’ve found the bug. This method works even when you can’t attach a debugger (container logs, embedded devices, restricted environments).\n\n## Complexity and real performance guidance (when I recommend it)\nSelection sort has a simple cost profile:\n\n- Comparisons: about n(n-1)/2, which is O(n^2)\n- Swaps: at most n-1, which is O(n)\n- Extra memory: O(1) for primitive arrays\n\nHere’s the guidance I give in code reviews:\n\nYou should pick selection sort when:\n\n- The input is small (often tens of elements, sometimes a few hundred).\n- You care about limiting writes (for example: arrays backed by storage where writes are slow, or objects where swaps imply expensive bookkeeping).\n- You want a sorting routine that is easy to audit, explain, and test.\n\nYou should not pick selection sort when:\n\n- You sort anything large or on a hot path (request handling, pricing loops, repeated UI refreshes).\n- Your data is already nearly sorted and you want an algorithm that benefits from that.\n- Stability matters (more on that soon).\n\nIn practice, on modern hardware, selection sort for very small arrays can run in typically a few microseconds to a couple of milliseconds depending on n, the JVM warmup state, and whether the data fits in cache. The moment you push into thousands of elements, O(n^2) starts to dominate and you should move to a library sort.\n\nIf you’re sorting in business code, my default recommendation is still: use Arrays.sort(...) or List.sort(...) unless you have a strong reason not to. Selection sort is a tool for specific constraints and for learning.\n\n## Common mistakes I still see (and how I prevent them)\nSelection sort is simple, which makes developers overconfident. These are the mistakes I most often fix:\n\n1) Swapping inside the inner loop\n- Problem: you do many swaps, turning the algorithm into something closer to exchange sort.\n- Fix: only swap once per outer iteration, after the minimum is known.\n\n2) Forgetting minIndex reset\n- Problem: minIndex should start at i for each pass. If it carries over, you’ll sometimes get partially sorted results that look “almost right.”\n- Fix: declare and set minIndex = i inside the outer loop.\n\n3) Wrong loop bounds\n- Problem: outer loop should stop at n - 1 because the last element is automatically correct once the prefix is sorted.\n- Fix: for (int i = 0; i < n - 1; i++).\n\n4) Not handling edge cases\n- Problem: null arrays, empty arrays, single-element arrays.\n- Fix: decide your policy. I throw IllegalArgumentException on null and treat empty arrays as already sorted.\n\n5) Confusing stability\n- Problem: selection sort with swaps is not stable. Equal items can change relative order.\n- Fix: if stability matters, either use a stable algorithm or implement the stable variant (next section).\n\n6) Comparing boxed integers accidentally\n- Problem: sorting Integer[] and comparing with == or calling compareTo incorrectly.\n- Fix: for objects, use Comparator and write tests with duplicates.\n\nIn 2026, I also see one newer failure mode: AI-generated code that “looks right” but subtly changes semantics (like swapping each time a smaller value is found). My prevention strategy is boring and effective: I require a trace for a small input with duplicates in code review, and I require tests.\n\n## Variants you’ll actually need: stability, descending order, and generics\n### Descending order (simple change)\nTo sort descending, flip the comparison so you select the maximum instead of the minimum.\n\n public static void selectionSortDescending(int[] values) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int maxIndex = i;\n for (int j = i + 1; j values[maxIndex]) {\n maxIndex = j;\n }\n }\n\n if (maxIndex != i) {\n int tmp = values[maxIndex];\n values[maxIndex] = values[i];\n values[i] = tmp;\n }\n }\n }\n\n### Stable selection sort (when equal elements must keep their order)\nStandard selection sort swaps values[i] with values[minIndex]. That swap can move an equal element ahead of another equal element, breaking stability.\n\nIf you need stability, a common approach is:\n\n- Find the minimum element.\n- Instead of swapping, store it.\n- Shift the intervening elements one step to the right.\n- Place the minimum at position i.\n\nThat keeps the relative order of equals.\n\n public static void stableSelectionSortAscending(int[] values) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int minIndex = i;\n for (int j = i + 1; j < n; j++) {\n if (values[j] i) {\n values[minIndex] = values[minIndex – 1];\n minIndex–;\n }\n\n values[i] = minValue;\n }\n }\n\nTrade-off: it does more writes due to shifting, but it preserves order for equal values.\n\n### Sorting objects with Comparator (how I write it in business code)\nReal applications sort objects, not just integers. Here’s a generic version that sorts an array of objects using a Comparator.\n\n import java.util.Comparator;\n\n public static void selectionSort(T[] values, Comparator comparator) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n if (comparator == null) {\n throw new IllegalArgumentException("comparator must not be null");\n }\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int minIndex = i;\n for (int j = i + 1; j < n; j++) {\n if (comparator.compare(values[j], values[minIndex]) < 0) {\n minIndex = j;\n }\n }\n\n if (minIndex != i) {\n T tmp = values[minIndex];\n values[minIndex] = values[i];\n values[i] = tmp;\n }\n }\n }\n\nIf you’re on Java 21+ (common in 2026 codebases), this integrates cleanly with modern domain models. You can sort Invoice, SensorReading, BuildArtifact, whatever your project uses.\n\n### Traditional vs modern workflow (how I recommend you build confidence)\nSelection sort is often taught with pencil-and-paper traces. I still do that for a 5-element array, but for real code I prefer a workflow that produces proof.\n\n
Traditional method
\n
—
\n
Manual trace
\n
Print statements
\n
“Looks good” review
\n
Make up examples
\n\nIf you use an AI coding assistant, treat it like a fast typist, not an authority. You should still demand tests and a trace.\n\n## Testing selection sort the way I actually trust\nIf selection sort is in learning code, you can stop at a couple of examples. If it’s in production code, I want tests that cover:\n\n- Empty array\n- Single element\n- Already sorted\n- Reverse sorted\n- Duplicates\n- Negative values\n- Large-ish random arrays (not for performance, for correctness)\n\nHere’s a JUnit 5 test class for the selectionSortAscending method above. I’ll finish the duplicates test, add negatives, and add one randomized test that compares against Arrays.sort as an oracle.\n\n import static org.junit.jupiter.api.Assertions.assertArrayEquals;\n import static org.junit.jupiter.api.Assertions.assertThrows;\n\n import java.util.Arrays;\n import java.util.Random;\n\n import org.junit.jupiter.api.Test;\n\n public class SelectionSortDemoTest {\n\n @Test\n void throwsOnNull() {\n assertThrows(IllegalArgumentException.class, () -> SelectionSortDemo.selectionSortAscending(null));\n }\n\n @Test\n void sortsEmptyArray() {\n int[] values = {};\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] {}, values);\n }\n\n @Test\n void sortsSingleValue() {\n int[] values = { 42 };\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] { 42 }, values);\n }\n\n @Test\n void sortsAlreadySorted() {\n int[] values = { 1, 2, 3, 4 };\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] { 1, 2, 3, 4 }, values);\n }\n\n @Test\n void sortsReverseSorted() {\n int[] values = { 9, 7, 5, 3, 1 };\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] { 1, 3, 5, 7, 9 }, values);\n }\n\n @Test\n void sortsWithDuplicates() {\n int[] values = { 5, 1, 3, 1, 5, 2 };\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] { 1, 1, 2, 3, 5, 5 }, values);\n }\n\n @Test\n void sortsWithNegatives() {\n int[] values = { 0, -10, 5, -3, 2 };\n SelectionSortDemo.selectionSortAscending(values);\n assertArrayEquals(new int[] { -10, -3, 0, 2, 5 }, values);\n }\n\n @Test\n void matchesArraysSortOnRandomInputs() {\n Random rnd = new Random(12345);\n\n for (int t = 0; t < 200; t++) {\n int n = 1 + rnd.nextInt(50);\n int[] values = new int[n];\n for (int i = 0; i < n; i++) {\n // Keep values in a small range so duplicates happen often.\n values[i] = rnd.nextInt(21) – 10;\n }\n\n int[] expected = Arrays.copyOf(values, values.length);\n Arrays.sort(expected);\n\n SelectionSortDemo.selectionSortAscending(values);\n\n assertArrayEquals(expected, values);\n }\n }\n }\n\nThe randomized test is my favorite “cheap confidence” tool. It’s not a performance benchmark. It’s a correctness net that catches off-by-one errors, incorrect comparisons, and bugs that only show up when duplicates are present.\n\nOne note: I deliberately used a fixed random seed (12345). That makes failures reproducible. If the test fails in CI, I want to see the exact same failing input locally.\n\n## Stability in practice: why you should care (and how to prove it)\nStability can feel theoretical until it breaks something real. Here’s the scenario I use to make it concrete.\n\nImagine you’re sorting purchase orders by totalCents. Two orders have the same total, and your UI is supposed to preserve the original order (maybe the order they were created, or the order a user manually arranged earlier). If your sort is unstable, those equal-total orders can flip positions. That can cause weird UX bugs and angry support tickets because the user sees the “same” page reshuffle randomly.\n\nSelection sort with swapping is not stable. The simplest way to prove it is to sort not just numbers, but “labeled numbers”: a value plus an ID that reveals the original order.\n\nLet’s say we have items (value, id):\n\n- (2, A), (2, B), (1, C)\n\nOn the first pass (i = 0), we find the minimum (1, C) and swap it with position 0. The array becomes:\n\n- (1, C), (2, B), (2, A)\n\nThe two items with value 2 flipped. That’s instability.\n\nIf stability matters, I do one of these:\n\n- Use a stable algorithm (often the library sort already is stable for objects, depending on what you call).\n- Use the stable selection sort variant (shift instead of swap) if I specifically need “selection sort behavior” and can afford the extra writes.\n- Encode a tie-breaker into the comparator (for example: sort by value, then by original index). This makes the output deterministic even if the algorithm is unstable, because “equal” values are no longer equal under the comparator.\n\nThat last option is underrated. Sometimes you don’t truly need stability; you need deterministic ordering. If you can define a tie-breaker that matches your product logic, it can be a clean fix.\n\n## Instrumentation: counting comparisons and writes (how I make costs visible)\nPeople remember O(n^2) but still underestimate how quickly it grows, and they also forget that “time” isn’t the only cost. In some systems, writes are expensive: maybe you’re writing to a memory-mapped file, maybe each swap triggers observers, maybe you’re sorting a structure that recalculates indexes on write.\n\nWhen I’m trying to decide whether selection sort is appropriate, I’ll often instrument it and measure two things:\n\n- comparisons: how often do we compare elements?\n- writes: how often do we assign array slots? (a swap is typically two or three assignments depending on representation)\n\nHere’s a simple instrumented variant for int[]. I keep it as a learning tool, not something I’d ship into production unchanged.\n\n public final class SelectionSortStats {\n public long comparisons;\n public long swaps;\n\n @Override\n public String toString() {\n return "SelectionSortStats{comparisons=" + comparisons + ", swaps=" + swaps + "}";\n }\n }\n\n public static SelectionSortStats selectionSortAscendingWithStats(int[] values) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n\n SelectionSortStats stats = new SelectionSortStats();\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int minIndex = i;\n\n for (int j = i + 1; j < n; j++) {\n stats.comparisons++;\n if (values[j] < values[minIndex]) {\n minIndex = j;\n }\n }\n\n if (minIndex != i) {\n int tmp = values[minIndex];\n values[minIndex] = values[i];\n values[i] = tmp;\n stats.swaps++;\n }\n }\n\n return stats;\n }\n\nThis makes two practical points obvious:\n\n- comparisons are basically fixed for a given n (it always scans the rest of the array each pass).\n- swaps depend on the input. If the array is already sorted, swaps are zero. If it’s random, you’ll see swaps often, but still capped at n - 1.\n\nIf I’m comparing selection sort to something like insertion sort, instrumentation changes the conversation. Insertion sort can do fewer comparisons on nearly sorted inputs, but it can also do many writes because it shifts elements. Selection sort does many comparisons regardless, but keeps writes low. Which one is better depends on what you’re optimizing for.\n\n## Edge cases and contracts: what “sorted” should mean in your codebase\nSorting functions are deceptively easy to call and hard to specify. I like to be explicit about the contract. When I’m reviewing a sorting utility, I ask these questions:\n\n- Does it sort in-place or return a new array?\n- How does it handle null input? Throw? Return null? Treat as empty?\n- For objects: how does it handle null elements inside the array?\n- Is the sort stable? If not, is the caller expecting stability?\n- What’s the comparator contract? (Consistency with equals? Total ordering? Handling of ties?)\n\nFor int[], life is easy: no null elements, fixed comparison. For T[], the space of bugs is larger.\n\nHere’s how I typically harden the generic version in real code:\n\n- I validate values and comparator are non-null.\n- I decide (and document) whether null elements are allowed. If they are, the comparator must define how to compare nulls (or I wrap it with Comparator.nullsFirst(...) / nullsLast(...)).\n- I avoid “clever” comparator logic that’s not transitive. Non-transitive comparators can produce nonsense results, and some sorting algorithms can even throw exceptions when the comparator violates its contract.\n\nIn my experience, the most common production bug is not the selection sort loop itself. It’s a comparator that does something like “compare by rounded price” or “compare by length but break ties randomly.” That kind of comparator is a landmine.\n\n## Practical scenario: sorting with expensive writes (why selection sort can still matter)\nSelection sort’s write behavior is its most practical feature. Here’s a realistic-ish example: you have an array of objects where swapping triggers bookkeeping.\n\nMaybe you have a UI list model where setting an element notifies observers. Or you have an array that’s mirrored into a native buffer. Or you have a data structure where “moving” an element updates an index map. In those situations, the difference between “swap at most once per outer iteration” and “swap many times while bubbling” is not theoretical. It’s visible.\n\nWhen I’m faced with this, I ask:\n\n- Can I sort indices instead of objects? (Sort an int[] of positions and keep the original data untouched.)\n- Can I do a stable sort with shifting, and is that acceptable?\n- If I must reorder the objects in-place, can I keep writes bounded?\n\nSelection sort is sometimes a reasonable compromise: lots of reads/comparisons (often cheap) and few writes (sometimes expensive).\n\nThat said, I still prefer library sort when possible. If writes are expensive, there’s often an even better architecture than any O(n^2) sort: avoid sorting at all, cache the sorted view, or maintain order incrementally. Selection sort is a tactical tool, not a strategy.\n\n## Alternative approaches: what I reach for before selection sort\nIf someone proposes selection sort in business code, my first reaction is: why not use the standard library? My second reaction is: what constraint are we actually optimizing for?\n\nHere’s the quick comparison I keep in my head:\n\n- Arrays.sort(int[]): fast, battle-tested, typically the right answer for primitives.\n- Arrays.sort(T[], Comparator): fast for objects, widely used, usually stable behavior expectations are clearer than rolling your own.\n- Insertion sort: often excellent for very small arrays or nearly sorted arrays; simple; tends to do more writes due to shifting.\n- Bubble sort: mostly educational; rarely the right choice.\n- Merge sort: predictable O(n log n) but uses extra memory; stable; great in many contexts.\n- Quicksort: fast on average, but worst-case O(n^2) unless guarded; not stable in typical in-place forms.\n- Hybrid sorts (like the ones used by the JDK): optimized for real-world patterns; this is why I don’t hand-roll sorting unless I have to.\n\nSo when do I actually choose selection sort? Usually one of these:\n\n- The array is tiny, and I want a dead-simple implementation in a place where importing heavier utilities is awkward (some embedded or restricted environments).\n- I’m teaching or documenting, and clarity is the primary goal.\n- Writes are unusually expensive, and I’ve measured that limiting them matters more than reducing comparisons.\n\nIf none of those are true, I use the library.\n\n## Debugging patterns: what to log when you can’t attach a debugger\nWhen I’m debugging a sorting bug in a container or a remote environment, I often can’t step through code. I’ll add temporary logging, but I try to log the smallest set of signals that reconstructs the algorithm.\n\nFor selection sort, the minimal set is:\n\n- the outer index i\n- the chosen minIndex\n- the values at values[i] and values[minIndex] before the swap\n- whether a swap happened\n\nHere’s a tiny debug helper that prints a trace. I keep it separate so I can delete it cleanly when done.\n\n private static void selectionSortAscendingWithTrace(int[] values) {\n if (values == null) {\n throw new IllegalArgumentException("values must not be null");\n }\n\n int n = values.length;\n for (int i = 0; i < n – 1; i++) {\n int minIndex = i;\n\n for (int j = i + 1; j < n; j++) {\n if (values[j] < values[minIndex]) {\n minIndex = j;\n }\n }\n\n System.out.println("i=" + i + ", minIndex=" + minIndex\n + ", beforeSwap=" + java.util.Arrays.toString(values));\n\n if (minIndex != i) {\n int tmp = values[minIndex];\n values[minIndex] = values[i];\n values[i] = tmp;\n }\n\n System.out.println("i=" + i + ", afterSwap=" + java.util.Arrays.toString(values));\n }\n }\n\nThis is intentionally noisy and slow. It’s for a five-to-twenty element array where you’re trying to answer: “Why is the third element wrong?” not “How fast is this?”\n\nThe reason I log both before and after is simple: if your swap is wrong (for example, you accidentally overwrite tmp or you swap the wrong indices), the inner loop can be perfect and you’ll still get bad output. Seeing the array immediately before and after the swap isolates the failure.\n\n## Sorting objects: Comparator pitfalls I watch for in review\nWhen selection sort is used with objects, almost every bug I’ve seen falls into one of these categories:\n\n1) Comparator violates transitivity\n- Example symptom: sorting sometimes produces inconsistent results, or different runs yield different orders.\n- Root cause: comparator says A < B, B < C, but C < A for some inputs.\n- Fix: simplify the comparator; avoid floating-point corner cases; use Comparator.comparing(...) building blocks; add tests.\n\n2) Comparator is inconsistent with equals (sometimes okay, often confusing)\n- If compare(a, b) == 0 but !a.equals(b), you’ve defined “equivalence classes” that aren’t object equality. That can be fine, but you must expect that order among those equivalents may be unstable unless the sort is stable or you have a tie-breaker.\n- Fix: add a tie-breaker to make ordering deterministic, like comparing IDs when the primary key matches.\n\n3) Null handling is implicit\n- If the array can contain nulls, do you want nulls first or last? Or do you want to forbid them?\n- Fix: be explicit. Either validate and throw, or wrap the comparator with Comparator.nullsFirst(...) / nullsLast(...).\n\n4) Accidental boxing and performance cliffs\n- Sorting Integer[] creates more overhead than sorting int[]. If you’re in a hot path, that can matter.\n- Fix: prefer primitives when possible, or keep the array small enough that it truly doesn’t matter.\n\nThe key thing I emphasize: selection sort is easy; comparators are where the dragons live.\n\n## Performance considerations (ranges, not fantasy numbers)\nIt’s tempting to quote exact timings, but exact timings are usually misleading because they depend on JVM warmup, CPU scaling, allocations, and the shape of the data. What I can say reliably is how the algorithm behaves.\n\n- For arrays in the tens of elements: selection sort is often “fast enough,” and the difference between it and a library sort may be unnoticeable compared to surrounding work.\n- For arrays in the hundreds: selection sort can still be acceptable in non-hot paths, especially if you value simplicity or bounded writes, but you should think twice.\n- For arrays in the thousands and beyond: selection sort’s O(n^2) comparisons dominate quickly. Even if each comparison is cheap, the count explodes.\n\nIf you want to measure honestly, I recommend using a proper harness like JMH rather than writing a loop around System.nanoTime(). JMH handles JVM warmup and avoids common benchmarking traps.\n\nEven then, I use benchmarks for decisions like “should we switch from selection sort to the library sort?” not “what is the precise nanosecond cost?” The point is direction and magnitude.\n\n## A small but useful optimization: early exit (and why it usually doesn’t help)\nOne question that comes up is: “Can we stop early if the array is already sorted?”\n\nFor selection sort, the answer is: not easily in a way that saves much, because you still have to scan the unsorted region to prove there isn’t a smaller element. The algorithm doesn’t naturally detect sortedness without doing the work.\n\nYou can add a check like “did we find any smaller element this pass?” and if not, maybe the rest is sorted. But the rest being sorted doesn’t follow from one pass having minIndex == i. It just means values[i] is the smallest element in values[i..n-1], not that the tail is sorted.\n\nThis is a good moment to choose a different algorithm if you expect nearly sorted data. Insertion sort (or just Arrays.sort) typically handles that better.\n\n## Another variant you’ll see: selection sort for partial sorting (top-k)\nSometimes you don’t need a fully sorted array. You need the smallest k elements, or you need the first page of results.\n\nSelection sort adapts nicely here because after k outer iterations, you’ve correctly placed the k smallest elements in the prefix. The remainder is not sorted, but you might not care.\n\nIf I need “top 10 cheapest items,” I might do something like: run the outer loop for i < k and stop. That’s O(nk) comparisons instead of O(n^2).\n\nThis can be a sweet spot when:\n\n- k is tiny\n- n is moderate\n- you want predictable behavior\n\nBut I still consider alternatives like heaps or selection algorithms if n is large. The point is: selection sort is not only “a full sort.” It’s a useful way to build a correct prefix.\n\n## Production considerations: guardrails I use when I really ship a custom sort\nI rarely ship a custom sort when the library would do, but when I do, I add guardrails:\n\n- Tests with duplicates and random data, comparing against a known-correct oracle (Arrays.sort or List.sort).\n- A clear method name that encodes intent: selectionSortAscending is better than sort.\n- Documentation on stability and null policy, especially for object sorting.\n- A size threshold comment or assertion if the method is only intended for small arrays. Example: “This is used for at most 50 elements.”\n- Benchmarks (optional) if performance is the reason we’re not using the library.\n\nIn teams, I also like to add a “why” comment near the call site, not in the sort implementation. The implementation is obvious; the decision is not.\n\nExample of the kind of comment I trust:\n\n- “We use selection sort here because this array is at most 20 elements and writes trigger a model rebuild.”\n\nExample of the kind of comment I don’t trust:\n\n- “Selection sort is fast.”\n\n## A quick checklist (how I implement selection sort from memory)\nIf you want to be able to write selection sort cleanly during an interview or while pair programming, here’s my mental checklist:\n\n- Validate inputs (null policy).\n- n = values.length\n- Outer loop: i from 0 to n - 2\n- Set minIndex = i\n- Inner loop: j from i + 1 to n - 1\n- Update minIndex when you find a smaller element\n- After inner loop, swap i with minIndex (only if different)\n\nIf I follow that sequence, I almost never make an off-by-one mistake.\n\n## Final thoughts: what selection sort teaches you even if you never ship it\nEven if you never use selection sort in production again, it’s worth learning because it teaches habits that scale:\n\n- Thinking in invariants (what is guaranteed true after each pass).\n- Tracing state changes rather than “staring at code.”\n- Separating algorithm correctness from performance claims.\n- Being explicit about contracts (stability, null handling, in-place vs copy).\n\nAnd it gives you a sorting routine you can reason about line-by-line, which is exactly what you want when you’re debugging something that’s hard to reproduce.\n\nIf you want to go one step further, my recommended next exercise is: implement a version that sorts a List (not just arrays), add a stable variant for objects, and write tests that prove stability using labeled duplicates. That’s where “I know selection sort” turns into “I can ship and maintain sorting code responsibly.”\n


