JavaCollectionsintermediate
Updated:

Java Collections Interview Questions and Answers

11 min read

The collections questions asked in almost every Java interview — HashMap internals, HashSet, iterators, ordering and thread-safe choices — answered properly.

TL;DR – Quick Answer

Java collections interviews concentrate on a predictable core: the framework hierarchy, ArrayList growth, HashMap internals (hashing, buckets, treeification, load factor), HashSet, ordering with LinkedHashMap and TreeMap, fail-fast iterators, Comparable vs Comparator, and thread-safe options like ConcurrentHashMap and CopyOnWriteArrayList. Interviewers grade you on internals and on choosing the right collection for a stated workload.

On This Page

Why interviewers ask about collections

Collections are the one Java topic you cannot avoid in any round, at any level. Every real program stores and looks things up, so your collection choices reveal how you think about data — and your grasp of HashMap internals reveals whether you understand hashing, equality and memory at all.

There is a second reason: collections questions have verifiable depth. "How does HashMap work?" can be answered in one sentence or in five layers, and the layer where you stop tells the interviewer your level with unusual precision.

This page covers the questions that appear in practically every loop. For the conceptual map behind them, start with the collections framework overview.

How to answer in an interview

Answer collection questions from the backing data structure outward. "ArrayList is backed by an array, therefore index access is O(1) and middle insertion is O(n)" is a derivation; "ArrayList is fast for get" is a memorized fact that collapses under one follow-up.

When asked to choose a collection, ask for the workload first: read-heavy or write-heavy, ordering needed, duplicates allowed, single-threaded or shared. Stating those four questions out loud — then answering — is exactly the behavior interviewers are scoring.

Q1. Give me a map of the Java Collections Framework.

Two root interfaces: Collection (things you iterate) and Map (key-value pairs, deliberately outside the Collection hierarchy). Under Collection: List (ordered, duplicates), Set (no duplicates), Queue/Deque (processing order). Each interface has hash-based, array-based, linked and tree-based implementations with predictable trade-offs.

The names encode the implementation: ArrayList = array-backed list, LinkedHashSet = hash set plus linked insertion order, TreeMap = red-black tree map. Utility classes Collections and Arrays supply algorithms (sorting, unmodifiable wrappers, binary search).

Interviewers open with this to find your edges — answer it crisply in under a minute, mention that Map is not a Collection (a favorite checkpoint), and let them pick where to dig.

Interview note: Trap: "why doesn't Map extend Collection?" Because a Map stores pairs, not elements — add(E) makes no sense for it. The views keySet(), values() and entrySet() are the bridge back into the Collection world.

Q2. How does ArrayList grow internally?

ArrayList wraps a plain array. When an add exceeds capacity, it allocates a new array 1.5x the size and copies everything over — so a single add is O(n) occasionally, but amortized O(1) across many adds.

The practical consequence: if you know you will store 100,000 elements, construct with new ArrayList<>(100_000) and skip a dozen intermediate copies. Also know that remove(index) shifts every later element left — removing from the front of a large list in a loop is an accidental O(n²).

List<Integer> ids = new ArrayList<>(100_000); // pre-size: no regrowth copies

Note that the backing array never shrinks automatically; trimToSize() exists but is rarely needed.

Interview note: Follow-up: "what is the difference between size and capacity?" Size is elements stored; capacity is the backing array's length. Confusing them suggests you have never pictured the array underneath.

Q3. Why must you override hashCode() when you override equals() for map keys?

Because HashMap finds the bucket using hashCode() first and only then checks equals() within that bucket. If equal objects can produce different hash codes, an object stored via one instance is searched in a different bucket via another — and the lookup fails even though the keys are "equal".

class Point { int x, y; /* equals overridden, hashCode NOT */ }

Map<Point, String> map = new HashMap<>();
map.put(new Point(1, 2), "home");
map.get(new Point(1, 2));   // null! different default hashCode → wrong bucket

This is the single most common self-inflicted collections bug. The related rule: keys should be immutable — if a key's fields change after insertion, its hash no longer matches its bucket and the entry is effectively lost while still consuming memory.

Interview note: Trap: "the map still 'contains' the mutated key's entry — how do you get it back?" Iterate entrySet(); only hashed lookup is broken. Knowing this shows you understand the mechanism, not just the rule.

