AlgoMaster Logo

Collections Utility Class

Medium Priority17 min readUpdated June 6, 2026
Listen to this chapter
Unlock Audio

java.util.Collections is a class full of static helper methods that act on the collections you already have. It doesn't define a new collection type. Instead, it gives you ready-made operations like sorting a product list, finding the cheapest item, shuffling recommendations, or grabbing a single-element list, so you don't have to write that code yourself. This lesson walks through the methods you'll actually use day to day, grouped by what they do.

Why a Utility Class Exists

A List, Set, or Map only knows how to do its own job. An ArrayList knows how to add, remove, and look up by index. It doesn't know how to sort itself in reverse order, find the minimum element, or be shuffled into a random order. Those operations are general; they work the same way for any list. So instead of repeating that logic on every collection class, the JDK packages it once into java.util.Collections as static methods that take a collection as the first argument.

The mental model is "free functions on top of collections". If you have a List<Product> and want it sorted, call Collections.sort(products). The list mutates in place. Same for shuffling, reversing, filling, and so on. For methods like min, max, and frequency, the call returns a value without modifying the collection.

Here's a map of the categories:

The diagram groups the methods by what they do to the collection: read it, mutate it, wrap it, or return a fixed value. We'll work through each group with concrete e-commerce examples.

A note on naming. The class java.util.Collections (plural, with an s) is the utility class. The interface java.util.Collection (singular) is the supertype of List, Set, and Queue. They sound almost identical, but they're different things. Throughout this lesson, Collections.sort(...) means the utility method on the plural class.

Sorting

Collections.sort puts a list in order. The simplest form takes a list of elements that already know how to compare themselves, like String, Integer, or any type that implements Comparable. The sort happens in place; the list is mutated.

The list started in the order the items were added and ended in ascending price order. The sort is stable, meaning equal elements keep their original relative order, and it runs in O(n log n) time.

For anything other than the natural order (price ascending, alphabetical, smallest-first), pass a Comparator as the second argument. Here we'll just use the two built-ins from Collections itself.

Collections.reverseOrder() returns a comparator that reverses the natural ordering. Pair it with sort to get highest-first, biggest-first, or Z-to-A:

Same data, opposite order. No custom comparator needed.

Collections.sort is O(n log n). It also allocates a temporary array internally. If you're sorting a tiny list inside a hot loop, that allocation can add up. For a one-shot sort on a few thousand items, it's not worth worrying about.

Searching with binarySearch

Collections.binarySearch looks up an element in a sorted list in O(log n) time. The catch, and it's a big one, is that the list must already be sorted. If it isn't, the result is undefined: you might get a wrong index, a negative number that means nothing, or what looks like a correct answer purely by accident.

The list is alphabetical, and "Pen" lives at index 2. The search divides the list in half each step, so even with a million products it would finish in roughly 20 comparisons.

When the element isn't present, binarySearch returns a negative number. It's not just -1. The return value is -(insertion_point) - 1, where insertion_point is where the element would go to keep the list sorted.

"Marker" isn't in the list. If it were, it would slot in at index 1 (between "Eraser" and "Notebook"). The return value -2 is the encoded version of "would go at index 1". You rarely need the encoded form directly, but the contract matters because a -1 return doesn't mean "not found at index 0"; it means "would insert at index 0".

The "must be sorted" rule is a common pitfall. Here's what goes wrong on an unsorted list:

"Pen" is actually in the list at index 0, but binarySearch returns -1. The list isn't sorted, so the algorithm's halving logic walks in the wrong direction and never finds it. The method didn't lie; it just operates under the assumption that the input is sorted, and the assumption was broken. If the input is unsorted and you don't know it, use indexOf, which is O(n) but doesn't care about order.

binarySearch on a LinkedList is O(n log n), not O(log n), because each midpoint lookup has to walk from the head. For binary search on lists with more than a few hundred items, use ArrayList.

Min, Max, and Frequency

When you want the smallest or largest element of a collection, Collections.min and Collections.max save you the loop. They work on anything that implements Collection, including List, Set, and Queue. The elements need to be comparable to each other, just like with sort.

The list isn't sorted, and it doesn't need to be. min and max walk the collection once in O(n) and return the smallest or largest element they find. Both throw NoSuchElementException if the collection is empty, which is a reasonable signal that asking for the cheapest item in an empty cart doesn't have a sensible answer.

Collections.frequency(collection, target) counts how many times an element appears in a collection. It's handy when you want a quick tally without setting up a Map<Element, Integer>.

frequency uses equals to compare, so it works for any type whose equals method is implemented correctly. It's O(n) because it has to scan the whole collection.

Collections.disjoint(c1, c2) returns true if the two collections have no elements in common. It's the inverse of "do these overlap?" without writing a nested loop.

The two lists share no elements, so disjoint returns true. If even one element matches, the return value flips to false.

