Java • Algorithms and Complexity

Arrays.sort & Collections.sort — Use Arrays.sort and Collections.sort; compare comparators (intro)

Master Java's built-in sorting tools and learn how comparators let you control sort order.

Java programming emblem with a coffee cup, code braces, and terminal
Inside this lesson 10 sections 5 questions 7 figures

SECTION 1

Historical Context & Motivation

Sorting is one of the oldest and most studied problems in computer science. Even before modern computers existed, people needed efficient ways to organize information — think of libraries alphabetizing their card catalogs or census offices ordering population records. When programming languages matured, developers realized they shouldn't have to write their own sorting code from scratch every time. This led to the creation of standard library sorting methods that are fast, reliable, and easy to use.

Java's sorting utilities evolved alongside the language itself. The earliest versions shipped with basic sorting routines, but over the years the Java team refined these tools to handle arrays, lists, and custom ordering rules. Understanding this history helps you appreciate why Java provides both Arrays.sort and Collections.sort — and why comparators became a central part of the design.

1996 • Java 1.0 Launches

Java ships with java.util.Arrays providing basic sorting for primitive arrays. The underlying algorithm is a variant of quicksort.

1998 • Java Collections Framework (JCF)

Java 1.2 introduces the Collections Framework, adding Collections.sort and the Comparator interface for custom ordering of object lists.

2009 • TimSort Adopted

Java 7 replaces the old merge sort for objects with TimSort, a hybrid algorithm that performs excellently on real-world data with partially sorted runs.

2011 • Dual-Pivot Quicksort

Java 7 also introduces Dual-Pivot Quicksort by Vladimir Yaroslavskiy for primitive arrays, outperforming the classic single-pivot version in practice.

2014 • Lambda Expressions

Java 8 adds lambda syntax, letting you write comparators in a single line like (a, b) -> a.length() - b.length(), dramatically reducing boilerplate code.

The key question this lesson addresses is: how do you sort data in Java efficiently, and how do you customize the sort order when the default (ascending) isn't what you need? The answer lies in mastering Arrays.sort, Collections.sort, and the Comparator interface.

SECTION 2

Core Principles & Definitions

Before diving into code, you need to understand a few foundational ideas. Java provides two main entry points for sorting — one for raw arrays and one for list-based collections. Both share the concept of a natural ordering (the default way elements compare themselves) and a custom ordering defined by a comparator you write.

1

Arrays.sort

A static method in java.util.Arrays that sorts arrays in place. Works on both primitive arrays (int[], double[]) and object arrays (String[], Integer[]). Uses Dual-Pivot Quicksort for primitives and TimSort for objects.
2

Collections.sort

A static method in java.util.Collections that sorts List objects (like ArrayList or LinkedList). Under the hood, it converts the list to an array, uses TimSort, then writes the sorted result back.
3

Comparable Interface

A class implements Comparable<T> to define its natural ordering. The compareTo() method returns a negative, zero, or positive int to indicate order. Strings and Integers already implement this.
4

Comparator Interface

A separate object that defines a compare(a, b) method for custom ordering. You can pass a Comparator to both Arrays.sort and Collections.sort to override natural ordering — sorting by length, reverse order, or any rule you invent.
5

Stability

A sort is stable if equal elements keep their original relative order. TimSort (used for objects) is stable. Dual-Pivot Quicksort (used for primitives) is not guaranteed stable, but this only matters when elements are distinguishable beyond their sort key.
✦

KEY TAKEAWAY

Think of sorting like organizing a playlist. The natural ordering is alphabetical by song title — that's the default. A comparator is like telling the music app: "Actually, sort by artist, then by year." You're providing a custom rule that overrides the default. Arrays.sort works on raw arrays (the plain array in memory), while Collections.sort works on Lists (a higher-level container).

SECTION 3

Visual Explanation — How Sorting Flows

The following diagram shows the decision tree when you want to sort data in Java. It starts with the type of collection you have (an array or a List) and follows through to how a Comparator plugs in to change the sort order.

What data structure?ArrayListArrays.sort()Collections.sort()primitive[]Object[]Dual-Pivot QuicksortTimSort (stable)TimSort (stable)No Comparator → Natural Ordering(ascending for numbers, alphabetical for Strings)With Comparator → Custom Ordering(reverse, by length, by any field you choose)Optional: pass aComparatorSorted Result (in place)Both methods modify the original array / list
This flowchart shows the decision path: start with your data type (array or List), choose the appropriate sort method, and optionally pass a Comparator to customize ordering. Both paths modify the original data in place.