Q4. Walk me through what happens on map.put(key, value).

HashMap computes the key's hash, spreads it (XOR of high bits), and maps it to a bucket index with (n - 1) & hash. An empty bucket gets a new node; a collision walks the bucket comparing hash-then-equals — replacing the value on a match, appending otherwise. If a bucket's chain passes 8 nodes (and the table has at least 64 buckets), it converts to a red-black tree, turning worst-case O(n) lookups into O(log n).

The index math is why capacity is always a power of two: (n - 1) & hash is a fast modulo that only works when n - 1 is all ones in binary. The treeification threshold is the Java 8 defense against hash-collision attacks and terrible hashCode() implementations.

Get the full walk-through with diagrams in how HashMap works internally — this exact narrative, told confidently, is a rite of passage in Java interviews.

Interview note: Follow-up: "when does the tree revert to a list?" On resize/removal when a bin shrinks below 6 nodes — the gap between 8 and 6 prevents flip-flopping at the boundary.

Q5. What do load factor and initial capacity control in HashMap?

Capacity is the bucket count; load factor (default 0.75) is the fill ratio that triggers a resize. When size exceeds capacity × load factor, the table doubles and every entry is redistributed — an O(n) event. 0.75 is the tuned compromise: lower wastes memory on empty buckets, higher piles up collisions.

The interview-grade detail: new HashMap<>(16) with default load factor resizes at 12 entries, not 16. To hold N entries without resizing, size for N / 0.75:

Map<String, User> byId = new HashMap<>(  // holds ~10k entries, no resize
    (int) Math.ceil(10_000 / 0.75));

Doubling capacity keeps the power-of-two invariant from Q4, and in Java 8+ each entry moves to either its old index or old index + old capacity — a one-bit decision, which keeps rehashing cheap.

Interview note: Trap: "does resize change the entries' hash codes?" No — hashes are cached in nodes; only the index mapping changes because n changed.

Q6. How does HashSet work internally?

HashSet is a HashMap in disguise: every element you add becomes a key in an internal map, with a single shared dummy object as the value. All its behavior — O(1) average add/contains, no duplicates, no ordering — is inherited directly from HashMap's key semantics.

"No duplicates" is literally map.put(element, PRESENT) returning a non-null previous value. That is why everything from Q3 applies to set elements: broken hashCode() means duplicates sneak in; mutated elements get lost.

Set<String> seen = new HashSet<>();
if (!seen.add(order.getId())) {      // add() returns false on duplicate
    throw new DuplicateOrderException(order.getId());
}

Using add()'s boolean return for duplicate detection — one operation instead of contains + add — is a small idiom that reads as fluency. The Set interface guide compares the three main implementations.

Interview note: Follow-up: "how would you get a set that remembers insertion order?" LinkedHashSet — same hashing, plus a linked list threading the entries in insertion order.

Q7. HashMap vs Hashtable vs ConcurrentHashMap — which do you pick?

HashMap: fast, null-tolerant, not thread-safe — the single-threaded default. Hashtable: legacy, one lock around every method — never pick it. ConcurrentHashMap: concurrent reads, fine-grained locked writes, atomic compound operations — the shared-state default.

The subtle point that separates levels: Hashtable's synchronization is per-method, so check-then-act sequences (containsKey then put) are still race conditions. Thread safety of individual operations does not compose into thread safety of your logic — ConcurrentHashMap's putIfAbsent, compute and merge exist precisely to make the compound step atomic.

Also be ready for the nulls question: ConcurrentHashMap forbids null keys and values because a null from get() would be ambiguous (absent vs stored-null) in a world where containsKey-then-get is racy. Full comparison: HashMap vs ConcurrentHashMap.

Interview note: Trap: "is ConcurrentHashMap's size() exact?" It is an estimate under concurrent updates — accept approximation or redesign; needing an exact live count usually signals a design smell.

Q8. LinkedHashMap vs TreeMap — how do you choose an ordered map?

LinkedHashMap preserves insertion order (or access order) with O(1) operations by threading a doubly-linked list through the hash entries. TreeMap keeps keys in sorted order via a red-black tree at O(log n) per operation, and adds navigation: firstKey, floorKey, headMap, range views. Insertion order → LinkedHashMap; sorted order or range queries → TreeMap.

