Skip to content

Java · Modern Java

Java Sequenced Collections — getFirst, getLast, and reversed() for Every Ordered Type

8 min read Updated 2026-06-20 Share:

Practice Sequenced Collections interview questions

The problem: no uniform first/last API

Before Java 21, every ordered collection type had its own non-uniform way to access the first or last element:

// List:
String first = list.get(0);
String last  = list.get(list.size() - 1);   // verbose, off-by-one risk

// Deque (ArrayDeque):
String first = deque.peekFirst();
String last  = deque.peekLast();

// SortedSet (TreeSet):
String first = sortedSet.first();
String last  = sortedSet.last();

// No collection had a unified reverse-iteration API without mutations or ugly iterators

This inconsistency forced every "get the last element" line to be written differently depending on which collection type was in scope — a genuine source of bugs and cognitive overhead.

Sequenced collections (Java 21, JEP 431) fix this with three new interfaces that provide a uniform getFirst() / getLast() / reversed() API retrofitted onto the existing hierarchy.

The three new interfaces

SequencedCollection<E>

Extends Collection<E>. Represents any collection with a defined encounter order and adds:

MethodBehaviour
getFirst()First element; NoSuchElementException if empty
getLast()Last element; NoSuchElementException if empty
addFirst(E)Insert at beginning (optional — UOE for fixed-size)
addLast(E)Insert at end (optional)
removeFirst()Remove and return first (optional)
removeLast()Remove and return last (optional)
reversed()Live reversed-order view
List<String> list = new ArrayList<>(List.of("a", "b", "c"));

list.getFirst();    // "a"
list.getLast();     // "c"

list.addFirst("z"); // ["z", "a", "b", "c"]
list.addLast("x");  // ["z", "a", "b", "c", "x"]

list.removeFirst(); // "z" — list is now ["a", "b", "c", "x"]
list.removeLast();  // "x" — list is now ["a", "b", "c"]

SequencedSet<E>

Extends both SequencedCollection<E> and Set<E>. No new methods, but guarantees set semantics (no duplicates) on an ordered collection. Implemented by LinkedHashSet and TreeSet (via SortedSet).

SequencedSet<String> set = new LinkedHashSet<>(List.of("banana", "apple", "cherry"));
set.getFirst(); // "banana" — insertion order
set.getLast();  // "cherry"

// Reversed view:
set.reversed().forEach(System.out::println); // cherry, apple, banana

SequencedMap<K, V>

Extends Map<K,V> and adds:

MethodBehaviour
firstEntry()First Map.Entry (no removal)
lastEntry()Last Map.Entry (no removal)
pollFirstEntry()Remove and return first entry
pollLastEntry()Remove and return last entry
putFirst(K, V)Insert/move to front (optional)
putLast(K, V)Insert/move to back (optional)
reversed()Live reversed map view
sequencedKeySet()SequencedSet<K>
sequencedValues()SequencedCollection<V>
sequencedEntrySet()SequencedSet<Map.Entry<K,V>>
SequencedMap<String, Integer> scores = new LinkedHashMap<>();
scores.put("Alice", 90);
scores.put("Bob",   85);
scores.put("Carol", 95);

scores.firstEntry(); // Alice=90
scores.lastEntry();  // Carol=95

scores.putFirst("Zara", 100); // Zara is now the first entry

Which existing collections implement the new interfaces

The new interfaces are retroactively wired into the existing collection hierarchy — you don't need to change your existing code to get the methods:

Concrete typeImplements
ArrayList, LinkedList, ArrayDequeSequencedCollection
LinkedHashSetSequencedSet
TreeSetSequencedSet (via SortedSet → SequencedSet)
LinkedHashMapSequencedMap
TreeMapSequencedMap (via SortedMap → SequencedMap)

Interface hierarchy additions:

  • List extends SequencedCollection
  • Deque extends SequencedCollection
  • SortedSet extends SequencedSet
  • SortedMap extends SequencedMap

So List.of(), Collections.unmodifiableList(), and Arrays.asList() all have getFirst()/getLast() now — they're List instances and List is a SequencedCollection.

The reversed() method — a live view

reversed() returns a live, write-through view of the collection in reverse order:

List<Integer> list = new ArrayList<>(List.of(1, 2, 3, 4, 5));
List<Integer> rev  = list.reversed();

System.out.println(rev);    // [5, 4, 3, 2, 1]

list.add(6);                // mutate original
System.out.println(rev);    // [6, 5, 4, 3, 2, 1] — reflected in view

rev.addFirst(99);           // add to reversed view's front (= original's back)
System.out.println(list);   // [1, 2, 3, 4, 5, 6, 99]

Because it's a live view, mutations through either handle affect both. If you need an independent reversed copy, call new ArrayList<>(list.reversed()).

