Senior 10 min · March 05, 2026

Array Sorting in Java — Appended Items Break Binary Search

Arrays.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Arrays.sort() rearranges array elements in ascending order (numeric or alphabetical). It modifies the original array directly — no new array is created
  • Key components: import java.util.Arrays; call Arrays.sort(array); for descending, use Integer[] with Collections.reverseOrder()
  • Performance: Dual-Pivot Quicksort for primitives (O(n log n)), TimSort for objects — both are stable sorts (maintain relative order of equal elements)
  • Production trap: Trying to reverse an int[] with Collections.reverseOrder() — compile error because reverseOrder() only works on object arrays
  • Biggest mistake: Forgetting that Arrays.sort() sorts in-place — you lose the original order permanently unless you made a copy first
Plain-English First

Imagine you have a messy stack of exam papers, each with a score written on top. Before you can hand them back in order from lowest to highest, you need to sort them. That's exactly what array sorting does — it takes a jumbled list of values stored in your program and rearranges them into a predictable order. Java has a built-in helper called Arrays.sort() that does this sorting for you automatically, the same way a tidy assistant would sort those papers without you lifting a finger.

Every real application deals with ordered data. A leaderboard ranks players by score. A contacts list shows names alphabetically. A shop sorts products by price. None of that is possible if your data is sitting in random order. Sorting is one of the most fundamental operations in programming, and the sooner you get comfortable with it, the faster you'll be able to build things that actually feel polished and useful.

Before sorting existed as a built-in feature, developers had to write dozens of lines of code manually swapping values around — a slow, error-prone nightmare. Java's Arrays utility class solves this entirely. It ships with a sort() method powered by a highly optimised algorithm under the hood, so you get fast, reliable sorting in a single line of code without needing to understand the internals.

By the end you'll know how to sort primitive arrays in ascending order, reverse them into descending order, sort arrays of strings alphabetically, write a custom sort rule using a Comparator, and avoid the classic traps that trip up beginners. You'll walk away with working code you can drop straight into a real project.

Sorting a Basic Number Array With Arrays.sort()

Before you can sort anything, you need to import Java's Arrays class. It lives in the java.util package and it's the toolbox that contains sort() and many other helpful array utilities. Think of it like importing a specialised calculator — once it's imported, all its functions are available to you.

Arrays.sort() takes your array as an argument and sorts it in-place. 'In-place' means it rearranges the values inside the original array — it doesn't create a new one. After the method runs, your original variable now holds the sorted data. This is important to keep in mind because there's no 'undo' — the original order is gone.

For arrays of primitive numbers (int, double, long etc.), the default sort is always ascending — smallest value first, largest value last. This mirrors natural numeric order, the same way you'd read numbers on a number line from left to right.

The algorithm Java uses internally for primitive arrays is called Dual-Pivot Quicksort. You don't need to understand it yet, but know that it's extremely fast — even an array of a million numbers sorts in a fraction of a second.

io/thecodeforge/sorting/SortStudentScores.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package io.thecodeforge.sorting;

import java.util.Arrays; // Gives us access to the Arrays utility class

public class SortStudentScores {

