Get last element of Stream/List in a one-liner
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Getting the last element is trivial for a list and less natural for a stream. That difference is not accidental. A list has indexed storage, so “last element” is a cheap direct lookup. A stream is a traversal abstraction, so finding the last element usually means consuming the whole stream.
For a List, Indexing Is the Right Answer
If you already have a List, the most direct one-liner is still the best one.
That is clear, fast, and idiomatic. It also makes the data structure assumption explicit: you have random access to an ordered collection.
If the list may be empty, wrap the access safely.
Or, if you prefer Optional:
The important point is that the one-liner should not hide the empty-list case if emptiness is possible.
For a Stream, reduce Is the Typical One-Liner
Streams do not support random access, so the common one-liner is a reduction that keeps replacing the previous value with the current one.
This works because the accumulator always returns the newer element, so after the stream is consumed, only the final element remains.
It is concise, but it is not magic. It still walks through the entire stream.
Do Not Force Streams When You Already Have a List
A lot of code becomes less readable because developers insist on a “stream one-liner” even when the input is already a list.
That works, but it is usually worse than list.get(list.size() - 1). You pay for a full traversal and lose the clarity of direct indexing.
The broader lesson is that streams are not automatically more elegant than collection operations. Choose the abstraction that matches the job.
Infinite and Parallel Streams Need Extra Caution
A request for the “last element” only makes sense when the stream has a real end. An infinite stream can never produce a last element. A parallel stream can also make “last” conceptually awkward unless encounter order is preserved and you really mean the last element in that encounter order.
So reduce((a, b) -> b) is fine for finite ordered streams, but the idea does not generalize universally.
Sometimes the Better Answer Is to Change the Data Structure
If your program frequently needs the last element, a plain List or Deque is usually a better model than repeatedly converting into streams.
That may be more expressive than either stream reduction or index math, depending on the broader usage pattern.
One-Liners Are Fine, but Semantics Matter More
The obsession with “one-liner” solutions can hide the real design question. The important part is not packing the code into one line. The important part is whether the code is correct for empty input, clear about ordering, and appropriate for the underlying data structure.
So the best answer is usually:
- list: direct index access,
- stream:
reduce((a, b) -> b), - and if this happens a lot, reconsider the data structure.
Common Pitfalls
- Using a stream reduction on a list when direct indexing is simpler and faster.
- Forgetting to handle empty collections.
- Asking for the last element of a stream that may be infinite.
- Assuming “stream one-liner” is always more idiomatic than collection access.
- Ignoring encounter order when using streams and still talking about the “last” element.
Summary
- For a
List, uselist.get(list.size() - 1)when the list is non-empty. - For a finite ordered
Stream, usestream.reduce((a, b) -> b). - Handle empty input explicitly if it is possible.
- Do not force streams when the underlying collection already gives direct last-element access.
- If the program often needs front and back access, consider a data structure such as
Deque.