Practical use — iterating in reverse without mutations

Before Java 21, reversing iteration required either mutating the list (Collections.reverse()) or using a backwards ListIterator:

// Old — mutates the list:
Collections.reverse(list);
list.forEach(System.out::println);
Collections.reverse(list); // put it back

// Old — verbose iterator:
var it = list.listIterator(list.size());
while (it.hasPrevious()) System.out.println(it.previous());

// Java 21 — clean:
list.reversed().forEach(System.out::println);  // no mutation

// Or with streams:
list.reversed().stream()
    .filter(x -> x > 2)
    .forEach(System.out::println);

getFirst() vs get(0) — what's the difference?

For List, getFirst() and get(0) return the same value but throw different exceptions on an empty list:

List<String> empty = new ArrayList<>();

empty.get(0);      // IndexOutOfBoundsException: Index: 0, Size: 0
empty.getFirst();  // NoSuchElementException

NoSuchElementException better communicates "there is no first element" versus IndexOutOfBoundsException which sounds like a bug in index arithmetic. Prefer getFirst() for clarity, especially in generic code that receives a SequencedCollection and doesn't know if it's a List.

Immutable and unmodifiable collections

Read-only methods (getFirst, getLast, firstEntry, lastEntry) work on any SequencedCollection including immutable ones:

List<String> fixed = List.of("x", "y", "z");
fixed.getFirst(); // "x" — fine
fixed.getLast();  // "z" — fine
fixed.addFirst("a"); // UnsupportedOperationException — immutable

Mutation methods (addFirst, removeFirst, putFirst, etc.) respect the collection's modifiability contract and throw UnsupportedOperationException for unmodifiable/ immutable collections, exactly as add() and remove() already do.

SequencedCollection in method signatures

Use SequencedCollection<E> in a method signature when your code specifically needs first/last access but doesn't need random index access (get(i)):

// Good — accepts ArrayList, LinkedList, ArrayDeque, or any ordered collection:
static <T> T penultimate(SequencedCollection<T> col) {
    if (col.size() < 2) throw new NoSuchElementException();
    return col.reversed().stream().skip(1).findFirst().orElseThrow();
}

// Too narrow — only accepts List:
static <T> T penultimate(List<T> list) { ... }

// Too broad — Collection doesn't guarantee order:
static <T> T penultimate(Collection<T> col) { ... } // getFirst() unavailable

SequencedCollection is the right abstraction when you care about order and first/last but not about array-like index access.

Replacing Deque methods

Deque already had peekFirst()/peekLast() and addFirst()/addLast(). Deque now extends SequencedCollection, so ArrayDeque has all the new methods too. The SequencedCollection equivalents are cleaner for non-queue uses:

Old Deque APINew SequencedCollection API
deque.peekFirst()deque.getFirst()
deque.peekLast()deque.getLast()
deque.pollFirst()deque.removeFirst()
deque.pollLast()deque.removeLast()

Note: peekFirst() returns null for an empty deque; getFirst() throws NoSuchElementException. Choose based on whether null vs exception is more appropriate.

Before and after — the complete comparison

// BEFORE Java 21:
List<String>   list   = new ArrayList<>(List.of("a", "b", "c"));
Deque<String>  deque  = new ArrayDeque<>(list);
TreeSet<String> sorted = new TreeSet<>(list);

String fl = list.get(0);              // first from List
String ll = list.get(list.size()-1);  // last from List
String fd = deque.peekFirst();        // first from Deque
String ld = deque.peekLast();         // last from Deque
String fs = sorted.first();           // first from SortedSet
String ls = sorted.last();            // last from SortedSet

// AFTER Java 21 — uniform API:
list.getFirst();   // "a"
list.getLast();    // "c"
deque.getFirst();  // "a"
deque.getLast();   // "c"
sorted.getFirst(); // "a"
sorted.getLast();  // "c"

// Reverse without mutation:
list.reversed().forEach(System.out::println);   // c, b, a
deque.reversed().forEach(System.out::println);  // c, b, a
sorted.reversed().forEach(System.out::println); // c, b, a

Recap

Sequenced collections (Java 21) add three interfaces — SequencedCollection, SequencedSet, and SequencedMap — that provide a uniform API for first/last access and reverse iteration across all ordered collections. getFirst()/getLast() replace get(0) / get(size-1) with clearer semantics and a better exception type. addFirst()/addLast()/removeFirst()/removeLast() mirror the Deque API on all List implementations. reversed() returns a live write-through view — not a copy — that reflects mutations in both directions. The interfaces are retrofitted onto ArrayList, LinkedHashSet, TreeSet, LinkedHashMap, TreeMap, and their parent interfaces, so you get the new methods without any code changes.

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel