How to Reverse an ArrayList in Java
java arraylist reverse: Learn how to reverse an ArrayList in Java using Collections.reverse(), manual two-pointer swaps, backward iteration, and ListIterator traversal.
The java arraylist reverse operation is a common task when working with ordered collections. An ArrayList in Java maintains insertion order, and sometimes the data you receive is in the wrong order for your use case. The most direct answer is Collections.reverse(list), which reverses the order of elements in place. But the right approach depends on whether you can mutate the original list or need a reversed copy.
Using Collections.reverse() for In-Place Reversal
java.util.Collections.reverse() is the standard library method for reversing a List. It operates in place, meaning it modifies the original list rather than creating a new one.
import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; ArrayList<String> names = new ArrayList<>(Arrays.asList("ada", "grace", "linus")); Collections.reverse(names); System.out.println(names); // [linus, grace, ada]
The method accepts any List implementation, not just ArrayList. It swaps elements symmetrically: the first element moves to the last position, the second to the second-to-last, and so on. For an ArrayList, this is an O(n) operation where n is the list size, because element access and assignment are both O(1) for a random-access list.
Manual In-Place Reversal with Two Pointers
If you need to reverse without Collections, or you want to understand the underlying mechanism, a two-pointer swap achieves the same result:
public static <T> void reverseInPlace(ArrayList<T> list) { int left = 0; int right = list.size() - 1; while (left < right) { T temp = list.get(left); list.set(left, list.get(right)); list.set(right, temp); left++; right--; } }
This approach is functionally equivalent to Collections.reverse() for an ArrayList. The loop terminates when the pointers cross, which happens after roughly n/2 iterations. Each iteration performs two reads and two writes, so the total cost is O(n). Writing it manually is rarely necessary in production code, but it clarifies what the library method does internally and is useful in contexts where you want to avoid the Collections utility class.
Creating a Reversed Copy Without Mutating the Original
When the original list must remain unchanged, Collections.reverse() is not suitable because it mutates in place. You need to build a new list. One straightforward way is to copy the list and then reverse the copy:
ArrayList<String> original = new ArrayList<>(Arrays.asList("ada", "grace", "linus")); ArrayList<String> reversed = new ArrayList<>(original); Collections.reverse(reversed); // original is unchanged: [ada, grace, linus] // reversed: [linus, grace, ada]
The copy constructor performs a shallow copy, which is fine for immutable elements. If the list contains mutable objects, both lists reference the same objects; reversing the order does not clone the elements themselves.
An alternative that avoids the intermediate copy is to iterate backward and add each element to a new list:
ArrayList<String> reversed = new ArrayList<>(original.size()); for (int i = original.size() - 1; i >= 0; i--) { reversed.add(original.get(i)); }
Pre-sizing the destination with new ArrayList<>(original.size()) avoids incremental resizing during the loop. Both approaches produce the same result; the copy-then-reverse version reads more clearly, while the backward loop avoids the temporary mutation.
Using ListIterator for Backward Traversal
A ListIterator provides a way to traverse an ArrayList in reverse without relying on index arithmetic. This is useful when you need to process elements in reverse order as you iterate, rather than materializing a reversed list:
ArrayList<String> names = new ArrayList<>(Arrays.asList("ada", "grace", "linus")); ListIterator<String> iterator = names.listIterator(names.size()); while (iterator.hasPrevious()) { String name = iterator.previous(); System.out.println(name); // linus, grace, ada }
The listIterator(int index) method positions the iterator at the given index, and hasPrevious()/previous() move backward through the list. This approach does not modify the list and does not allocate a new collection. It is the right choice when the goal is reverse-order processing rather than a reversed data structure.
Performance and Memory Tradeoffs
The main performance distinction is between in-place reversal and copy-based reversal. In-place reversal via Collections.reverse() allocates no additional storage beyond a single temporary variable for each swap, and runs in O(n) time. Copy-based reversal allocates a second list of the same size, doubling the memory footprint for the duration of the operation, and also runs in O(n) time.
| Approach | Mutates original | New allocation | Time complexity | Best use case |
|---|---|---|---|---|
Collections.reverse() | Yes | No | O(n) | In-place reversal when mutation is acceptable |
| Two-pointer manual swap | Yes | No | O(n) | Avoiding the Collections utility or learning the mechanism |
| Copy then reverse | No | Yes (one list) | O(n) | Preserving the original while producing a reversed list |
| Backward loop with pre-sized list | No | Yes (one list) | O(n) | Building a reversed list without a temporary mutation |
ListIterator backward traversal | No | No | O(n) | Processing elements in reverse without building a new list |
For large lists, the allocation cost of the copy-based approaches is the dominant factor. If the list contains millions of elements, an in-place reversal avoids a large allocation, but it permanently changes the order of the original list. The decision should be driven by whether downstream code depends on the original order.
Edge Cases and Common Mistakes
An empty ArrayList is a valid input for all the approaches above. Collections.reverse() on an empty list is a no-op. The two-pointer loop never executes because left < right is false when the list is empty. The backward loop produces an empty list. The ListIterator positioned at index 0 has no previous element.
A list with a single element is also a no-op for reversal, since there is nothing to swap.
Null elements do not cause problems with any of these approaches, because reversal only moves references; it never dereferences the elements. A list containing null values reverses correctly.
A common mistake is calling Collections.reverse() on a list that is later expected to retain its original order. This is a subtle bug because the reversal succeeds silently, and the corruption of the original order may only surface in a later part of the program. If the original order matters, use a copy-based approach.
Another mistake is using Collections.reverse() on a list that is actually an unmodifiable view, such as Collections.unmodifiableList(...). The method will throw UnsupportedOperationException at runtime because the underlying list does not support set(). This failure is not caught at compile time, so it is worth verifying that the list is mutable before calling the method.
Choosing the Right Approach for Your Use Case
The decision between in-place and copy-based reversal comes down to whether the original list is still needed. If the list is a local variable used only for the current operation, Collections.reverse() is the simplest and most readable choice. If the list is shared with other parts of the application, or if it is a field that other methods read, a copy-based approach avoids introducing a subtle ordering bug.
For reverse-order processing without materializing a reversed list, the ListIterator approach is the most memory-efficient. It is particularly useful in streaming or batch scenarios where the reversed order is consumed once and then discarded.
The manual two-pointer implementation is rarely the best production choice, since Collections.reverse() is standard, tested, and immediately recognizable to other developers. Its main value is educational, or in codebases that deliberately avoid the Collections utility class for consistency reasons.