Mutating Methods: reverse, shuffle, swap, fill, copy, rotate, addAll

This group changes the list in place. They're useful when you want to rearrange, fill, or copy data without writing the loop yourself.

Collections.reverse flips the order. Last becomes first, first becomes last.

Reversing is O(n) and swaps elements pairwise in place. A common use is showing order history with the most recent order on top.

Collections.shuffle puts the list in a random order. Use it for shuffling product recommendations on a homepage so every visitor sees a slightly different layout.

Output (varies between runs):

The output changes every time you run it because shuffle uses a default random source. For reproducible shuffles in tests, pass a Random with a fixed seed as the second argument: Collections.shuffle(list, new Random(42)).

Collections.swap(list, i, j) swaps two elements by index. Saves the temp-variable dance.

Collections.fill(list, value) replaces every element with the given value. Use it to reset a list to a default state without recreating it.

Collections.copy(dest, src) copies elements from src into dest, position by position. The destination must already be at least as long as the source, or you get IndexOutOfBoundsException. The destination's size doesn't grow; only the existing slots are overwritten.

Collections.rotate(list, distance) rotates the elements by the given amount. Positive distance moves elements toward the end; negative moves them toward the front. The list wraps around.

Each element moved one step forward, and the last element wrapped around to the front.

Collections.addAll(collection, elements...) appends one or more elements to a collection in a single call. It accepts varargs, so you can pass them inline.

It's a shortcut for the loop you'd otherwise write. For adding the contents of another collection, the collection's own addAll(Collection) method works too; Collections.addAll is specifically the varargs form.

Singletons, Empty Collections, and nCopies

Sometimes you need a collection that contains exactly one element, or no elements at all, or the same element repeated n times. The factories on Collections produce these without going through new ArrayList<>() and a series of add calls.

Collections.singletonList(element) returns an immutable list with exactly one element. It's lighter than a one-element ArrayList and signals intent: this list will never have anything added to or removed from it.

The list is immutable. Calling defaultCart.add("Pen") throws UnsupportedOperationException. The related methods Collections.singleton(element) (a Set) and Collections.singletonMap(key, value) (a Map) work the same way for those types.

Collections.emptyList(), Collections.emptySet(), and Collections.emptyMap() return immutable empty collections. They're useful as default return values from methods that didn't find anything, instead of returning null.

The caller can now loop, check .size(), or pass the result on without worrying about a null check. Each emptyXxx factory returns the same shared instance every time, so there's no allocation cost.

Collections.nCopies(n, element) returns an immutable list with the same element repeated n times. Use it when you need a placeholder list of a known length.

The list looks like five separate slots, but internally it stores the element once and reports the same value for every index. That makes nCopies cheap regardless of n, but it also means the list is immutable.

Wrapper Methods (Brief Tour)

Collections also offers wrappers that change the behavior of a collection without copying its data. Two families are worth flagging here:

  • Collections.synchronizedList(list), synchronizedSet(set), and synchronizedMap(map) return a thread-safe view that synchronizes every method on a single lock.
  • Collections.unmodifiableList(list), unmodifiableSet(set), and unmodifiableMap(map) return a read-only view that throws UnsupportedOperationException on any modification attempt.

A Reference Table of the Most-Used Methods

Keep this table handy when you're reaching for a method and can't remember the exact name or shape:

MethodWhat it doesTime complexityNotes
sort(list)Sorts a list in natural orderO(n log n)Mutates in place; stable
sort(list, comparator)Sorts using a custom comparatorO(n log n)Mutates in place
reverseOrder()Returns a comparator for descending orderO(1)Use with sort
binarySearch(list, key)Finds element index in a sorted listO(log n) on ArrayListNegative return means not found
min(collection)Returns smallest elementO(n)Throws if empty
max(collection)Returns largest elementO(n)Throws if empty
frequency(c, target)Counts occurrences of an elementO(n)Uses equals
disjoint(c1, c2)True if no elements in commonO(n)
reverse(list)Reverses element order in placeO(n)
shuffle(list)Randomly permutes the listO(n)Pass Random for reproducibility
swap(list, i, j)Swaps elements at two indexesO(1)
fill(list, value)Sets every element to valueO(n)
copy(dest, src)Copies src into dest position by positionO(n)dest must be sized
rotate(list, distance)Rotates elements by distanceO(n)Negative = toward front
addAll(c, e1, e2, ...)Appends elements via varargsO(n)
singletonList(e)Immutable list of one elementO(1)
emptyList(), emptySet(), emptyMap()Immutable empty collectionO(1)Shared instance
nCopies(n, e)Immutable list of n copies of eO(1)

The general rule is "don't reinvent these". If you find yourself writing a loop to find the maximum price, count occurrences, or flip a list end-to-end, check this table first. The utility method is almost always clearer, shorter, and at least as fast.

Quiz

Collections Utility Class Quiz

10 quizzes