Skip to main content

Java Collections Framework Interview Questions & Answers

These questions cover essential and tricky concepts of the Java Collections Framework with senior-level depth.

1. Explain the Collection Hierarchy

The Java Collections Framework is organized into a well-defined hierarchy of interfaces and implementations:

๐Ÿ—‚๏ธ Java Collections Framework โ€” Hierarchy Overview

โ”€โ”€ MAP TREE (separate from Collection) โ”€โ”€IterableCollectionListSetQueue / DequeArrayListLinkedListVector (legacy)CopyOnWriteArrayListHashSetLinkedHashSetTreeSetPriorityQueueArrayDequeBlockingQueueMapHashMapLinkedHashMapTreeMapHashtable (legacy)ConcurrentHashMap

๐Ÿ’ก Click any node to see its details, complexity, and notes.

Key interfaces:

  • List: Ordered collection allowing duplicates and index-based access. Maintains insertion order.
  • Set: No duplicates. HashSet is unordered, LinkedHashSet preserves insertion order, TreeSet sorts elements.
  • Queue/Deque: FIFO processing (Queue) or double-ended (Deque).
  • Map: Key-value pairs. Not part of the Collection interface hierarchy.

Why Map doesn't extend Collection

Collection.add(E e) accepts a single element. Map.put(K key, V value) requires two parameters (a pair). The fundamental data model is different โ€” a Map is a collection of entries (key-value pairs), not individual elements. However, you can get Collection views: map.keySet(), map.values(), and map.entrySet().

2. What is the difference between Fail-Fast and Fail-Safe Iterators?

FeatureFail-FastFail-Safe
BehaviorThrows ConcurrentModificationException on structural modificationNever throws exception
Works onOriginal collectionClone/snapshot of the collection
CollectionsArrayList, HashMap, HashSetConcurrentHashMap, CopyOnWriteArrayList
MemoryNo extra memoryExtra memory for the copy/snapshot
Reflects changesN/A (throws exception)May not reflect modifications made after iterator creation

How Fail-Fast detection works internally

// Inside ArrayList โ€” the modCount mechanism
transient int modCount = 0; // Incremented on add(), remove(), clear()

// Inside ArrayList$Itr (the iterator)
int expectedModCount = modCount; // Captured at iterator creation

public E next() {
if (modCount != expectedModCount) // Check on every next() call
throw new ConcurrentModificationException();
// ... return element
}

Important: Fail-fast is a best-effort mechanism, not a guarantee. The JavaDoc explicitly states it should not be relied upon for correctness โ€” only for bug detection.

3. What is a BlockingQueue?

BlockingQueue (in java.util.concurrent) is a thread-safe queue that supports blocking operations โ€” the thread waits instead of failing when the operation cannot be completed immediately.

Blocking behavior

OperationIf Queue is EmptyIf Queue is Full
put(e)N/ABlocks until space available
take()Blocks until element availableN/A
offer(e, timeout)N/AWaits up to timeout, returns false
poll(timeout)Waits up to timeout, returns nullN/A
add(e)N/AThrows IllegalStateException
remove()Throws NoSuchElementExceptionN/A

Common Implementations

ImplementationCapacityOrderingUse Case
ArrayBlockingQueueBounded (fixed)FIFOProducer-consumer with backpressure
LinkedBlockingQueueOptionally boundedFIFOThread pool work queues (Executors.newFixedThreadPool)
PriorityBlockingQueueUnboundedPriority-basedTask scheduling by priority
SynchronousQueueZero capacityDirect handoffExecutors.newCachedThreadPool
DelayQueueUnboundedBy delay expirationScheduled task execution

Producer-Consumer Pattern

BlockingQueue<Task> queue = new ArrayBlockingQueue<>(100);

// Producer thread โ€” blocks if queue is full (backpressure!)
queue.put(new Task("process-order"));

// Consumer thread โ€” blocks if queue is empty (waits for work)
Task task = queue.take();
task.execute();

