The Collections Framework Map
The interface hierarchy and what each contract promises, why some methods throw UnsupportedOperationException by design, and the differences between Arrays.asList, List.of and List.copyOf.
On this page
Knowing which collection to reach for is a daily decision, and interviewers use it as a proxy for whether you understand the trade-offs underneath. This page is the map of the framework and the contracts each interface promises; the pages that follow open up the implementations.
Key Takeaways
Collectionis the root forList,SetandQueue.Mapis deliberately outside it.- Interfaces define contracts; some operations are optional, which is why
UnsupportedOperationExceptionexists in a compiled language. Arrays.asListis a fixed-size view,List.ofis immutable and null-hostile,List.copyOfis an immutable snapshot. All three behave differently.Map's three views —keySet,values,entrySet— are live windows, not copies.- Choose by the operation you perform most: lookup by key, ordered iteration, index access, or first/last removal.
The hierarchy
Each interface adds one promise to the one above it:
Iterable promises only that you can walk it once, in some order, with a for-each loop.
Collection adds size, membership testing, and bulk operations — add, remove, contains,
stream. It says nothing about order or duplicates.
List adds positional access and permits duplicates. Its contract is an ordered sequence with
an index, which is what makes get(i), add(i, e) and indexOf meaningful.
Set forbids duplicates, defined by equals. It adds no methods at all — the entire difference
from Collection is a documented behavioural constraint, which is a nice illustration that a Java
interface contract is more than its method list.
Queue adds an intended ordering for insertion and removal, usually FIFO. Deque allows both
ends. SortedSet and NavigableSet add range queries: first, last, headSet, ceiling,
floor.
Map is a separate hierarchy of key-to-value associations. SortedMap and NavigableMap add
the same range operations as their set counterparts.
LinkedList is the odd one out: it implements both List and Deque, which is why it appears in
answers to questions about both. In practice ArrayDeque is the better deque and ArrayList the
better list, so LinkedList is rarely the right choice for either job.
Optional operations
Java's collection interfaces are older than default methods, and they solve the "not every implementation supports every method" problem with documentation plus a runtime exception rather than a finer-grained type hierarchy.
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> fixed = Arrays.asList("a", "b");
List<String> immutable = List.of("a", "b");
mutable.set(0, "z"); // ok
fixed.set(0, "z"); // ok — writes through to the backing array
immutable.set(0, "z"); // UnsupportedOperationException
mutable.add("c"); // ok
fixed.add("c"); // UnsupportedOperationException — cannot resize an array
immutable.add("c"); // UnsupportedOperationExceptionThis design is widely criticised, and knowing why it exists is a better interview answer than
agreeing it is bad: splitting the hierarchy into mutable and immutable interfaces would have doubled
the number of types and forced every method that accepts a List to choose. The JDK traded compile-
time safety for a simpler API surface, and documented the cost.
The practical consequence is that the type of a parameter tells you nothing about whether you may
mutate it. A method that takes a List and calls add on it works with the caller's ArrayList
and throws with the caller's List.of. Either copy defensively or document the requirement.
The three factory methods
| Factory | Mutable? | Nulls? | Backed by |
|---|---|---|---|
new ArrayList<>(...) | Fully | Yes | Its own array |
Arrays.asList(a, b) | set only | Yes | The caller's array |
List.of(a, b) | No | Throws | Its own compact storage |
List.copyOf(other) | No | Throws | A snapshot |
Collections.unmodifiableList(l) | No | Yes | A live view of l |
Three of these have caught people out often enough to be interview staples.
Arrays.asList on a primitive array does not do what it looks like: Arrays.asList(new int[]{1,2,3})
produces a single-element List<int[]>, because int[] is one object rather than three Integers.
Use Arrays.stream(arr).boxed().toList().
List.of rejects null, both on construction and in contains(null). Code migrating from
Arrays.asList to List.of for immutability sometimes starts throwing NullPointerException from a
contains check that used to return false.
Collections.unmodifiableList returns a view. Mutating the underlying list changes what the view
shows. It prevents the recipient from mutating; it does not give you a snapshot. That distinction is
covered in Immutable objects and defensive copying.
Map views are live
Map<String, Integer> scores = new HashMap<>(Map.of("a", 1, "b", 2, "c", 3));
Set<String> keys = scores.keySet();
keys.remove("a"); // removes the ENTRY from scores
scores.size(); // 2
scores.values().removeIf(v -> v > 2); // removes entries whose value is > 2
scores.size(); // 1
for (Map.Entry<String, Integer> e : scores.entrySet()) {
e.setValue(e.getValue() * 10); // writes through to the map
}keySet(), values() and entrySet() are backed by the map, so removal propagates and
Entry.setValue updates it. You cannot add through them — there would be no value to associate —
and iterating one while structurally modifying the map throws ConcurrentModificationException, as
covered in Fail-fast versus fail-safe iterators.
Iterating entrySet() is also the correct way to walk a map. Iterating keySet() and calling
get(key) inside the loop performs a second hash lookup per entry, which is a small but pointless
cost and a detail interviewers notice.
Choosing one
The decision is almost always driven by the operation you perform most often:
- Lookup by key —
HashMap. AddLinkedHashMapif you need insertion or access order,TreeMapif you need sorted keys or range queries. - Index access, iteration, append —
ArrayList. Effectively always, for the reasons in ArrayList vs LinkedList. - Membership testing —
HashSet.LinkedHashSetwhen iteration order must be stable,TreeSetwhen you need sorted order orceiling/floor. - Add and remove at both ends —
ArrayDeque. It is faster thanLinkedListand faster thanStack, which is a synchronised Java 1.0 class nobody should still be using. - Producer-consumer between threads — a
BlockingQueue, always bounded. - Shared across threads —
ConcurrentHashMap,CopyOnWriteArrayListfor read-mostly, or a concurrent queue. Never aHashMapguarded by hope.
The follow-up worth preparing for is "and what if the collection is read far more often than it is
written?" — the answer being CopyOnWriteArrayList or an immutable collection replaced wholesale,
both of which trade write cost for lock-free reads.
What gets asked
Expect: draw the hierarchy; why Map is not a Collection; the difference between Collection and
Collections; and the Arrays.asList behaviour. The last one is the most common, and the complete
answer names all three states — mutable, fixed-size, immutable — rather than just saying "it is
immutable", which is wrong.
Frequently Asked Questions
Why is Map not a Collection?
Why does Arrays.asList(...).add(...) throw UnsupportedOperationException?
What is the difference between Collection and Collections?
Related tutorials
- ArrayList vs LinkedList: Internals and GrowthWhat each one stores in memory, the 1.5x growth and array copy, why LinkedList loses even at insertion in the middle, and the per-element overhead that makes cache locality decide the winner.
- HashMap Internals: Buckets, Resize and TreeificationHow HashMap stores entries, why the hash is XORed with its own high bits, what happens during a resize, when a bucket becomes a red-black tree, and the Java 7 race that caused infinite loops.
- ConcurrentHashMap vs Hashtable vs synchronizedMapHow ConcurrentHashMap achieves concurrency without a global lock, why segments disappeared in Java 8, the computeIfAbsent deadlock, and why size() is only an estimate.
- TreeMap, LinkedHashMap and Building an LRU CacheHow TreeMap uses a red-black tree for sorted keys and range queries, how LinkedHashMap adds a doubly-linked list for ordering, and building an LRU cache in ten lines with removeEldestEntry.