Lists, sets and maps cover most code, but the rest of the Collections Framework solves specific problems neatly: Queue and Deque for FIFO and stack behaviour, PriorityQueue for "always give me the smallest", iterators that fail fast, the Collections helper class, and special maps such as EnumMap and WeakHashMap.

Queue, Deque, PriorityQueue

Queue — FIFO

// Queue interface (FIFO — first in, first out)
Queue<String> queue = new LinkedList<>();

// ── TWO VERSIONS OF EACH OPERATION ──
// Throwing versions:      | Safe versions (return null/false):
queue.add("A");          // queue.offer("A")  — prefer this
queue.remove();          // queue.poll()       — prefer this
queue.element();         // queue.peek()       — prefer this

queue.offer("B");
queue.offer("C");
System.out.println(queue.peek());   // A (head, not removed)
System.out.println(queue.poll());   // A (removed)
System.out.println(queue.poll());   // B
System.out.println(queue.poll());   // C
System.out.println(queue.poll());   // null (empty, no exception)

// ── PRIORITY QUEUE ──
// Heap-based, always dequeues MINIMUM element
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(30); pq.offer(10); pq.offer(20);
pq.poll();   // 10 (min first!)
pq.poll();   // 20

// Max-heap
PriorityQueue<Integer> maxPQ = new PriorityQueue<>(Comparator.reverseOrder());
maxPQ.offer(30); maxPQ.offer(10); maxPQ.offer(20);
maxPQ.poll();   // 30 (max first!)

// ── DEQUE (double-ended queue) ──
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("A");   // front
deque.addLast("B");    // back
deque.peekFirst();      // A
deque.peekLast();       // B
deque.pollFirst();      // A
deque.pollLast();       // B

Interview Questions

How does PriorityQueue work internally?

PriorityQueue uses a binary min-heap (backed by an array). The parent is always smaller than its children. offer() bubbles up, poll() removes root and sifts down — both O(log n). peek() is O(1). The elements are NOT sorted in iteration order, only the head is guaranteed minimum.

Advertisement

Iterator Deep Dive — Fail-Fast vs Fail-Safe

// FAIL-FAST iterators: throw ConcurrentModificationException
// if collection modified during iteration (ArrayList, HashMap, etc.)
List<String> list = new ArrayList<>(Arrays.asList("a","b","c","d"));

// ❌ WRONG: modifying during for-each (uses fail-fast iterator)
for (String s : list) {
    if (s.equals("b")) list.remove(s); // ConcurrentModificationException!
}

// ✅ CORRECT 1: Iterator.remove()
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.equals("b")) it.remove(); // safe removal via iterator
}

// ✅ CORRECT 2: removeIf (Java 8)
list.removeIf(s -> s.equals("b")); // cleaner

// ✅ CORRECT 3: collect to new list
list = list.stream().filter(s -> !s.equals("b")).collect(Collectors.toList());

// HOW fail-fast works:
// ArrayList has int modCount (modification count)
// Iterator captures modCount at creation
// Each next() checks: if modCount != expectedModCount → throw CME

// FAIL-SAFE iterators: work on a COPY — no CME
// CopyOnWriteArrayList, ConcurrentHashMap
CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(list);
for (String s : cowList) {
    if (s.equals("a")) cowList.add("x"); // safe! iterates original copy
}
// Trade-off: more memory, slightly stale view

// ── LIST ITERATOR ──
ListIterator<String> li = list.listIterator();
while (li.hasNext()) {
    String s = li.next();
    li.set(s.toUpperCase()); // REPLACE current element
}
// Go backwards
while (li.hasPrevious()) {
    System.out.println(li.previous());
}
li.add("new"); // insert before next element
li.nextIndex(); // index of next element
li.previousIndex();

// ── SPLITERATOR ──
// Designed for parallel processing with Streams
Spliterator<String> sp = list.spliterator();
sp.characteristics(); // ORDERED | SIZED | SUBSIZED
sp.estimateSize();    // estimated elements
Spliterator<String> half = sp.trySplit(); // split for parallel
sp.forEachRemaining(System.out::println);

Collections Utility Class — All Methods

import java.util.Collections;

List<Integer> list = new ArrayList<>(Arrays.asList(5,3,1,4,2));

// ── SORTING ──
Collections.sort(list);                        // [1,2,3,4,5]
Collections.sort(list, Comparator.reverseOrder()); // [5,4,3,2,1]
Collections.reverse(list);                     // reverse in-place
Collections.shuffle(list);                     // random order
Collections.shuffle(list, new Random(42));     // seeded shuffle
Collections.rotate(list, 2);                   // shift right by 2
Collections.swap(list, 0, 4);                  // swap elements at index 0 and 4

// ── SEARCHING ──
Collections.sort(list); // must be sorted first!
int idx = Collections.binarySearch(list, 3);  // index of 3
int min = Collections.min(list);               // minimum element
int max = Collections.max(list);               // maximum element
int min2 = Collections.min(list, Comparator.reverseOrder()); // custom comparator