The showpiece detail: LinkedHashMap in access-order mode plus one overridden method is a working LRU cache —

Map<String, Data> lru = new LinkedHashMap<>(16, 0.75f, true) {
    protected boolean removeEldestEntry(Map.Entry<String, Data> e) {
        return size() > 1000;   // evict least-recently-used beyond 1000
    }
};

For TreeMap, remember it uses compareTo/Comparator for equality — not equals() — so keys "equal" by comparison overwrite each other even if equals() disagrees.

Interview note: Follow-up: "can TreeMap hold a null key?" With natural ordering, no — compareTo on null throws NPE. A custom null-tolerant Comparator can allow it, but don't.

Q9. What is a fail-fast iterator, and how does it differ from fail-safe?

Fail-fast iterators (ArrayList, HashMap) track a modification count; if the collection is structurally modified outside the iterator mid-iteration, the next iterator operation throws ConcurrentModificationException. "Fail-safe" iterators (CopyOnWriteArrayList, ConcurrentHashMap) never throw — they iterate a snapshot or a weakly-consistent view and may not reflect concurrent changes.

Two things candidates get wrong: fail-fast is best-effort, not guaranteed — it is a bug detector, not a synchronization mechanism; and the exception fires in single-threaded code too, most commonly when removing inside a for-each loop.

list.removeIf(item -> item.isExpired());       // safe and idiomatic
// vs: for (Item i : list) { list.remove(i); } // throws CME

Inside a manual loop, iterator.remove() is the sanctioned mutation path — it updates the expected mod count.

Interview note: Trap: "will replacing an element with set() trigger CME?" No — only structural modifications (changing size) bump modCount. set() is not structural.

Q10. Comparable vs Comparator — when do you use each?

Comparable is the type's single natural order, implemented as compareTo inside the class (String, Integer, LocalDate have one). Comparator is an external, pluggable order — use it when you need multiple orders, cannot modify the class, or the "natural" order is not obvious for the domain.

Modern comparator composition is the part to demonstrate live:

employees.sort(Comparator.comparing(Employee::getDept)
        .thenComparing(Employee::getSalary, Comparator.reverseOrder())
        .thenComparing(Employee::getName));

Key rules: keep compareTo consistent with equals (sorted sets treat compare-equal as duplicate), never subtract ints in a comparator (a - b overflows — use Integer.compare), and use Comparator.nullsFirst/nullsLast when fields may be null.

Interview note: Follow-up: "what happens if your comparator violates transitivity?" sort can throw "Comparison method violates its general contract!" — TimSort actively detects broken comparators.

Q11. Unmodifiable vs immutable collections — what is the difference?

Collections.unmodifiableList(list) is a read-only view: you cannot modify through the wrapper, but anyone holding the original list still can, and the view sees those changes. List.of(...) / List.copyOf(...) create truly immutable collections — no path to mutation exists anywhere.

List<String> source = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(source);
source.add("c");
System.out.println(view);        // [a, b, c] — the "unmodifiable" view changed

That output is the whole interview answer in one line. Practical guidance: return List.copyOf(internal) from getters for defensive copies, and remember List.of rejects nulls and its mutators throw UnsupportedOperationException — a runtime failure, not compile-time, so document immutability in the API.

Interview note: Trap: "is a List.of() list of StringBuilders immutable?" The list is; the elements are not. Immutability of the container never extends to mutable elements.

Q12. Why ArrayDeque over Stack, and what does PriorityQueue actually guarantee?

Stack extends Vector: synchronized on every call and, worse, it is a List — callers can insert into the middle of your "stack". ArrayDeque is the modern answer for both stack (push/pop) and queue (offer/poll) usage. PriorityQueue is a binary heap: poll() always returns the smallest element by the ordering, but iteration order is not sorted.

The PriorityQueue caveat catches even experienced candidates: printing the queue or iterating it shows heap-array order, not priority order. To consume in priority order, poll repeatedly — each poll is O(log n).

Queue<Task> tasks = new PriorityQueue<>(Comparator.comparing(Task::getDeadline));
tasks.offer(t1); tasks.offer(t2);
Task next = tasks.poll();   // earliest deadline — but tasks.toString() is NOT sorted

Extending Vector is also a clean example of inheritance-gone-wrong for design discussions — the composition argument made concrete.