Notice that both Arrays.sort and Collections.sort converge on the same outcome: your data ends up rearranged in the original container. Neither method returns a new array or list — they sort in place. The critical fork is whether you supply a Comparator. Without one, Java falls back to natural ordering (numbers ascending, strings alphabetically). With one, you take full control of the sort logic.

SECTION 4

How Comparators Work

A Comparator is an object that implements the java.util.Comparator<T> interface. It has one essential method: int compare(T a, T b). The return value tells the sort algorithm how to order the two elements.

COMPARATOR CONTRACT

compare(a,b)→negative⇒acomesbeforebcompare(a,b)→0⇒aandbareequalcompare(a,b)→positive⇒acomesafterbcompare(a, b) → negative ⇒ a comes before b compare(a, b) → 0 ⇒ a and b are equal compare(a, b) → positive ⇒ a comes after b

The magnitude of the return value doesn't matter — only its sign (negative, zero, or positive) determines ordering. For example, returning −1 or −1000 both mean "a comes first."

Three Ways to Create a Comparator

There are three common approaches. The first is an anonymous inner class, where you create the Comparator object inline without naming a separate class. This was the standard approach before Java 8:

ℹ

Anonymous Inner Class

Arrays.sort(words, new Comparator<String>() { public int compare(String a, String b) { return a.length() - b.length(); } }); This sorts the words array by string length (shortest first).

The second approach uses a lambda expression (Java 8+), which is much shorter and cleaner. Since Comparator has exactly one abstract method, Java lets you replace the whole anonymous class with an arrow function:

💡

Lambda Expression

Arrays.sort(words, (a, b) -> a.length() - b.length()); This single line does exactly the same thing as the anonymous inner class above.

The third approach uses the Comparator utility methods introduced in Java 8, such as Comparator.comparingInt():

💡

Utility Method

Arrays.sort(words, Comparator.comparingInt(String::length)); This reads almost like English: "sort words, comparing by integer result of length." It's the most readable option for simple key-based sorts.

REVERSE ORDER SHORTCUT

Collections.sort(list,Comparator.reverseOrder())Collections.sort(list,myComparator.reversed())Collections.sort(list, Comparator.reverseOrder()) Collections.sort(list, myComparator.reversed())

Use Comparator.reverseOrder() to flip natural ordering, or call .reversed() on any existing comparator to invert its logic.

SECTION 5

Arrays.sort vs. Collections.sort — Side by Side

While both methods sort data, they target different data structures and have subtle differences. The diagram below visualizes what happens under the hood for each method, and the table that follows provides a feature-by-feature comparison.

Under the Hood: Arrays.sort vs Collections.sortArrays.sort()Collections.sort()Input: int[] or String[]421738591Dual-Pivot Quicksort (primitives)TimSort (objects)Sorted in place:517384291Input: ArrayList<String>"cat""ant""dog""bee""eel"Step 1: list → Object[] array(list.toArray())Step 2: TimSort the arrayStep 3: write sorted array → listSorted in place:"ant""bee""cat""dog""eel"
Left: Arrays.sort directly sorts the array in place using Dual-Pivot Quicksort (for primitives) or TimSort (for objects). Right: Collections.sort first converts the list to an array, applies TimSort, then copies sorted elements back into the list.
FeatureArrays.sortCollections.sort
Works onArrays (int[], String[], etc.)Lists (ArrayList, LinkedList)
Primitives?Yes — int[], double[], char[], etc.No — Lists cannot hold primitives directly; use wrapper classes (Integer, Double)
Algorithm (objects)TimSort — O(n log n)TimSort — O(n log n)
Algorithm (primitives)Dual-Pivot Quicksort — O(n log n) averageN/A
Stable?Objects: yes. Primitives: not guaranteed.Yes (always TimSort)
Comparator supportYes (for object arrays only)Yes
Import neededimport java.util.Arrays;import java.util.Collections;

Feature comparison of Arrays.sort and Collections.sort

SECTION 6

Worked Example — Sorting Student Names

Let's walk through a complete example. Imagine you have an ArrayList of student names and you want to sort them first alphabetically (natural order), and then by name length (custom order using a comparator).