4. Synchronized vs. Concurrent Collections

FeatureSynchronizedConcurrent
ExamplesHashtable, Vector, Collections.synchronizedMap()ConcurrentHashMap, CopyOnWriteArrayList
Lock granularityEntire collection (coarse-grained)Per-bucket/segment (fine-grained)
Read blockingYes โ€” readers block other readersNo โ€” reads are lock-free
IteratorFail-fastWeakly consistent
Compound operationsNot atomic (check-then-act is racy)Atomic (computeIfAbsent, putIfAbsent)
ScalabilityPoor (serializes all access)Excellent (high concurrency)

The compound operation problem

// BROKEN with synchronizedMap โ€” NOT atomic!
Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
if (!syncMap.containsKey("counter")) { // Thread A: false
// Thread B: also sees false, also enters this block
syncMap.put("counter", 1); // Both threads put โ€” lost update!
}

// CORRECT with ConcurrentHashMap โ€” atomic compound operation
ConcurrentHashMap<String, Integer> concMap = new ConcurrentHashMap<>();
concMap.putIfAbsent("counter", 1); // Atomic โ€” no race condition
concMap.computeIfAbsent("counter", k -> 1); // Also atomic

5. How does HashMap work internally?

HashMap works on the principle of hashing with a hybrid data structure:

The put(K, V) operation step by step:

  1. Hash calculation: hash = key.hashCode() ^ (key.hashCode() >>> 16) โ€” the high bits are mixed into the low bits to improve distribution for small tables.
  2. Bucket index: index = hash & (table.length - 1) โ€” bitwise AND (faster than modulo for power-of-2 sizes).
  3. Empty bucket: Store a new Node<K,V> directly.
  4. Collision (same bucket): Walk the linked list/tree. If an existing node has the same key (equals() returns true), replace the value. Otherwise, append a new node.
  5. Treeification check: If the linked list at this bucket exceeds 8 nodes (TREEIFY_THRESHOLD), convert to a Red-Black Tree for O(log n) lookup.
  6. Resize check: If total size exceeds capacity ร— loadFactor, resize (double the table).

The get(K) operation:

  1. Calculate hash and bucket index (same as put).
  2. Check the first node in the bucket โ€” if it matches, return immediately (O(1)).
  3. If not, and the bucket contains a tree, do a tree lookup (O(log n)).
  4. If it's a linked list, traverse linearly (O(n) worst case for that bucket).

Memory Layout

HashMap
โ”œโ”€โ”€ Node[] table (length = capacity, always power of 2)
โ”‚ โ”œโ”€โ”€ [0] โ†’ null
โ”‚ โ”œโ”€โ”€ [1] โ†’ Node(hash=1, "Alice"โ†’"Engineer") โ†’ Node(hash=1, "Bob"โ†’"Manager")
โ”‚ โ”œโ”€โ”€ [2] โ†’ null
โ”‚ โ”œโ”€โ”€ [3] โ†’ TreeBin โ†’ TreeNode... (if โ‰ฅ 8 collisions)
โ”‚ โ””โ”€โ”€ ...
โ”œโ”€โ”€ size = 2 (actual entries)
โ”œโ”€โ”€ threshold = 12 (capacity ร— loadFactor = 16 ร— 0.75)
โ””โ”€โ”€ loadFactor = 0.75

The equals() and hashCode() Contract

RuleConsequence if Violated
Equal objects must have equal hashcodesget() searches wrong bucket โ†’ entry "lost"
Unequal objects may have equal hashcodesAllowed (collision) โ€” performance impact only
hashCode() must be consistent across callsEntry moves between buckets between calls
If equals() uses a field, hashCode() must tooInconsistent behavior
// CRITICAL: Override BOTH or NEITHER
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Employee e)) return false;
return id == e.id && Objects.equals(name, e.name);
}

@Override
public int hashCode() {
return Objects.hash(id, name); // Same fields as equals()
}

๐Ÿ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%