Interview note: Follow-up: "is PriorityQueue thread-safe?" No — PriorityBlockingQueue is the concurrent variant, and it also adds blocking retrieval for producer-consumer designs.

Q13. When is CopyOnWriteArrayList the right choice?

When reads massively outnumber writes and iteration must never throw ConcurrentModificationException — classically listener lists and configuration snapshots. Every mutation copies the entire backing array, so write-heavy or large-list usage is a performance disaster.

The mental model: readers and iterators work on an immutable snapshot with zero locking; writers pay the full copy cost under a lock. An iterator created before a write never sees it — which is a feature for "notify all listeners registered at this moment" semantics, and a bug if you expected live data.

The decision sentence for interviews: "hundreds of reads per write and small lists → CopyOnWriteArrayList; otherwise a ConcurrentHashMap-based design or explicit synchronization." Naming the workload condition is what makes the answer senior.

Interview note: Trap: "can its iterator remove elements?" No — snapshot iterators throw UnsupportedOperationException on remove(). Mutation goes through the list itself, never the iterator.

Q14. How do you choose the right collection for a given workload?

Ask four questions in order: (1) pairs or elements → Map vs Collection; (2) duplicates → List vs Set; (3) ordering — none, insertion, sorted, priority → Hash, Linked, Tree*, PriorityQueue; (4) concurrency → java.util.concurrent variants, chosen by read/write ratio. Then confirm with the access pattern: random index reads favor arrays, key lookups favor hashing, range queries favor trees.**

Practice verbalizing it on a scenario: "deduplicate incoming order IDs and report them in arrival order" → duplicates no, ordering insertion, single-threaded → LinkedHashSet. Interviewers often score the procedure more than the final answer. This question is also where everything above compounds: the load-factor math (Q5) sets the initial capacity, the iterator rules (Q9) govern mutation, and the concurrency column (Q7, Q13) picks the package.

Interview note: Follow-up: "and if you need a sorted, thread-safe map?" ConcurrentSkipListMap — the answer that confirms you know the concurrent package beyond ConcurrentHashMap.

How to prepare

Drill HashMap until you can narrate put, get and resize on a whiteboard without pausing — it is the most-asked internals question in Java interviews, and Q3–Q5 here are its supporting cast. Then write the failure demos yourself: a key class without hashCode(), a removal inside for-each, the unmodifiable-view surprise from Q11. Each takes five minutes and permanently inoculates you against the trap version of the question.

Pair this page with the OOP question set — equality, immutability and inheritance questions bridge both — and if you are targeting a specific band, check the 2-year experience questions for how collections answers get framed inside an experience interview. For structured practice with follow-up pressure, a mock interview focused purely on collections is the fastest way to find your stopping layer — and move it one layer deeper.

Frequently Asked Questions

Which collections topics are asked most often in Java interviews?
HashMap internals dominate — hashing, collisions, load factor and the Java 8 treeification change. After that: ArrayList vs other lists, HashSet mechanics, fail-fast iterators, Comparable vs Comparator, and thread-safe collection choices. Prepare HashMap twice as deeply as everything else.
Do freshers and experienced developers get the same collections questions?
The topics overlap but the depth differs. Freshers are asked what a HashMap is and when to use a Set. Experienced candidates are asked how resize works, why the load factor is 0.75, what happens with a bad hashCode, and how they picked collections for a real workload.
How do I remember the time complexities of Java collections?
Derive them from the backing structure instead of memorizing: array-backed means O(1) index access and O(n) middle insertion; hash-backed means O(1) average lookup; tree-backed means O(log n) everything. Once you know what backs each class, the complexities follow.
Are legacy classes like Vector and Stack still asked in interviews?
They appear as comparison questions — Vector vs ArrayList, Stack vs ArrayDeque — to test whether you know why they were replaced. The expected answer is that their per-method synchronization is both slow and insufficient for compound operations, so modern alternatives are preferred.
What collections mistakes do interviewers watch for in coding rounds?
Modifying a list inside a for-each loop, using mutable objects as map keys, comparing wrapper keys with ==, calling get() in a loop on a structure with O(n) access, and choosing a HashMap where insertion order was required. Avoiding these in live code scores quietly but heavily.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 14 July 2026 LinkedIn
Chat with us