JUNE 2026 Coding interviews test recall. kodr.run tests engineering judgment
JavaIntermediate15 minGeneral

Java Collections Framework Interview Questions

Key takeaways

  1. Collections questions cluster into four families: List implementations (ArrayList, LinkedList), Map implementations (HashMap, LinkedHashMap, TreeMap, and their thread-safe variants), Set implementations, and iteration/ordering contracts (fail-fast behaviour, Comparable vs Comparator, Queue semantics).
  2. Knowing an implementation's name isn't the bar. Interviewers want to know why you'd pick one over another for a given access pattern. For example random access vs frequent insertion, ordering guarantees, thread-safety needs.
  3. Several of Java's most cited "gotchas" live in this list: ConcurrentModificationException, HashMap keys that get mutated after insertion, and assuming HashMap preserves insertion order. All three compile fine and fail at runtime or silently, which is exactly why they're asked.

Intro

The Collections Framework is where a lot of real Java bugs live, which is exactly why it's interview territory. This guide collects the fifteen questions that come up most, covering List, Set, Map, and Queue implementations, plus the ordering and thread-safety contracts that catch experienced developers as often as beginners. Every question ships with working code and its printed output.

How interviewers read a Java Collections answer

LayerWhat a weaker answer doesWhat a stronger answer does
Conceptual clarityNames an implementation, can't say what's different underneathNames the underlying structure (array, linked nodes, hash table, red-black tree) and what that implies
Design judgmentDefaults to ArrayList/HashMap for everythingPicks the implementation based on the actual access pattern, reads vs writes, ordering and thread-safety
CorrectnessCode compiles but violates an implicit contract (mutating a key, modifying a list mid-iteration)Anticipates the contract and codes around it before being asked
CommunicationStates the definition and stopsExplains what breaks in production when the wrong choice is made

1What is the difference between ArrayList and LinkedList, and when should you use each?

Java · ArrayListVsLinkedListDemo.java
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


public class ArrayListVsLinkedListDemo {
    public static void main(String[] args) {
        List<Integer> arrayList = new ArrayList<>();
        arrayList.add(1);
        arrayList.add(2);
        arrayList.add(0, 0); // insert at the front - shifts every element right, O(n)
        System.out.println(arrayList); // [0, 1, 2]
        System.out.println(arrayList.get(1)); // 1 - O(1), backed by a plain array


        List<Integer> linkedList = new LinkedList<>();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(0, 0); // insert at the front - O(1), just relinks pointers
        System.out.println(linkedList); // [0, 1, 2]
    }
}

ArrayList is backed by a resizable array. O(1) random access via get(index), but inserting anywhere except the end means shifting every subsequent element. LinkedList is backed by doubly-linked nodes, inserting at either end is O(1), but it has no real random access. The mistake is forgetting that second half:

Java · LinkedListRandomAccessMistake.java
import java.util.LinkedList;
import java.util.List;


public class LinkedListRandomAccessMistake {
    public static void main(String[] args) {
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < 5; i++) linkedList.add(i);


        // Mistake: index-based access on a LinkedList - each get() walks from the head, O(n) per call
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.print(linkedList.get(i) + " ");
        }
        System.out.println();
        // 0 1 2 3 4 -- correct output, but O(n^2) overall on a LinkedList
    }
}

What this evaluates: Design judgment. The output is correct either way. The signal is recognising that get(i) in a loop is fine on an ArrayList and quietly quadratic on a LinkedList, and reaching for a for-each loop or Iterator instead.


2What is the difference between HashMap, LinkedHashMap, and TreeMap?

Java · MapImplementationsDemo.java
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;


public class MapImplementationsDemo {
    public static void main(String[] args) {
        Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
        Map<String, Integer> treeMap = new TreeMap<>();


        String[] keys = {"banana", "apple", "cherry"};
        for (String key : keys) {
            linkedHashMap.put(key, key.length());
            treeMap.put(key, key.length());
        }


        System.out.println(linkedHashMap.keySet()); // [banana, apple, cherry] - insertion order preserved
        System.out.println(treeMap.keySet()); // [apple, banana, cherry] - sorted (natural) order
    }
}

All three implement Map, but their ordering guarantees come from different underlying structures: HashMap is a plain hash table with no ordering guarantee at all, LinkedHashMap adds a linked list threading through the entries to preserve insertion order, and TreeMap is a red-black tree that keeps keys sorted, at O(log n) instead of O(1) per operation. The mistake is assuming plain HashMap behaves like the other two:

Java · HashMapOrderMistake.java
import java.util.HashMap;
import java.util.Map;


public class HashMapOrderMistake {
    public static void main(String[] args) {
        Map<Integer, String> scores = new HashMap<>();
        scores.put(3, "three");
        scores.put(1, "one");
        scores.put(2, "two");


        // Mistake: assuming HashMap preserves insertion order
        for (Map.Entry<Integer, String> entry : scores.entrySet()) {
            System.out.println(entry.getKey() + " -> " + entry.getValue());
        }
        // Order is not guaranteed. For small Integer keys you'll often see them
        // print in ascending order (1, 2, 3) - because Integer.hashCode() equals
        // the value itself, which maps directly to bucket index. That is NOT the
        // same thing as insertion order (3, 1, 2), and it is not a contract you
        // can rely on - it changes with key types, hash collisions, and resizing.
    }
}

What this evaluates: Conceptual clarity. Small integer keys often print in a way that looks ordered by coincidence, which is precisely what makes this mistake so easy to carry into production undetected.


3What is a fail-fast iterator, and how do you avoid ConcurrentModificationException?

Java · FailFastIteratorDemo.java
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;


public class FailFastIteratorDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Ravi", "Asha", "Meera"));


        Iterator<String> iterator = names.iterator();
        while (iterator.hasNext()) {
            String name = iterator.next();
            if (name.equals("Ravi")) {
                iterator.remove(); // safe - goes through the iterator, which resyncs its internal state
            }
        }
        System.out.println(names); // [Asha, Meera]
    }
}

Iterator.remove() is the only safe way to remove elements while iterating — it updates the iterator's own bookkeeping so the next next() call doesn't notice anything changed. Removing directly from the list underneath an active iterator is the classic mistake:

Java · ConcurrentModificationMistake.java
import java.util.ArrayList;
import java.util.List;


public class ConcurrentModificationMistake {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>(List.of("Ravi", "Asha", "Meera"));


        for (String name : names) {
            if (name.equals("Ravi")) {
                names.remove(name); // structural change during for-each - fail-fast detects it
            }
        }
        System.out.println(names);
        // Throws java.util.ConcurrentModificationException on the next iteration -
        // the enhanced for-loop uses an Iterator under the hood, and names.remove()
        // desyncs its internal modCount without going through that iterator.
    }
}

What this evaluates: Correctness. This is a compiles-clean, fails-at-runtime bug. A strong candidate names the fix (iterator.remove(), or collecting matches into a separate list to remove after the loop) without needing to see the stack trace first.

4What is the difference between Comparable and Comparator?

Java · ComparableVsComparatorDemo.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


class Employee implements Comparable<Employee> {
    String name;
    int age;


    Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.age, other.age); // natural ordering: by age
    }


    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}


public class ComparableVsComparatorDemo {
    public static void main(String[] args) {
        List<Employee> employees = new ArrayList<>(List.of(
            new Employee("Ravi", 35),
            new Employee("Asha", 28),
            new Employee("Meera", 42)
        ));


        Collections.sort(employees); // uses compareTo() - natural ordering, by age
        System.out.println(employees); // [Asha(28), Ravi(35), Meera(42)]


        employees.sort(Comparator.comparing(e -> e.name)); // Comparator - an alternate ordering, by name
        System.out.println(employees); // [Asha(28), Meera(42), Ravi(35)]
    }
}

Comparable is implemented by the class itself and defines one natural ordering. Comparator is external and lets you define as many orderings as you need without touching the class. The mistake is not having either when you need one:

Java · NotComparableMistake.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


class Product { // does not implement Comparable
    String name;
    double price;


    Product(String name, double price) {
        this.name = name;
        this.price = price;
    }
}


public class NotComparableMistake {
    public static void main(String[] args) {
        List<Product> products = new ArrayList<>();
        products.add(new Product("Pen", 20));
        products.add(new Product("Notebook", 60));


        Collections.sort(products);
        // Does not compile: Product does not implement Comparable<Product>,
        // so Collections.sort(List<T>) has no natural ordering to fall back on.
        // Fix: implement Comparable on Product, or call
        // products.sort(Comparator.comparingDouble(p -> p.price)) instead.
    }
}

What this evaluates: Design judgment. A strong answer doesn't just know the fix, it knows when to reach for Comparable (one obvious natural order, e.g. price) versus Comparator (multiple situational orders, or a class you can't modify).

5How does HashMap actually work internally?