Sorting an ArrayList Two Ways

  1. 1

    Step 1 — Create the List

    We start by creating an ArrayList and adding five student names: ArrayList<String> names = new ArrayList<>(); names.add("Zara"); names.add("Bo"); names.add("Michaela"); names.add("Lee"); names.add("Alejandro"); At this point, the list order is: [Zara, Bo, Michaela, Lee, Alejandro].
    List: [Zara, Bo, Michaela, Lee, Alejandro]
  2. 2

    Step 2 — Sort Alphabetically (Natural Order)

    Calling Collections.sort(names) uses String's natural ordering, which is alphabetical (case-sensitive, uppercase before lowercase). Since all our names start with uppercase letters followed by lowercase, this works as expected.
    Sorted: [Alejandro, Bo, Lee, Michaela, Zara]
  3. 3

    Step 3 — Define a Length Comparator

    Now we want to sort by name length (shortest first). We write a lambda comparator: Comparator<String> byLength = (a, b) -> a.length() - b.length(); When a is shorter than b, a.length() - b.length() returns a negative number, meaning a comes first. That's exactly what we want.
    Comparator defined: shortest name first
  4. 4

    Step 4 — Sort with the Comparator

    We pass the comparator as the second argument: Collections.sort(names, byLength); The sort algorithm now uses our byLength comparator instead of alphabetical order. Names with 2 letters come before names with 3 letters, and so on.
    Sorted by length: [Bo, Lee, Zara, Michaela, Alejandro]
  5. 5

    Step 5 — Reverse the Custom Order

    To sort longest first, we reverse the comparator: Collections.sort(names, byLength.reversed()); The .reversed() method returns a new comparator that flips the ordering.
    Final result: [Alejandro, Michaela, Zara, Lee, Bo]
⚠

Important Note

Be careful with the subtraction trick a.length() - b.length() when comparing large integers — it can overflow! For lengths of strings this is safe, but for comparing arbitrary int values, prefer Integer.compare(a, b) instead.

SECTION 7

Strengths, Limitations & When to Use Each

Both sorting methods are powerful, but each has trade-offs depending on your situation. The table below summarizes when you'd reach for one versus the other, and when a comparator is necessary.

ScenarioBest ChoiceWhy
Sorting an int[] ascendingArrays.sort(arr)Primitives can only be sorted with Arrays.sort. Fast Dual-Pivot Quicksort is used.
Sorting an ArrayList<String>Collections.sort(list)Lists require Collections.sort (or list.sort()). Natural ordering gives alphabetical.
Sorting an int[] descendingSort ascending, then reverse manually (or use Integer[])Arrays.sort on primitives doesn't accept a Comparator. You must use a wrapper type or reverse after sorting.
Sorting objects by a custom fieldEither method + ComparatorPass a Comparator lambda to control which field(s) determine order.
Need stable sort (equal items keep original order)Either method with objectsTimSort (used for objects) is stable. Primitive sort is not guaranteed stable.

Choosing between Arrays.sort and Collections.sort

✦

KEY TAKEAWAY

Think of it like choosing a tool for a job. Arrays.sort is like a power drill — fast and direct, designed for raw materials (arrays). Collections.sort is like a drill with interchangeable heads — it works through an extra layer (the List interface) but is just as effective. A Comparator is the drill bit you swap in to change what kind of hole you're making — it doesn't change the drill, just the result.
⚠

Limitation: Primitive Arrays & Comparators

A common gotcha: you cannot pass a Comparator to Arrays.sort(int[]). Java doesn't support generics with primitive types, so to sort an int array in a custom order you must first convert it to an Integer[] or use an ArrayList<Integer>.

SECTION 8

Connection to Advanced Sorting Concepts

The sorting tools you've learned are your first step into a much larger landscape. As you advance in computer science, you'll encounter situations where built-in sort methods aren't enough — or where understanding the underlying algorithms becomes critical for performance analysis.

What You Know NowWhat Comes Next
Arrays.sort / Collections.sortImplementing your own sorting algorithms (merge sort, quicksort, heap sort) to understand time and space complexity trade-offs.
Comparator with lambda expressionsChaining comparators with .thenComparing() for multi-level sorts (e.g., sort by last name, then by first name).
O(n log n) average performanceBig-O analysis of best, average, and worst cases. Understanding why O(n log n) is the theoretical lower bound for comparison-based sorting.
Stable vs. unstable sortRadix sort and counting sort — non-comparison algorithms that can beat O(n log n) for specific data types.
Sorting lists and arraysUsing Stream.sorted() for functional-style sorting that returns new collections without modifying originals.

