Java Collections interview questions, with answers
The Collections Framework is where Java interviews test data structures in the language you will actually use: which List, Set or Map to pick, what each costs, and what happens inside. The questions are practical — why a HashMap lookup is fast and when it is not, why ArrayList usually beats LinkedList, what a ConcurrentModificationException is telling you, and how to sort objects two different ways. Every answer below comes with code, and every output shown was produced by compiling and running it.
The questions start with the framework's shape, then HashMap's internals, then ordering, iteration and immutability. Then take the free Java diagnostic — ten questions across every Java topic in the bank — to see which of these you can explain but not yet predict.
The questions, with answers
1.What are the main interfaces of the Java Collections Framework?
In short: Collection branches into List (ordered, indexed, duplicates allowed), Set (no duplicates) and Queue or Deque (processing order); Map, for key-value pairs, is a separate hierarchy.
Know one standard implementation of each and when to pick it. List: ArrayList by default. Set: HashSet for fast membership, LinkedHashSet to keep insertion order, TreeSet for sorted order. Queue and Deque: ArrayDeque for stacks and FIFO queues, PriorityQueue for always taking the smallest item first. Map: HashMap by default, LinkedHashMap for insertion or access order, the basis of an LRU cache, and TreeMap for sorted keys and range queries such as floorKey. Map does not extend Collection, a common trick question, though its keySet(), values() and entrySet() views are collections. Collections, with an s, is a separate utility class of static methods such as sort and unmodifiableList.
var set = new TreeSet<>( List.of(5, 1, 3)); System.out.println(set); // [1, 3, 5] Deque<Integer> dq = new ArrayDeque<>(); dq.push(1); dq.push(2); System.out.println(dq.pop()); // 2
2.How does a HashMap work internally in Java?
In short: It hashes each key to choose one of an array of buckets, stores the entry there, and uses equals() to find the right key within a bucket, giving O(1) lookups on average.
put(k, v) computes k.hashCode(), mixes the high bits into the low ones, and masks the result with the table size, a power of two, to pick a bucket. Keys that land in the same bucket are chained. Since Java 8, a bucket that grows beyond eight entries is converted to a balanced tree once the table has at least 64 buckets, so a bad hash degrades a lookup to O(log n) rather than O(n). When the size passes capacity times the load factor, 0.75 by default, the table doubles and the entries are redistributed. get(k) repeats the hash and compares with equals() within the bucket, which is why the two strings below, which share a hash code, stay separate keys. One null key is allowed.
int h1 = "Aa".hashCode(); int h2 = "BB".hashCode(); System.out.println(h1 == h2); // true Map<String, Integer> m = new HashMap<>(); m.put("Aa", 1); m.put("BB", 2); System.out.println(m.size()); // 2
3.What is the difference between ArrayList and LinkedList in Java?
In short: ArrayList is a resizable array with O(1) access by index; LinkedList is a doubly linked list with O(n) access by index, and in practice ArrayList is faster for almost every workload.
ArrayList stores its elements in one contiguous array, so get(i) is O(1) and appending is amortised O(1): when the array is full it grows by half and copies. Inserting or removing in the middle shifts every later element, which is O(n). LinkedList's nodes point both ways, so adding or removing at either end is O(1), but reaching the i-th element walks the list, O(n), and each node costs extra memory and scatters data across the heap, which hurts cache performance. The textbook claim that LinkedList wins for insertions in the middle holds only when you already have an iterator at that position. For stack or queue use, ArrayDeque beats both.
List<Integer> a = new ArrayList<>(); a.add(1); a.add(2); a.add(0, 9); System.out.println(a); // [9, 1, 2] var l = new LinkedList<>(a); l.addFirst(0); int last = l.getLast(); System.out.println(last); // 2
4.What is the difference between HashMap, Hashtable and ConcurrentHashMap?
In short: HashMap is unsynchronized and allows one null key; Hashtable is a legacy fully synchronized map with no nulls; ConcurrentHashMap is the thread-safe choice, with fine-grained locking and no nulls.
Hashtable dates from Java 1.0: every method is synchronized on the whole table, so only one thread uses it at a time, and it rejects null keys and values. Collections.synchronizedMap(new HashMap<>()) has the same single-lock design. ConcurrentHashMap lets reads proceed without locking and locks only the bucket being updated, so many threads can work at once. It also rejects nulls, as the last line below shows, because in a concurrent map a get returning null would be ambiguous: absent, or present with null? Compound actions need its atomic methods, such as merge, compute and putIfAbsent, because a get followed by a put can interleave with another thread. Its iterators never throw ConcurrentModificationException.
Map<String, Integer> c = new ConcurrentHashMap<>(); c.merge("k", 1, Integer::sum); c.merge("k", 1, Integer::sum); System.out.println(c); // {k=2} c.put("n", null); // throws NullPointerException
5.What is the difference between Comparable and Comparator in Java?
In short: Comparable is implemented by the class itself to define its one natural order through compareTo; a Comparator is a separate object, so there can be as many orderings as you need.
String, Integer and LocalDate implement Comparable, which is why Collections.sort and TreeSet can order them without help. For your own class, implement Comparable when there is one obvious order, and keep compareTo consistent with equals, or a TreeSet will treat unequal objects as duplicates. Every other ordering is a Comparator, and Java 8 made them easy to build: comparingInt, reversed and thenComparing chain into a sort by marks descending, then by name, as below. Avoid the old trick of returning a - b from compare, which overflows for large values of opposite sign; Integer.compare(a, b) or comparingInt is correct.
record S(String n, int m) {} List<S> xs = new ArrayList<>(); xs.add(new S("Ravi", 80)); xs.add(new S("Anu", 92)); xs.add(new S("Dev", 80)); xs.sort(Comparator .comparingInt(S::m) .reversed() .thenComparing(S::n)); System.out.println(xs.get(1)); // S[n=Dev, m=80]
6.What is a fail-fast iterator, and how do you remove elements while iterating in Java?
In short: A fail-fast iterator throws ConcurrentModificationException when its collection changes structurally behind its back; remove through the iterator itself, or with removeIf.
ArrayList, HashMap and most java.util collections keep a modification count. Their iterators record it when created and check it at each step, so adding or removing through the collection during a loop, including inside a for-each, which is an iterator in disguise, throws on the next call to next(), as this page's sample shows. The check is best-effort bug detection, and single-threaded code triggers it as often as multithreaded code. The fixes: removeIf with a condition, the clearest; Iterator.remove(), which keeps the count in step; or collecting what to remove and removing it afterwards. The concurrent collections, such as CopyOnWriteArrayList and ConcurrentHashMap, have iterators that never throw it.
List<Integer> xs = new ArrayList<>(); for (int i = 1; i <= 4; i++) xs.add(i); xs.removeIf(x -> x % 2 == 0); System.out.println(xs); // [1, 3] var it = xs.iterator(); while (it.hasNext()) if (it.next() == 1) it.remove(); System.out.println(xs); // [3]
7.What is the difference between HashSet, LinkedHashSet and TreeSet?
In short: All three reject duplicates; HashSet has no defined order and O(1) operations, LinkedHashSet keeps insertion order, and TreeSet keeps elements sorted with O(log n) operations.
HashSet is a HashMap underneath, with the elements as keys and one shared dummy value, so it inherits HashMap's O(1) average add and contains and its dependence on equals() and hashCode(). LinkedHashSet threads a linked list through the entries, so iteration follows insertion order at a small memory cost; it is the standard way to remove duplicates while keeping order, as below. TreeSet is a red-black tree: operations are O(log n), elements need a natural order or a Comparator, and it offers navigation methods such as first(), floor() and headSet(). TreeSet decides duplicates with compareTo, not equals, so a comparator that ignores a field merges elements that differ only in that field.
var a = List.of(3, 1, 3, 2); var h = new LinkedHashSet<>(a); var t = new TreeSet<>(a); System.out.println(h); // [3, 1, 2] System.out.println(t); // [1, 2, 3] System.out.println(t.floor(5)); // 3
8.What does List.of() return, and how does it differ from Collections.unmodifiableList()?
In short: List.of returns a truly immutable list that rejects nulls; Collections.unmodifiableList wraps an existing list in a read-only view, so changes to the original still show through.
Both throw UnsupportedOperationException on add, remove or set, but they are different things. List.of, Set.of and Map.of, since Java 9, build compact immutable collections: nothing can change them, they reject null elements, and Set.of and Map.of reject duplicate arguments. Collections.unmodifiableList(list) returns a view that blocks writes through the view only; whoever holds the original list can still change it, and the view reflects the change, as below, while add on the view itself throws. List.copyOf takes an immutable snapshot, which is what a class should store when it must not be affected by its caller. For thread safety, prefer the concurrent collections to Collections.synchronizedList, whose iteration still needs manual locking.
List<Integer> src = new ArrayList<>(); src.add(1); var v = Collections .unmodifiableList(src); var c = List.copyOf(src); src.add(2); System.out.println(v); // [1, 2] System.out.println(c); // [1]
How the diagnostic asks it
One question from the Java bank, exactly as a sitting would show it. The bank has 4 on collections and 30 across Java.
What happens when this Java code runs? (java.util.* is imported.)
List<String> names = new ArrayList<>(List.of("a", "b", "c")); for (String n : names) { if (n.equals("a")) names.remove(n); } System.out.println(names);
- 1It prints [b, c]
- 2It prints [a, b, c]
- 3ConcurrentModificationException is throwncorrect
- 4It does not compile: a list cannot be changed inside a for-each loop
The for-each loop runs on an Iterator, and ArrayList's iterator is fail-fast: after names.remove("a") changes the list directly, the iterator's next call to next() detects the modification and throws ConcurrentModificationException. It prints [b, c] is what the code intends, and it is what names.removeIf(n -> n.equals("a")) or an explicit Iterator's remove() would give. It prints [a, b, c] assumes the removal was ignored. The code compiles: the compiler does not track modifications inside loops.
Measure it
Reading answers tells you what’s true. A diagnostic tells you what you get wrong.
10 Java questions across its topics, easy to hard, about fifteen minutes. You get a readiness figure with the arithmetic shown, the topics you missed named, and a practice set sized for today. Free: 1 diagnostic a month and 15 problems a day. No card.