Java · HashMapInternalsDemo.java
import java.util.HashMap;
import java.util.Map;


public class HashMapInternalsDemo {
    // Reproduces HashMap's internal bucket-index formula for a table of the given capacity
    static int bucketIndex(int hash, int capacity) {
        int spread = hash ^ (hash >>> 16); // HashMap "spreads" high bits into the low bits
        return spread & (capacity - 1); // capacity is always a power of two, so this is a fast modulo
    }


    public static void main(String[] args) {
        int capacity = 16; // HashMap's default initial capacity


        System.out.println(bucketIndex(3, capacity)); // 3
        System.out.println(bucketIndex(19, capacity)); // 3 - collides with 3 in a 16-bucket table


        Map<Integer, String> table = new HashMap<>();
        table.put(3, "three");
        table.put(19, "nineteen"); // same bucket as 3 - stored alongside it in that bucket's list
        System.out.println(table.get(19)); // nineteen - equals() disambiguates within the bucket
    }
}

A HashMap computes a key's hash, spreads its bits to reduce collisions, then masks it down to a bucket index within the current table size. Two different keys can land in the same bucket. HashMap resolves that by chaining entries in a list per bucket (or a tree, once a bucket gets crowded) and calling equals() to find the exact match. The mistake that breaks this whole mechanism is mutating a key after it's already been inserted:

Java · MutableKeyMistake.java
import java.util.HashMap;
import java.util.Map;


class MutableKey {
    int value;
    MutableKey(int value) { this.value = value; }


    @Override
    public int hashCode() { return value; }


    @Override
    public boolean equals(Object o) {
        return o instanceof MutableKey && ((MutableKey) o).value == this.value;
    }
}


public class MutableKeyMistake {
    public static void main(String[] args) {
        Map<MutableKey, String> map = new HashMap<>();
        MutableKey key = new MutableKey(3);
        map.put(key, "original bucket");


        key.value = 5; // mutating the key after insertion changes its hash code


        System.out.println(map.get(key)); // null - looks in bucket 5; the entry is still in bucket 3
        System.out.println(map.get(new MutableKey(3))); // null too - bucket 3 has an entry, but it no longer equals 3
        System.out.println(map.size()); // 1 - the entry still exists internally, just unreachable
    }
}

What this evaluates: Conceptual depth. Most candidates can say "HashMap uses hashing", but fewer can explain the spread-and-mask step, or predict that mutating a key strands its entry in the wrong bucket, permanently unreachable, without throwing any error at all.


6What is the difference between HashSet, LinkedHashSet, and TreeSet?

Java · SetImplementationsDemo.java
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;


public class SetImplementationsDemo {
    public static void main(String[] args) {
        Set<Integer> linkedHashSet = new LinkedHashSet<>();
        Set<Integer> treeSet = new TreeSet<>();


        int[] values = {30, 10, 20, 10}; // 10 is a duplicate
        for (int v : values) {
            linkedHashSet.add(v);
            treeSet.add(v);
        }


        System.out.println(linkedHashSet); // [30, 10, 20] - insertion order, duplicate dropped
        System.out.println(treeSet); // [10, 20, 30] - sorted order, duplicate dropped
    }
}

Evaluates: Conceptual clarity. All three enforce uniqueness, but only LinkedHashSet preserves insertion order and only TreeSet guarantees sorted order; plain HashSet guarantees neither.


7What is the difference between Iterator and ListIterator?

Java · ListIteratorDemo.java
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;


public class ListIteratorDemo {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>(List.of("apple", "banana", "cherry"));


        ListIterator<String> it = fruits.listIterator();
        while (it.hasNext()) {
            String fruit = it.next();
            if (fruit.equals("banana")) {
                it.set("blueberry"); // Iterator can only remove; ListIterator can also replace and insert
            }
        }


        while (it.hasPrevious()) { // ListIterator can walk backward too - plain Iterator cannot
            System.out.print(it.previous() + " ");
        }
        // cherry blueberry apple
    }
}

Evaluates: Conceptual clarity. ListIterator extends Iterator with backward traversal, in-place replacement (set), and insertion (add), none of which plain Iterator supports.


8What is the difference between HashMap, Hashtable, and ConcurrentHashMap?