From introductory sorting to advanced concepts

As you move into AP Computer Science or college-level courses, you'll be expected to analyze sorting algorithms in terms of time complexity and space complexity. For now, the important takeaway is that Java's built-in sort methods use highly optimized O(n log n) algorithms, and you should always prefer them over writing your own sort — unless an assignment specifically asks you to implement one.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

What is the difference between Comparable and Comparator? When would you use each one?
Show answer
Comparable is implemented by the class itself, defining its natural ordering through a compareTo() method. For example, the String class implements Comparable to provide alphabetical ordering. Comparator is a separate object you create when you need a custom ordering that differs from the natural one. Use Comparable when there's one obvious way to order objects (like numbers ascending). Use Comparator when you need flexibility — sorting students by grade one time and by name another.

Question 2: BASIC CALCULATION

Given the array int[] nums = {8, 3, 12, 1, 5};, write one line of code to sort it in ascending order. What will the array contain after sorting?
Show answer
The single line is: Arrays.sort(nums);. After this call, the array is modified in place and contains {1, 3, 5, 8, 12}. Since nums is a primitive int array, Java uses Dual-Pivot Quicksort with O(n log n) average performance. No Comparator is needed because ascending order is the default for primitives.

Question 3: INTERMEDIATE

You have ArrayList<String> fruits = new ArrayList<>(Arrays.asList("banana", "fig", "apple", "kiwi", "elderberry"));. Write code to sort this list by string length (shortest first). If two fruits have the same length, they should remain in their original relative order.
Show answer
Use: Collections.sort(fruits, (a, b) -> a.length() - b.length()); or equivalently Collections.sort(fruits, Comparator.comparingInt(String::length));. The result is [fig, kiwi, apple, banana, elderberry]. The requirement that same-length items stay in their original order is automatically satisfied because Collections.sort uses TimSort, which is a stable sort — equal elements maintain their relative positions from the input.

Question 4: APPLIED

A teacher stores student scores in Integer[] scores = {88, 72, 95, 88, 65};. She wants to display them from highest to lowest. Write the code. Then explain: why do we need Integer[] instead of int[] to use a Comparator?
Show answer
The code is: Arrays.sort(scores, Comparator.reverseOrder());. After sorting, scores contains {95, 88, 88, 72, 65}. We need Integer[] (the wrapper class) instead of int[] because Java generics — which Comparator relies on — cannot work with primitive types. The Comparator<T> interface requires T to be an object type. If you tried Arrays.sort(intArray, Comparator.reverseOrder()) with a primitive int[], the compiler would give an error.

Question 5: CRITICAL THINKING

Consider a list of words: ["hi", "zoo", "an", "be"]. You sort it with Collections.sort(words) (natural order) and then separately sort the original list with a length-based comparator. The natural-order result is [an, be, hi, zoo]. The length-based result is [hi, an, be, zoo]. Both "hi", "an", and "be" have length 2. Explain why the two-letter words appear in different orders in each result, and connect this to the concept of stability.
Show answer
In the natural (alphabetical) sort, "an" comes before "be" which comes before "hi" because 'a' < 'b' < 'h' in Unicode order. The algorithm actively reorders these based on their characters. In the length-based sort, all three two-letter words are "equal" according to the comparator (they all return 0 when compared). Because TimSort is stable, equal elements keep their original order from the input list. The original order was [hi, zoo, an, be], so among the two-letter words, "hi" stays first, then "an", then "be". This demonstrates that stability preserves the input sequence for ties. If the sort were unstable, the two-letter words could appear in any order.

SUMMARY

Lesson Summary

Summary

Java provides two primary sorting tools: Arrays.sort for arrays and Collections.sort for Lists. Both sort data in place using efficient O(n log n) algorithms. For primitive arrays, Java uses Dual-Pivot Quicksort; for objects (in both methods), it uses TimSort, which is a stable sorting algorithm.

By default, elements are sorted in natural order (ascending for numbers, alphabetical for strings). To customize the sort, you supply a Comparator — a separate object whose compare(a, b) method returns a negative, zero, or positive integer to define order. Lambda expressions (Java 8+) make comparators concise: (a, b) -> a.length() - b.length(). Remember that primitive arrays cannot use Comparators — you must use wrapper types like Integer[] for custom ordering of numeric data.

Varsity Tutors • Java • Arrays.sort & Collections.sort — Use Arrays.sort and Collections.sort; compare comparators (intro)