// ── FREQUENCY & DISJOINT ──
Collections.frequency(list, 3);   // count of 3
List<String> l1 = Arrays.asList("a","b");
List<String> l2 = Arrays.asList("c","d");
Collections.disjoint(l1, l2);     // true (no common elements)

// ── FILL & COPY ──
Collections.fill(list, 0);        // fill all with 0
List<Integer> dest = new ArrayList<>(Arrays.asList(0,0,0,0,0));
Collections.copy(dest, list);     // copy src to dest (dest must be >= size)

// ── nCopies ──
List<String> nCopies = Collections.nCopies(5, "hello"); // [hello,hello,hello,hello,hello]
// Note: returned list is FIXED SIZE and IMMUTABLE

// ── WRAPPER COLLECTIONS ──
// Thread-safe wrappers
List<String> syncList = Collections.synchronizedList(new ArrayList<>());
Map<String,Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
// Must externally synchronize iteration!
synchronized(syncList) {
    for (String s : syncList) { }
}

// Unmodifiable wrappers
List<String> unmod = Collections.unmodifiableList(new ArrayList<>(list));
// unmod.add("x"); // UnsupportedOperationException!

// Also: unmodifiableMap, unmodifiableSet, unmodifiableSortedMap

// Singleton (one element only)
Set<String> single = Collections.singleton("only");
List<Integer> singleList = Collections.singletonList(42);
Map<String,Integer> singleMap = Collections.singletonMap("key", 1);

// Empty collections
List<String> empty = Collections.emptyList();
Set<Integer> emptySet = Collections.emptySet();
Map<String,Object> emptyMap = Collections.emptyMap();

// checkedList: runtime type checking
List rawList = new ArrayList();
List<String> checked = Collections.checkedList(rawList, String.class);
// checked.add(42); // ClassCastException at runtime (not compile time)

Special Map Implementations

// ── ENUMMAP: fastest map with enum keys ──
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "Meeting");
schedule.put(Day.FRI, "Review");
// Backed by array indexed by enum ordinal — very fast O(1)
// Always iterates in declaration order of enum

// ── IDENTITYHASHMAP: uses == not equals ──
IdentityHashMap<String, Integer> idMap = new IdentityHashMap<>();
String s1 = new String("key");
String s2 = new String("key"); // different object, same value
idMap.put(s1, 1);
idMap.put(s2, 2); // DIFFERENT entry! (uses == comparison)
System.out.println(idMap.size()); // 2
// Use case: object graph traversal (tracking visited nodes)

// ── WEAKHASHMAP: auto-removes entries when key is GC'd ──
WeakHashMap<Object, String> weakMap = new WeakHashMap<>();
Object key = new Object();
weakMap.put(key, "value");
System.out.println(weakMap.size()); // 1
key = null; // remove strong reference
System.gc(); // suggest GC
Thread.sleep(100);
System.out.println(weakMap.size()); // 0 (entry auto-removed!)
// Use case: caches where entries should be GC'd with their keys

// ── LINKEDHASHMAP access order (LRU cache) ──
LinkedHashMap<String, Integer> lru = new LinkedHashMap<>(
    16, 0.75f, true) { // accessOrder=true
    @Override
    protected boolean removeEldestEntry(Map.Entry<String,Integer> eldest) {
        return size() > 3; // keep max 3 entries
    }
};
lru.put("a", 1); lru.put("b", 2); lru.put("c", 3);
lru.get("a"); // 'a' now most recently used
lru.put("d", 4); // evicts 'b' (least recently used)
System.out.println(lru.keySet()); // [c, a, d]

// ── NAVIGABLEMAP methods ──
TreeMap<Integer, String> tree = new TreeMap<>();
tree.put(1,"one"); tree.put(3,"three"); tree.put(5,"five"); tree.put(7,"seven");
tree.ceilingKey(4)     // 5 (smallest key >= 4)
tree.floorKey(4)       // 3 (largest key <= 4)
tree.higherKey(5)      // 7 (smallest key > 5)
tree.lowerKey(5)       // 3 (largest key < 5)
tree.firstKey()        // 1
tree.lastKey()         // 7
tree.headMap(5)        // {1=one, 3=three} (keys < 5)
tree.headMap(5, true)  // {1=one, 3=three, 5=five} (inclusive)
tree.tailMap(5)        // {5=five, 7=seven} (keys >= 5)
tree.subMap(3, true, 7, false) // {3=three, 5=five} (3<=key<7)
tree.descendingMap()   // reversed view
tree.pollFirstEntry()  // remove and return first entry
tree.pollLastEntry()   // remove and return last entry

FAQs

What is the difference between Queue and Deque in Java?

A Queue adds at the tail and removes from the head (FIFO). A Deque (double-ended queue) allows adding and removing at both ends, so it can act as a queue or a stack; ArrayDeque is the usual implementation.

Is PriorityQueue sorted?

Only partially: it is a binary heap, so peek() and poll() always return the smallest element (or largest with a reversed comparator), but iterating over it does not give sorted order.

What is a fail-fast iterator?

An iterator that throws ConcurrentModificationException if the collection is structurally modified during iteration (other than through the iterator itself), as ArrayList and HashMap iterators do.