Skip to content
Java core 3 min read

Collections Framework

The Java Collections Framework (JCF) is a unified set of interfaces and implementations for storing and manipulating groups of objects. Instead of hand-rolling resizable arrays or linked structures, you program against stable interfaces — List, Set, Map, Queue — and swap implementations freely. The framework lives in java.util and has anchored every non-trivial Java application since Java 2.

The Hierarchy

Two root abstractions sit at the top. Iterable (and its child Collection) model a group of elements; Map models key-to-value associations and deliberately stands apart — a map is not a Collection.

        Iterable
           |
       Collection
      /    |     \
   List   Set    Queue
    |      |        \
ArrayList HashSet  Deque
LinkedList TreeSet  ArrayDeque
          LinkedHashSet  PriorityQueue

Map  (separate root — not a Collection)
 |
HashMap, LinkedHashMap, TreeMap, ConcurrentHashMap

A Map is not a Collection. It exposes its contents as collections via keySet(), values(), and entrySet(), but you cannot pass a Map where a Collection is expected.

Choosing a Structure

Each top-level interface answers a different question: ordering, uniqueness, and access pattern.

InterfaceDuplicatesOrderingKey accessTypical use
ListAllowedInsertion (indexed)By indexOrdered sequences, positional access
SetForbiddenDepends on implBy elementUniqueness, membership tests
MapUnique keysDepends on implBy keyLookups, associations, caches
QueueAllowedFIFO / priorityBy head/tailPipelines, work queues, BFS

A quick decision guide:

  • Need positional order and indexed access?List (default ArrayList).
  • Need uniqueness or fast membership checks?Set (default HashSet).
  • Need to look something up by a key?Map (default HashMap).
  • Need FIFO/LIFO/priority processing?Queue/Deque (default ArrayDeque).

A Concrete Example

List<String> tasks = new ArrayList<>(List.of("build", "test", "build"));
Set<String> unique = new HashSet<>(tasks);
Map<String, Integer> counts = new HashMap<>();
for (String t : tasks) counts.merge(t, 1, Integer::sum);

System.out.println("tasks : " + tasks);
System.out.println("unique: " + unique);
System.out.println("counts: " + counts);

Output:

tasks : [build, test, build]
unique: [test, build]
counts: {test=1, build=2}

Note how the List preserved the duplicate build, the HashSet collapsed it, and the Map tallied occurrences.

Fail-Fast Iteration

Most java.util collections return fail-fast iterators: structurally modifying a collection while iterating (outside the iterator’s own remove) throws ConcurrentModificationException. This is a best-effort bug detector, not a concurrency guarantee.

List<Integer> nums = new ArrayList<>(List.of(1, 2, 3));
for (Integer n : nums) {
    if (n == 2) nums.remove(n);   // ConcurrentModificationException
}

Use Iterator.remove(), removeIf(...), or a concurrent collection instead.

Best Practices

  • Declare variables by interface type (List<T> x = ...), not implementation, so you can swap backends.
  • Pre-size collections (new ArrayList<>(expectedSize)) when the count is known to avoid resize churn.
  • Prefer immutable factories (List.of, Set.of, Map.of) for fixed data — they are compact and defensive.
  • Never store mutable objects in a HashSet/as HashMap keys if the fields used by hashCode() can change.
  • Reach for Collections.emptyList() / Map.of() over null to model “no elements.”

Interview Questions

Q: Why is Map not part of the Collection interface? A: A Collection models a group of single elements with a uniform add(E) contract. A Map stores pairs and is keyed, so a single add method does not fit. It instead exposes collection views (keySet, values, entrySet).

Q: What is the difference between fail-fast and fail-safe iterators? A: Fail-fast iterators (ArrayList, HashMap) track a modCount and throw ConcurrentModificationException on concurrent structural modification. Fail-safe iterators (CopyOnWriteArrayList, ConcurrentHashMap) iterate over a snapshot or tolerate concurrent change without throwing, at the cost of possibly stale data.

Q: How does HashMap work internally? A: It maintains an array of buckets. A key’s hashCode() is spread and masked to a bucket index; entries are stored in that bucket. Collisions chain in a linked list, which converts to a balanced tree (red-black) once a bucket exceeds 8 entries (and table size ≥ 64), giving O(log n) worst-case lookups instead of O(n).

Q: When would you choose a LinkedList over an ArrayList? A: Rarely. LinkedList only wins when you frequently insert/remove at the ends or via an iterator’s cursor. For nearly all index-based or random-access workloads, ArrayList is faster and more cache-friendly.

Last updated June 1, 2026
Was this helpful?