    public static void main(String[] args) {

        // A class of 6 students just got their test results back — completely out of order
        int[] studentScores = { 78, 45, 92, 61, 33, 88 };

        // Print the scores BEFORE sorting so we can see the difference
        System.out.println("Before sorting: " + Arrays.toString(studentScores));

        // Arrays.sort() rearranges the values inside studentScores from low to high
        // No new array is created — the same array is modified directly
        Arrays.sort(studentScores);

        // Print the scores AFTER sorting — now lowest to highest
        System.out.println("After sorting:  " + Arrays.toString(studentScores));

        // We can now easily find the lowest score (index 0) and highest (last index)
        int lowestScore  = studentScores[0];
        int highestScore = studentScores[studentScores.length - 1];

        System.out.println("Lowest score:   " + lowestScore);
        System.out.println("Highest score:  " + highestScore);
    }
}
Pro Tip:
Always use Arrays.toString() when printing an array — printing the array variable directly (e.g. System.out.println(studentScores)) gives you a memory address like [I@6d06d69c, which is useless for debugging.
Production Insight
Arrays.sort() sorts in-place — the original array is permanently changed. No copy is made, no new array is returned.
If you need the original order for any future operation, you must copy before sorting: int[] copy = original.clone(); Arrays.sort(copy);
Rule: Never sort an array that other parts of your code still rely on in its original unsorted order. Copy first unless you're certain.
Key Takeaway
Arrays.sort() sorts in-place — the original array is permanently changed, so copy it first if you need the original order preserved.
Reverse order requires switching from int[] to Integer[] — primitives don't support Comparators, object wrappers do.
Rule: Always use Arrays.toString() when printing arrays — the raw variable prints a useless memory address, not the values.
Sorting Strategy Selection
IfArray contains primitives (int, double, long, char)
UseUse Arrays.sort(arr). Sorts ascending. To reverse, sort ascending then manually reverse, or switch to object array.
IfArray contains objects (String, Integer, etc.) and you need ascending natural order
UseUse Arrays.sort(arr). Uses natural ordering of elements (alphabetical for String, numeric for Number).
IfArray contains objects and you need descending order
UseUse Arrays.sort(arr, Collections.reverseOrder()). Array must be object array (Integer[], not int[]).
IfArray contains objects and you need custom sort (by field, property, calculated value)
UseUse Arrays.sort(arr, (a,b) -> Integer.compare(a.field, b.field)). Lambda comparator for one-line custom sort.
IfArray is large (>10M elements) and you need to sort repeatedly
UseUse Arrays.parallelSort(arr) for parallel sorting using multiple CPU cores. Faster on large arrays, slower on small ones.

Sorting Strings Alphabetically and Numbers in Descending Order

Sorting strings works exactly the same way as sorting numbers — just call Arrays.sort() and Java handles the alphabetical ordering for you. Under the hood, Java compares strings character by character using their Unicode values, which means uppercase letters ('A' = 65) sort before lowercase letters ('a' = 97). For everyday words that share the same case, this just looks like normal A-to-Z ordering.

Now, what about descending order — highest to lowest? This is where things get slightly different. Arrays.sort() has no built-in 'reverse' flag for primitive arrays. The cleanest workaround for int[] is to sort ascending first, then reverse the array manually. Alternatively, you can switch from a primitive int[] to an Integer[] (the object wrapper version), which unlocks a second form of Arrays.sort() that accepts a Comparator — a rule that tells Java how to compare two items.

Comparators.reverseOrder() is a ready-made comparator that flips the sort direction. Think of it as telling Java: 'for every comparison you make, do the opposite of what you'd normally do.'

This section shows both approaches side by side so you can choose whichever fits your situation.

io/thecodeforge/sorting/SortNamesAndRankings.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package io.thecodeforge.sorting;

import java.util.Arrays;
import java.util.Collections; // Needed for Collections.reverseOrder()

public class SortNamesAndRankings {

    public static void main(String[] args) {

        // ── PART 1: Sort a String array alphabetically ──────────────────────
        String[] playerNames = { "Zara", "Ahmed", "Priya", "Luca", "Mei" };

        System.out.println("Names before sort: " + Arrays.toString(playerNames));

        Arrays.sort(playerNames); // Sorts A-to-Z using Unicode/alphabetical order

        System.out.println("Names after sort:  " + Arrays.toString(playerNames));

        // ── PART 2: Sort integers in DESCENDING order (highest first) ────────
        // We must use Integer[] (capital I — the object wrapper) NOT int[]
        // because Collections.reverseOrder() only works with objects, not primitives
        Integer[] highScores = { 1500, 3200, 800, 4750, 2100 };

        System.out.println("\nScores before sort: " + Arrays.toString(highScores));

        // Pass Collections.reverseOrder() as the second argument to flip the sort
        Arrays.sort(highScores, Collections.reverseOrder());

        System.out.println("Scores after sort:  " + Arrays.toString(highScores));

        // The top player's score is now conveniently sitting at index 0
        System.out.println("Top score belongs to: " + playerNames[0] + " with " + highScores[0]);
    }
}
Watch Out:
Collections.reverseOrder() does NOT work with int[] — it causes a compile error. You must use Integer[] (the wrapper class) instead. This is one of the most common beginner mistakes when trying to sort in reverse order.
Production Insight
int[] and Integer[] are not interchangeable in sorting. int[] uses Dual-Pivot Quicksort (primitive operations). Integer[] uses TimSort with Comparator.
For descending order, converting int[] to Integer[] and back is expensive: O(n) boxing + O(n) sorting + O(n) unboxing.
Rule: For large primitive arrays (>1M ints), prefer sorting ascending then manual reversal in-place (O(n) reverse) over boxing to Integer[] (which duplicates memory).
Key Takeaway
Reverse order requires switching from int[] to Integer[] — primitives don't support Comparators, object wrappers do.
For large arrays, ascending sort + manual reversal is faster than boxing/unboxing to Integer[].
Rule: Use Collections.reverseOrder() on object arrays only (Integer[], String[], etc.). For int[], reverse manually after ascending sort.
Descending Sort Implementation
IfArray size < 100,000, convenience over performance
UseBox int[] to Integer[], sort with reverseOrder(), unbox back: int[] sorted = Arrays.stream(arr).boxed().sorted(Collections.reverseOrder()).mapToInt(i->i).toArray();
IfArray size > 100,000, performance matters
UseSort ascending with Arrays.sort(arr), then reverse in-place: for (int i=0; i<arr.length/2; i++) { int t = arr[i]; arr[i] = arr[arr.length-1-i]; arr[arr.length-1-i] = t; }
IfYou already have Integer[] array
UseUse Arrays.sort(arr, Collections.reverseOrder()). No boxing overhead. This is the cleanest approach.
IfYou need to sort by custom comparator descending
UseUse Arrays.sort(arr, (a,b) -> comparator.compare(b, a)) where comparator is your custom ascending comparator. Or use comparator.reversed().
IfArray contains primitives and you sort descending frequently
UseStore as Integer[] from the start if possible. The memory overhead (8 bytes per element extra for reference + object header) is acceptable for moderate sizes (<10M).

Custom Sorting Rules With a Comparator (Sort by Word Length, Not Alphabet)

Sometimes alphabetical or numeric order isn't what you need. What if you want to sort a list of movie titles by how long the title is — shortest first? Or sort products by the number of characters in their name? This is where a custom Comparator comes in.

A Comparator is simply a rule that tells Arrays.sort() how to decide which of two elements should come first. You hand it two items — call them 'a' and 'b' — and your rule returns a negative number if 'a' should come first, a positive number if 'b' should come first, or zero if they're equal. Java uses this rule for every comparison it makes during the sort.

In modern Java (version 8 and above) you can write a Comparator as a lambda expression — a short, inline piece of logic that looks like an arrow: (a, b) -> someRule. This means you don't need to write a whole separate class just to define one comparison rule.

Once you understand this pattern, you can sort absolutely anything by any rule you can imagine — it's one of the most powerful and flexible tools in Java.

io/thecodeforge/sorting/SortMovieTitlesByLength.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package io.thecodeforge.sorting;

import java.util.Arrays;

public class SortMovieTitlesByLength {

    public static void main(String[] args) {

        String[] movieTitles = {
            "Inception",
            "Up",
            "The Dark Knight",
            "Dune",
            "Interstellar"
        };

        System.out.println("Titles before sort: " + Arrays.toString(movieTitles));

        // Custom Comparator using a lambda expression:
        // (a, b) -> Integer.compare(a.length(), b.length())
        //
        // For each pair of titles, compare their lengths:
        //   - If a is shorter than b  → negative number → a goes first
        //   - If a is longer than b   → positive number → b goes first
        //   - If equal length         → 0               → keep current order
        Arrays.sort(movieTitles, (a, b) -> Integer.compare(a.length(), b.length()));

        System.out.println("Titles sorted by length (short → long):");

        // Loop through and print each title with its character count
        for (String title : movieTitles) {
            System.out.println("  " + title + " (" + title.length() + " chars)");
        }

        // BONUS: Reverse the lambda to get longest title first
        // Simply swap a and b in the comparison: compare(b.length(), a.length())
        Arrays.sort(movieTitles, (a, b) -> Integer.compare(b.length(), a.length()));

        System.out.println("\nTitles sorted by length (long → short):");
        for (String title : movieTitles) {
            System.out.println("  " + title + " (" + title.length() + " chars)");
        }
    }
}
Interview Gold:
Interviewers love asking you to 'sort by a custom field'. The pattern is always the same: Arrays.sort(array, (a, b) -> Integer.compare(a.someProperty, b.someProperty)). Memorise this shape and you can adapt it to any sorting problem in seconds.
Production Insight
Custom comparators can be slow if they perform expensive calculations inside the compare method (e.g., database lookups, network calls).
The comparator is called O(n log n) times (20 million times for 1M elements). Each call should be O(1) and cheap.
Rule: Precompute values into a separate array before sorting, then sort indices based on precomputed values. Or use a sorting network if comparator cost is high.
Key Takeaway
Custom Comparators follow one pattern: (a, b) -> Integer.compare(a.field, b.field) — swap a and b to reverse direction.
For multiple fields, chain comparators: Comparator.comparing(Person::getName).thenComparingInt(Person::getAge).
Rule: Never use subtraction for comparison: a.value - b.value can overflow. Always use Integer.compare(a.value, b.value).
Comparator Implementation Choices
IfCompare by single numeric property (age, price, score)
UseUse Comparator.comparingInt(Obj::getIntProperty). Or lambda: (a,b) -> Integer.compare(a.getVal(), b.getVal()). Avoid a.getVal() - b.getVal() — overflow risk.
IfCompare by multiple properties (first by name, then by age)
UseUse Comparator.comparing(Obj::getName).thenComparingInt(Obj::getAge). This chains comparators cleanly.
IfCompare by string property with case-insensitivity
UseUse Comparator.comparing(Obj::getName, String.CASE_INSENSITIVE_ORDER). Or lambda with a.getName().compareToIgnoreCase(b.getName()).
IfCompare by derived property (calculated on the fly)
UseIf calculation is cheap (O(1) arithmetic), include in comparator. If expensive, precompute into array of values and use comparator on precomputed.
IfReverse existing comparator
UseUse comparator.reversed(). Or in lambda, swap a and b: (a,b) -> original.compare(b, a). Or use Collections.reverseOrder(comparator).

Time and Space Complexity: Dual-Pivot Quicksort vs TimSort

Java uses two different sorting algorithms under the hood depending on whether the array contains primitives or objects. Understanding the complexity of each helps you predict performance for different data sizes and choose the right tool.

AlgorithmData TypeAverage TimeWorst TimeSpaceStable
Dual-Pivot Quicksortprimitives (int[], double[], etc.)O(n log n)O(n²)O(log n)No
TimSortobjects (Integer[], String[], custom objects)O(n log n)O(n log n)O(n)Yes

Dual-Pivot Quicksort is in-place (negligible extra memory) and very cache-friendly, making it ideal for primitives. Its worst-case O(n²) occurs only on pathological data (e.g., already sorted with specific pivot choices). Java mitigates this with randomised pivots.

TimSort is a hybrid of merge sort and insertion sort. It's stable (equal elements keep original order) and guarantees O(n log n) worst-case. The O(n) space comes from temporary arrays for merging. Stability is essential for objects because the sort may be a secondary operation after a primary sort (e.g., sort by name, then by age).

Production note: TimSort's space usage can be a concern for huge arrays on memory-constrained systems. If you have a 100 million element Integer[] array, TimSort may require up to 100 million extra references (roughly 800 MB for the temporary array). For primitive arrays, Dual-Pivot Quicksort uses almost no extra memory.

Production Insight
TimSort's O(n) space can cause OutOfMemoryError for very large object arrays (e.g., 100M elements). For such cases, consider sorting with a custom in-place algorithm or using external sorting.
Dual-Pivot Quicksort's O(n²) worst case is rare but possible. If your data has many duplicates or a specific order that triggers worst-case, consider shuffling before sort or using TimSort via Integer[].
Key Takeaway
Primitives → Dual-Pivot Quicksort (fast, in-place, unstable, O(log n) space). Objects → TimSort (stable, guaranteed O(n log n), O(n) space). Choose based on data type and stability requirements.

Collections.sort() vs Arrays.sort() — When to Use Which

Both Collections.sort() and Arrays.sort() use TimSort for object arrays, but they operate on different data structures. Collections.sort() sorts List implementations (like ArrayList, LinkedList), while Arrays.sort() sorts arrays directly.

FeatureCollections.sort()Arrays.sort()
Input typeList (ArrayList, LinkedList, etc.)Array (primitive or object)
AlgorithmTimSort for objectsDual-Pivot Quicksort for primitives, TimSort for objects
In-placeYes (modifies original list)Yes (modifies original array)
StableYesFor objects only (TimSort); primitives unstable
Parallel versionNone (use list.parallelStream().sorted() )Arrays.parallelSort()
SyntaxCollections.sort(list)Arrays.sort(array)
Custom comparatorCollections.sort(list, comparator)Arrays.sort(array, comparator)

When to use which: - If your data is in an array (e.g., from legacy code or fixed-size buffer), use Arrays.sort(). - If your data is in a List (modern Java prefers collections), use Collections.sort(). - For primitive arrays, Arrays.sort() is the only option. - For large object collections that need stable sorting, both behave identically.

Production trap: Collections.sort() on a LinkedList is very inefficient because TimSort needs O(n log n) random accesses; LinkedList gives O(n) access per element. Always sort ArrayList-backed lists, not LinkedList.

Production Insight
Collections.sort() delegates to List.sort() (since Java 8), which copies the list to an array, sorts, and copies back. This creates a temporary array that doubles memory briefly. For huge lists, consider ArrayList.sort() (same as Collections.sort()) or use Arrays.sort() on the underlying array if accessible.
Key Takeaway
Use Arrays.sort() for arrays, Collections.sort() for Lists. Both use TimSort for objects. Avoid sorting LinkedList — convert to ArrayList first.

Sorting with Stream API: sorted() and sorted(Comparator)

Java 8 introduced the Stream API, which provides a functional way to sort arrays without mutating the original. The stream().sorted() method returns a new stream with elements sorted, leaving the original array untouched. This is ideal for one-off sorting where you don't want to modify the source array.

Stream.sorted() with natural order: ``java int[] numbers = {5, 3, 8, 1, 9}; int[] sorted = Arrays.stream(numbers).sorted().toArray(); System.out.println(Arrays.toString(sorted)); // [1, 3, 5, 8, 9] ``

Stream.sorted() with Comparator: For object arrays, you can pass a Comparator: ``java String[] names = {"Zara", "Ahmed", "Priya"}; String[] sortedDesc = Arrays.stream(names) .sorted(Comparator.reverseOrder()) .toArray(String[]::new); ``

Custom comparator with method references: ``java List<Person> people = ...; List<Person> sorted = people.stream() .sorted(Comparator.comparing(Person::getAge)) .collect(Collectors.toList()); ``

Performance considerations: Stream sorting creates a copy of the data. For large arrays, this doubles memory usage. However, the copy is temporary and will be garbage collected. If you need to sort the original array in-place for performance, use Arrays.sort() instead.

io/thecodeforge/sorting/StreamSortDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.thecodeforge.sorting;

import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.Collectors;

public class StreamSortDemo {

    public static void main(String[] args) {
        // Primitive array sorted with streams (does not modify original)
        int[] scores = {88, 45, 92, 61, 33};
        int[] sortedScores = Arrays.stream(scores).sorted().toArray();
        System.out.println("Original: " + Arrays.toString(scores));
        System.out.println("Sorted:   " + Arrays.toString(sortedScores));

        // Object array sorted descending
        String[] players = {"Zara", "Ahmed", "Priya", "Luca"};
        String[] descending = Arrays.stream(players)
                .sorted(Comparator.reverseOrder())
                .toArray(String[]::new);
        System.out.println("Descending: " + Arrays.toString(descending));

        // Custom comparator: sort by string length
        String[] movies = {"Inception", "Up", "Interstellar"};
        String[] byLength = Arrays.stream(movies)
                .sorted(Comparator.comparingInt(String::length))
                .toArray(String[]::new);
        System.out.println("By length: " + Arrays.toString(byLength));
    }
}
When to Use Stream Sorting:
Use stream sorting when you need a sorted version without mutating the original, or when chaining multiple operations (filter, map, sort). Use Arrays.sort() when memory is tight or you need in-place modification for performance.
Production Insight
Stream.sorted() creates an intermediate array for sorting. For arrays of millions of elements, this temporary array doubles memory usage. If you already have the array in memory, Arrays.sort() is more efficient. Stream sorting is best for one-off views or when the data size is moderate.
Key Takeaway
Stream.sorted() returns a new sorted array without modifying the original. Use it for functional pipelines; use Arrays.sort() for in-place, memory-efficient sorting.

Parallel Sorting with Arrays.parallelSort()

Arrays.parallelSort() is a multi-threaded version of sorting that splits the array into chunks, sorts each chunk in parallel using the Fork/Join pool, then merges the results. It is available for both primitive and object arrays.

When to use: - Array size > 10 million elements (threshold varies by system, typically ~8192 for the parallel branch) - Sorting is a bottleneck and you have multiple CPU cores - You are sorting on a non-real-time thread (the Fork/Join pool can delay other tasks)

When NOT to use: - Small arrays (overhead of thread management outweighs benefits) - Real-time or latency-sensitive applications (parallel sorting may block the common Fork/Join pool) - Memory-constrained environments (parallelSort uses additional temporary arrays for merging)

Performance: On a typical 4-core machine, parallelSort can be 2-4x faster for arrays of 10 million integers. The speedup diminishes with more cores due to memory bandwidth limits.

Code example: ``java int[] bigArray = new int[50_000_000]; // fill array... Arrays.parallelSort(bigArray); // sorts using multiple threads ``

Under the hood: The array is divided into subarrays equal to the number of available processors. Each subarray is sorted independently using Dual-Pivot Quicksort (primitives) or TimSort (objects). Then a merge operation combines them.

io/thecodeforge/sorting/ParallelSortDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package io.thecodeforge.sorting;

import java.util.Arrays;
import java.util.Random;

public class ParallelSortDemo {

    public static void main(String[] args) {
        // Create a large array
        int size = 10_000_000;
        int[] data = new int[size];
        Random rand = new Random();
        for (int i = 0; i < size; i++) {
            data[i] = rand.nextInt(1_000_000);
        }

        // Time regular sort
        int[] copy1 = data.clone();
        long start = System.nanoTime();
        Arrays.sort(copy1);
        long regularTime = System.nanoTime() - start;

        // Time parallel sort
        int[] copy2 = data.clone();
        start = System.nanoTime();
        Arrays.parallelSort(copy2);
        long parallelTime = System.nanoTime() - start;

        System.out.println("Regular sort:  " + regularTime / 1_000_000 + " ms");
        System.out.println("Parallel sort: " + parallelTime / 1_000_000 + " ms");
        System.out.println("Speedup: " + (regularTime / (double) parallelTime) + "x");
    }
}
Caveat:
Arrays.parallelSort() uses the common Fork/Join pool, which is shared with parallel streams. Long-running parallel sorts can starve other parallel operations. Consider using a custom pool for critical applications.
Production Insight
In production, profile before adopting parallelSort. The actual speedup depends on array size, data distribution, core count, and memory bandwidth. For arrays smaller than 10 million, Arrays.sort() is often faster due to lower overhead. Also, parallelSort can increase memory pressure because of intermediate merge buffers.
Key Takeaway
Use Arrays.parallelSort() for large arrays (>10M elements) on multi-core systems. For smaller arrays or latency-sensitive contexts, stick with Arrays.sort().

Multi-Key Sorting with Comparator.comparing() and thenComparing()

Sorting by multiple fields is a common requirement: sort employees by department, then by salary within the same department, then by name. Java 8 introduced factory methods in the Comparator interface that make this clean and composable.

Comparator.comparing() extracts a key and returns a comparator that sorts by that key. It's like a lambda that says: "compare two objects by comparing one property of each."

Chaining with thenComparing() adds secondary sort criteria. If two objects are equal by the first comparator, the second one is used, and so on.

Syntax: ```java Comparator<Person> multiComparator = Comparator .comparing(Person::getDepartment) // primary: department name .thenComparingInt(Person::getSalary) // secondary: salary (ascending) .thenComparing(Person::getName); // tertiary: name

Arrays.sort(people, multiComparator); ```

You can reverse any step by calling .reversed() on that comparator. For example, Comparator.comparing(Person::getDepartment).reversed().thenComparingInt(Person::getSalary) sorts by department descending, then salary ascending.

Important: When chaining, be careful with .reversed() placement. To reverse the entire comparator, call .reversed() at the end: multiComparator.reversed().

io/thecodeforge/sorting/MultiKeySortDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package io.thecodeforge.sorting;

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

class Employee {
    String name;
    String department;
    int salary;

    Employee(String name, String department, int salary) {
        this.name = name;
        this.department = department;
        this.salary = salary;
    }

    @Override
    public String toString() {
        return String.format("%s (%s, $%d)", name, department, salary);
    }
}

public class MultiKeySortDemo {

    public static void main(String[] args) {
        Employee[] team = {
            new Employee("Alice", "Engineering", 120000),
            new Employee("Bob",   "Engineering", 95000),
            new Employee("Carol", "Sales",       110000),
            new Employee("Dave",  "Engineering", 120000),
            new Employee("Eve",   "Sales",       95000)
        };

        System.out.println("Before sorting:");
        Arrays.stream(team).forEach(System.out::println);

        // Sort by department (ascending), then by salary descending, then by name ascending
        Comparator<Employee> byDept = Comparator.comparing(Employee::getDepartment);
        Comparator<Employee> bySalaryDesc = Comparator.comparingInt(Employee::getSalary).reversed();
        Comparator<Employee> byName = Comparator.comparing(Employee::getName);

        Comparator<Employee> multi = byDept.thenComparing(bySalaryDesc).thenComparing(byName);

        Arrays.sort(team, multi);

        System.out.println("\nAfter sorting (dept asc, salary desc, name asc):");
        Arrays.stream(team).forEach(System.out::println);
    }
}
Method Reference Shortcut:
You can use Comparator.comparing(Person::getName) instead of (a,b) -> a.getName().compareTo(b.getName()). It's shorter and more readable.
Production Insight
Chaining multiple comparators is O(1) per comparison — each comparator is invoked only when the previous ones return 0. For sorting hundreds of thousands of objects with 3-4 keys, performance is negligible. However, if a key extraction is expensive (e.g., file I/O), precompute the key into a field before sorting.
Key Takeaway
Use Comparator.comparing(keyExtractor).thenComparing(anotherKeyExtractor).thenComparing(...) to sort by multiple fields. Apply .reversed() on individual steps or on the entire chain.

Sorting a 2D Array in Java

2D arrays (arrays of arrays) are common in grid-based problems, matrix operations, and data tables. Sorting a 2D array usually means sorting rows based on the values in one or more columns.

Sort by a specific column: Use a custom comparator that compares the elements at the chosen column index.

Example: sort by first column ascending, second column descending: ```java int[][] matrix = { {5, 2}, {1, 9}, {5, 1}, {3, 7} };

Arrays.sort(matrix, (a, b) -> { if (a[0] != b[0]) return Integer.compare(a[0], b[0]); // first column asc else return Integer.compare(b[1], a[1]); // second column desc }); ```

This syntax works because the 2D array is an array of int[] objects. The comparator receives two rows (int[]), and you compare specific indices.

For stable sorting with multiple columns: Use Comparator.comparingInt(row -> row[0]).thenComparingInt(row -> -row[1]) but careful with negation overflow. Better to use the lambda with explicit Integer.compare.

Sorting an array of arrays by multiple fields (like SQL ORDER BY): You can chain comparators using method references if you have a simple class, but for raw 2D arrays, lambdas are clearest.

io/thecodeforge/sorting/Sort2DArray.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package io.thecodeforge.sorting;

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

public class Sort2DArray {

    public static void main(String[] args) {
        int[][] grid = {
            {4, 2, 7},
            {1, 9, 3},
            {4, 2, 1},
            {3, 5, 6}
        };

        System.out.println("Original:");
        print2D(grid);

        // Sort by column 0 ascending, then column 1 descending, then column 2 ascending
        Arrays.sort(grid, (a, b) -> {
            if (a[0] != b[0]) return Integer.compare(a[0], b[0]);
            if (a[1] != b[1]) return Integer.compare(b[1], a[1]);
            return Integer.compare(a[2], b[2]);
        });

        System.out.println("\nSorted (col0 asc, col1 desc, col2 asc):");
        print2D(grid);

        // Alternative using Comparator.comparing() with reversed() – note reversed() reverses entire chain
        // Best for single-column sorts:
        // Arrays.sort(grid, Comparator.comparingInt(row -> row[0]));
    }

    private static void print2D(int[][] arr) {
        for (int[] row : arr) {
            System.out.println(Arrays.toString(row));
        }
    }
}
Printing 2D Arrays:
Use Arrays.deepToString(grid) to print a 2D array in one line. For readability in debugging, loop and print each row with Arrays.toString().
Production Insight
Sorting 2D arrays with custom lambdas is efficient for moderately sized grids (<100k rows). For huge 2D arrays, consider using a wrapper object that stores the row and implements Comparable, then sort an array of those wrappers. This reduces comparator overhead (row[0] access becomes field access).
Key Takeaway
Sort 2D arrays by passing a lambda comparator to Arrays.sort() that compares specific column indices. Use explicit Integer.compare to handle ties with descending order.

Practice Exercises: Sorting Arrays in Java

Sharpen your sorting skills with these real-world exercises. Each builds on concepts from this guide.

Exercise 1: Sort objects by multiple fields Create a Student class with fields name (String), grade (int), and age (int). Create an array of 10 students with varied data. Sort them by grade descending, then by age ascending, then by name alphabetically. Print the sorted list.

Exercise 2: Sort a 2D array by column Given a 2D array int[][] scores = {{3, 5, 1}, {2, 8, 7}, {3, 5, 3}}; sort the rows by first column ascending, then second column descending, then third column ascending. Verify the result.

Exercise 3: Partial sort using Arrays.sort() Create an int array of 20 random numbers. Sort only the elements from index 5 to index 14 (inclusive) using Arrays.sort(arr, fromIndex, toIndex). Leave the rest untouched. Print the array to verify the partial sort.

Exercise 4: Sort strings by custom rule Given an array of strings, sort them by the number of vowels in each word (case-insensitive). If two words have the same vowel count, sort them by their natural (alphabetical) order. Hint: define a helper method to count vowels.

Exercise 5: Merge sorted arrays Create two sorted int arrays: a = {1, 3, 5, 7} and b = {2, 4, 6, 8}. Merge them into a single sorted array without using Arrays.sort() on the combined array. Use a two-pointer approach. Then verify the result is sorted.

Bonus challenge: Implement a custom sort() method that uses insertion sort for small arrays (size < 20) and delegates to Arrays.sort() for larger ones. Measure the performance difference on 100 random arrays of varying sizes.

Production Insight
Practice exercises like partial sort (range sort) are useful in production when you need to sort only a portion of a larger buffer without disturbing the rest. The Arrays.sort(arr, fromIndex, toIndex) variant is often overlooked but very handy for paginated or windowed data.
Key Takeaway
Practice multi-field sorting, 2D array sorting, and partial sorting to solidify your understanding of comparators and array manipulation.
● Production incidentPOST-MORTEMseverity: high

The Sorted Inventory That Weren't Actually Sorted

Symptom
The product listing page sorted by price ascending most of the time, but occasionally products appeared out of order. The out-of-order products were always the newest inventory additions. Refreshing the page sometimes fixed it, sometimes didn't. The array was sorted once at application startup, not before every display.
Assumption
The developer assumed that once you sort an array with Arrays.sort(), it stays sorted forever. They didn't realise that adding new elements to the array (or receiving new data from the database) would break the sorted order. They also didn't know that Arrays.sort() sorts in-place, meaning the original array is permanently modified — but new elements appended after the sort are not automatically inserted correctly.
Root cause
The product list was an array of product objects that was sorted once at application startup. When the inventory system added new products during the day, the code appended them to the end of the array using a naive expansion + assignment. The new elements were not inserted in sorted order. The product listing page continued to use the same array reference, which was now mostly sorted except for the new items appended at the end. Binary search (for quick price lookup) started failing because the array was no longer fully sorted. The page displayed products in the array's order (mostly sorted + new items at end), causing the price-jumping behaviour.
Fix
1. Moved from a raw array to an ArrayList<Product> with automatic sorting before each display using Collections.sort() with a custom comparator. 2. For performance, switched to a TreeSet<Product> with a comparator that sorts by price — the collection maintains sorted order automatically on every insertion (O(log n)). 3. If array was required, implemented insertion sort for new elements: find insertion point using binary search (O(log n)) then shift array elements right (O(n)). 4. Added a metric to measure array sortedness in production: isSorted = true; for (int i = 1; i < arr.length; i++) if (arr[i-1] > arr[i]) isSorted = false; 5. Added a unit test that adds new products after sorting and verifies the array remains sorted.
Key lesson
  • Arrays.sort() sorts once, not continuously. The array is not magically 'watched' for changes. Any mutation after sort breaks the sorted invariant.
  • In-place sort means the original array is modified. If you need the original order elsewhere, copy first: int[] copy = Arrays.copyOf(original, original.length); Arrays.sort(copy);
  • For collections that need to stay sorted after every insert, use TreeSet (unique elements) or PriorityQueue (ordered, allows duplicates) instead of manual sorting.
  • If an array must remain sorted, never append elements without inserting them in the correct position. Use int idx = Arrays.binarySearch(arr, newValue); if (idx < 0) idx = -idx - 1; then shift and insert.
Production debug guideSymptom → Action mapping for common sorting failures in production Java applications.5 entries
Symptom · 01
Sorted array appears to have items out of order — not fully sorted
Fix
The array was sorted once, but new elements were appended without re-sorting. Re-sort: Arrays.sort(arr). Better: use data structure that maintains order (TreeSet, PriorityQueue). Check insert path for binary search to find correct insertion point.
Symptom · 02
Compiler error when using Collections.reverseOrder() on int[]
Fix
reverseOrder() works only on object arrays. Change int[] to Integer[]. Or sort ascending then reverse manually: for (int i = 0; i < arr.length/2; i++) { int temp = arr[i]; arr[i] = arr[arr.length-1-i]; arr[arr.length-1-i] = temp; }
Symptom · 03
Binary search returns wrong index on sorted array
Fix
The array is not actually sorted (or sorted with different comparator than the search uses). Verify: for (int i = 1; i < arr.length; i++) assert arr[i-1] <= arr[i] : 'Array not sorted at index ' + i;
Symptom · 04
NullPointerException when sorting array with null elements
Fix
Comparator.compare() throws NPE on null. Use Comparator.nullsFirst(Comparator.naturalOrder()) or Comparator.nullsLast() to handle nulls gracefully: Arrays.sort(arr, Comparator.nullsLast(String::compareTo));
Symptom · 05
Custom Comparator seems to be looping — sort never finishes for large array
Fix
Comparator may violate consistency rules: sign(a,b) must be opposite of sign(b,a). Also must be transitive and reflexive. Violations cause some sorts (TimSort) to throw IllegalArgumentException: 'Comparison method violates its general contract'.
★ Array Sorting Debug Cheat SheetFast diagnostics for sorting issues in production Java applications.
Array not sorted after calling Arrays.sort() — no error
Immediate action
Check if you're sorting a view or proxy, not the actual array
Commands
System.out.println('Is array null? ' + (arr == null)); System.out.println('Array length: ' + arr.length);
Arrays.sort(arr); System.out.println('After sort: ' + Arrays.toString(arr));
Fix now
Ensure the array variable you're sorting is the same reference you're reading later. If you sort a copy, the original remains unsorted. Use Arrays.sort(theActualArray).
reverseOrder() compile error — 'no suitable method found'+
Immediate action
Check array type: int[] (primitive) vs Integer[] (object)
Commands
System.out.println('arr.getClass().isPrimitive(): ' + arr.getClass().isPrimitive()); // but arrays are objects, actual test: 'int[].class'
Integer[] boxed = Arrays.stream(primitiveArr).boxed().toArray(Integer[]::new); Arrays.sort(boxed, Collections.reverseOrder());
Fix now
Change int[] to Integer[] in variable declaration. Or use manual reversal: sort ascending then swap elements.
Sorting objects with custom Comparator — Exception 'Comparison method violates its general contract'+
Immediate action
Check if comparator is transitive (if a > b and b > c, then a > c) and reflexive
Commands
java -Djava.util.Arrays.useLegacyMergeSort=true // temporary workaround to switch to legacy sort that doesn't check contract
Arrays.sort(testArray, (a,b) -> { int res = a.value - b.value; System.out.println('Comparing ' + a + ' and ' + b + ' -> ' + res); return res; });
Fix now
Rewrite comparator to be consistent. Never return a.value - b.value if values can overflow. Use Integer.compare(a.value, b.value). Ensure compare(a,b) == -compare(b,a).
NullPointerException when sorting array with Comparator+
Immediate action
The array contains null elements, and the comparator doesn't handle null
Commands
System.out.println('Null count: ' + Arrays.stream(arr).filter(Objects::isNull).count());
Arrays.sort(arr, Comparator.nullsFirst(Comparator.naturalOrder())); // or nullsLast
Fix now
Add null handling: if (a == null && b == null) return 0; if (a == null) return -1; if (b == null) return 1; Or use Comparator.nullsFirst().
Array elements changed after sorting — lost original order+
Immediate action
Arrays.sort() sorts in-place. Did you need to preserve the original order?
Commands
int[] originalCopy = Arrays.copyOf(original, original.length); Arrays.sort(originalCopy); // keep original
System.out.println('Original lost: ' + original.equals(originalCopy) + ' after sort?');
Fix now
If you need both sorted and original, copy before sorting: int[] sorted = original.clone(); Arrays.sort(sorted); Use original for original order, sorted for sorted order.
Arrays.sort() Method Variants
VariantSignatureWorks onReturnsBest for
Default ascendingArrays.sort(arr)primitive[] + Comparable[]void (in-place)
Descending orderArrays.sort(arr, Collections.reverseOrder())Object[] only (Integer[], String[])void (in-place)
Custom Comparator (lambda)Arrays.sort(arr, (a,b) -> Integer.compare(a.f,b.f))Object[] onlyvoid (in-place)
Parallel sort (multicore)Arrays.parallelSort(arr)primitive[] + Comparable[]void (in-place)
Partial sorting (range)Arrays.sort(arr, fromIndex, toIndex)primitive[] + Comparable[]void (in-place)

Key takeaways

1
Arrays.sort() sorts in-place
the original array is permanently changed, so copy it first if you need the original order preserved
2
Reverse order requires switching from int[] to Integer[]
primitives don't support Comparators, object wrappers do
3
Custom Comparators follow one pattern
(a, b) -> Integer.compare(a.field, b.field) — swap a and b to reverse direction
4
Always use Arrays.toString() when printing arrays
the raw variable prints a useless memory address, not the values
5
Never use subtraction for comparison (a.value - b.value)
it can overflow. Use Integer.compare(a.value, b.value) instead.

Common mistakes to avoid

5 patterns
×

Using Collections.reverseOrder() on a primitive int[] array

Symptom
Compile-time error: 'no suitable method found for sort(int[], Comparator)' because reverseOrder() returns a Comparator<Object> that doesn't work with primitives.
Fix
Change int[] to Integer[] in variable declaration. Or sort ascending then manually reverse: for (int i=0; i<arr.length/2; i++) { int t = arr[i]; arr[i] = arr[arr.length-1-i]; arr[arr.length-1-i] = t; }
×

Forgetting that Arrays.sort() modifies the original array

Symptom
Later code uses the array, expecting original unsorted order, but finds it sorted. Hard-to-debug because no exception is thrown — just wrong data.
Fix
Make a copy first using int[] copy = Arrays.copyOf(original, original.length); Arrays.sort(copy); The original remains untouched. Use original for original order, copy for sorted order.
×

Printing an array with System.out.println(myArray) and getting a garbage memory address like [I@7852e922

Symptom
Expecting [1, 2, 3], getting cryptic output like [I@6d06d69c. This happens because arrays don't override toString() in Java.
Fix
Always wrap the array in Arrays.toString(myArray) before printing: System.out.println(Arrays.toString(myArray));. For 2D arrays, use Arrays.deepToString(my2DArray).
×

Using subtraction in comparator (a.value - b.value) causing integer overflow

Symptom
Sort order is incorrect for values near Integer.MAX_VALUE/MIN_VALUE. For example, comparing Integer.MIN_VALUE (-2147483648) and 1: MIN_VALUE - 1 = 2147483647 (positive), incorrectly indicating MIN_VALUE > 1.
Fix
Always use Integer.compare(a.value, b.value) or Long.compare(a.value, b.value) which handle overflow correctly. Never use a.value - b.value for comparison.
×

Assuming Comparator with logic that violates the compare contract

Symptom
Comparator may return inconsistent results: compare(a,b) != -compare(b,a). TimSort throws IllegalArgumentException: Comparison method violates its general contract
Fix
Ensure comparator is reflexive (compare(a,a)=0), symmetric (compare(a,b) = -compare(b,a)), and transitive (if a>b and b>c then a>c). Use Comparator helper methods instead of manual logic.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What sorting algorithm does Arrays.sort() use internally for primitive a...
Q02SENIOR
How would you sort an array of strings by their length in descending ord...
Q03JUNIOR
If I call Arrays.sort() on an int[] and then need the original unsorted ...
Q04SENIOR
How does Java handle sorting of large arrays with parallelSort? When wou...
Q01 of 04SENIOR

What sorting algorithm does Arrays.sort() use internally for primitive arrays vs object arrays, and why are they different?

ANSWER
For primitive arrays (int[], double[], etc.), Arrays.sort() uses Dual-Pivot Quicksort. For object arrays (Integer[], String[]), it uses TimSort (a hybrid of merge sort and insertion sort). Why different? Primitive arrays can use quicksort because it's in-place, faster, and uses less memory — but quicksort is unstable (doesn't preserve order of equal elements). Stable sort isn't required for primitives because equal values are identical. For objects, stability matters: if two objects are equal by the comparator, their original relative order should be preserved. TimSort is stable and guarantees O(n log n) worst-case performance. Quicksort's worst-case O(n²) is unacceptable for objects where comparison is more expensive. The different algorithms optimise for their data type's characteristics.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
How do I sort an array in descending order in Java?
02
Does Arrays.sort() change the original array in Java?
03
Why does printing a Java array directly show something like [I@6d06d69c instead of the values?
04
What's the difference between Arrays.sort() and Arrays.parallelSort()?
05
How do I sort an array of custom objects by multiple fields (e.g., by name, then by age)?
🔥

That's Arrays. Mark it forged?

10 min read · try the examples if you haven't

Previous
Multi-dimensional Arrays in Java
3 / 8 · Arrays
Next
Array Searching in Java