Java · HashMapVsHashtableDemo.java
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public class HashMapVsHashtableDemo {
    public static void main(String[] args) {
        Map<String, String> hashMap = new HashMap<>();
        hashMap.put(null, "allowed as a key"); // HashMap permits one null key, and null values
        System.out.println(hashMap.get(null)); // allowed as a key


        Map<String, String> concurrentMap = new ConcurrentHashMap<>();
        try {
            concurrentMap.put(null, "not allowed");
        } catch (NullPointerException e) {
            System.out.println("ConcurrentHashMap rejects null keys/values");
        }


        Map<String, String> hashtable = new Hashtable<>();
        try {
            hashtable.put(null, "not allowed either");
        } catch (NullPointerException e) {
            System.out.println("Hashtable rejects null keys/values too");
        }
    }
}

Evaluates: Conceptual clarity. HashMap is unsynchronized and null-permissive; Hashtable is legacy and fully synchronized (one lock for the whole map); ConcurrentHashMap is the modern thread-safe choice, using fine-grained locking and rejecting nulls outright, since a null can't disambiguate "absent" from "present with a null value" under concurrent access.


9What is a PriorityQueue, and how does it order elements?

Java · PriorityQueueDemo.java
import java.util.PriorityQueue;
import java.util.Queue;


public class PriorityQueueDemo {
    public static void main(String[] args) {
        Queue<Integer> minHeap = new PriorityQueue<>(); // natural ordering: smallest first
        minHeap.add(30);
        minHeap.add(10);
        minHeap.add(20);


        System.out.println(minHeap.poll()); // 10 - smallest, not first-in
        System.out.println(minHeap.poll()); // 20
        System.out.println(minHeap.poll()); // 30
    }
}

Evaluates: Conceptual clarity. PriorityQueue is not FIFO; it's a heap that always returns the smallest element first (or, with a custom Comparator, the "highest priority" one), regardless of insertion order.


10What is the difference between Collections.synchronizedList and CopyOnWriteArrayList?

Java · ThreadSafeListDemo.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


public class ThreadSafeListDemo {
    public static void main(String[] args) {
        List<Integer> synced = Collections.synchronizedList(new ArrayList<>(List.of(1, 2, 3)));
        List<Integer> cow = new CopyOnWriteArrayList<>(List.of(1, 2, 3));


        // synchronizedList: iterating still requires manual synchronization to be safe
        synchronized (synced) {
            for (int v : synced) {
                System.out.print(v + " ");
            }
        }
        System.out.println();


        // CopyOnWriteArrayList: safe to iterate without manual locking - it iterates
        // over a snapshot array taken when the iterator was created
        for (int v : cow) {
            System.out.print(v + " ");
        }
        System.out.println();
        // 1 2 3
        // 1 2 3
    }
}

Evaluates: Design judgment. synchronizedList locks every method call but still needs manual synchronization during iteration to avoid ConcurrentModificationException; CopyOnWriteArrayList iterates safely without locking by copying its backing array on every write, which suits read-heavy, write-rare workloads and is a poor fit for write-heavy ones.


11What is the difference between poll(), peek(), and remove() on a Queue?

Java · QueueMethodsDemo.java
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;


public class QueueMethodsDemo {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();


        System.out.println(queue.poll()); // null - empty queue, poll() fails safely
        try {
            queue.remove(); // remove() throws on an empty queue instead
        } catch (NoSuchElementException e) {
            System.out.println("remove() threw NoSuchElementException on an empty queue");
        }


        queue.add(1);
        queue.add(2);
        System.out.println(queue.peek()); // 1 - looks without removing
        System.out.println(queue.size()); // 2 - confirms peek() did not remove
        System.out.println(queue.poll()); // 1 - removes and returns the head
        System.out.println(queue.size()); // 1 - only element 2 is left
    }
}

Evaluates: Correctness. Queue offers two method families for the same operations: poll()/peek() return a sentinel (null) on an empty queue, while remove()/element() throw. Reaching for the wrong family is a common source of unexpected NullPointerExceptions or NoSuchElementExceptions.


12How do you sort a list of custom objects in Java?

Java · SortCustomObjectsDemo.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;


class Student {
    String name;
    double gpa;


    Student(String name, double gpa) {
        this.name = name;
        this.gpa = gpa;
    }


    @Override
    public String toString() {
        return name + "(" + gpa + ")";
    }
}


public class SortCustomObjectsDemo {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>(List.of(
            new Student("Ravi", 8.2),
            new Student("Asha", 9.1),
            new Student("Meera", 7.8)
        ));


        students.sort(Comparator.comparingDouble((Student s) -> s.gpa).reversed());
        System.out.println(students); // [Asha(9.1), Ravi(8.2), Meera(7.8)]
    }
}

Evaluates: Idiom. Building a Comparator with Comparator.comparingDouble(...).reversed() instead of hand-writing a compare method, without touching the Student class itself.


13What is the difference between Set and List?

Java · SetVsListDemo.java
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class SetVsListDemo {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(5);
        list.add(3);
        System.out.println(list); // [5, 5, 3] - order preserved, duplicates allowed


        Set<Integer> set = new HashSet<>();
        set.add(5);
        set.add(5);
        set.add(3);
        System.out.println(set.size()); // 2 - the duplicate is silently dropped
        System.out.println(list.get(0)); // 5 - List supports index access
        // set.get(0) would not even compile - Set has no index-based access at all
    }
}

Evaluates: Correctness.List is ordered, index-accessible, and permits duplicates; Set guarantees uniqueness and, depending on implementation, may guarantee no order and no index access whatsoever.


14How do you make a collection unmodifiable in Java?

Java · UnmodifiableCollectionDemo.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class UnmodifiableCollectionDemo {
    public static void main(String[] args) {
        List<Integer> mutable = new ArrayList<>(List.of(1, 2, 3));
        List<Integer> readOnly = Collections.unmodifiableList(mutable);


        try {
            readOnly.add(4);
        } catch (UnsupportedOperationException e) {
            System.out.println("readOnly rejected the write");
        }


        mutable.add(4); // the backing list can still be changed directly
        System.out.println(readOnly); // [1, 2, 3, 4] - the "view" reflects the change
    }
}

Evaluates: Design judgment. Collections.unmodifiableList blocks writes through that specific reference, but it's a live view over the original list, not an immutable copy; the backing list can still be mutated directly. For a genuine immutable snapshot, List.copyOf(mutable) is the right call instead.

15What is the difference between an array and an ArrayList?

Java · ArrayVsArrayListDemo.java
import java.util.ArrayList;
import java.util.List;


public class ArrayVsArrayListDemo {
    public static void main(String[] args) {
        int[] array = new int[3]; // fixed size, holds primitives directly, size fixed at creation
        array[0] = 10;
        array[1] = 20;
        array[2] = 30;
        System.out.println(array.length); // 3


        List<Integer> list = new ArrayList<>(); // grows dynamically, only holds objects
        list.add(10);
        list.add(20);
        list.add(30);
        list.add(40); // arrays can't do this without manual resizing
        System.out.println(list.size()); // 4
    }
}

Evaluates: Conceptual clarity. An array is a fixed-size, low-level construct that holds primitives directly; ArrayList is a resizable object built on top of an array internally, but it only holds reference types (autoboxing int to Integer as needed), which carries real memory and performance cost at scale.

How should you prepare for a Java Collections interview?

  1. Match the implementation to the access pattern, out loud. For every question here, be ready to say "I'd pick X because the workload is mostly Y" — that's the actual thing being tested, not the implementation's name.
  2. Know which contracts the compiler won't enforce for you. Fail-fast iteration, key mutability, null handling across Map types — these all compile cleanly and fail (or silently corrupt) at runtime.
  3. Run the mistakes, not just the fixes. Seeing ConcurrentModificationException or a stranded HashMap entry once in a sandbox saves you from debugging it cold in production.


FAQ


What are the most commonly asked Java Collections interview questions? ArrayList vs LinkedList, the Map implementations (HashMap, LinkedHashMap, TreeMap), fail-fast iteration and ConcurrentModificationException, and Comparable vs Comparator come up most consistently, followed closely by HashMap's internal hashing mechanism.


Why does ConcurrentModificationException happen, and how do I avoid it? It's thrown when a collection is structurally modified directly while an active iterator (including the one behind an enhanced for-loop) is still walking it. Use Iterator.remove() instead of the collection's own remove(), or collect the items to remove into a separate list and remove them after the loop.


Does HashMap guarantee any ordering? No. LinkedHashMap guarantees insertion order and TreeMap guarantees sorted order — plain HashMap guarantees neither, even if small integer keys often happen to print in what looks like sorted order.


When should I use TreeMap instead of HashMap? When you need keys in sorted order for iteration, range queries, or "closest key" lookups. The trade-off is O(log n) operations instead of HashMap's average O(1).


How long does it take to prepare for a Java Collections interview? Less about breadth and more about depth on a handful of implementations. Being able to explain why HashMap behaves the way it does, from hashing through collision resolution, covers a disproportionate share of what gets asked.