10 Java Collections Follow-Up Questions: Views, Races, and Ordering

Practice Java Collections follow-ups with six runnable examples covering list views, stable keys, atomic updates, sorted sets, priority queues, and generics.

Topics: Java, Collections, Concurrency, Generics

Author: PracHub

Published: 10/21/2026

10 Java Collections Follow-Up Questions: Views, Races, and Ordering

October 21, 2026
11 min read
10 Java Collections Follow-Up Questions: Views, Races, and Ordering

Quick Overview

Practice Java Collections follow-ups with six runnable examples covering list views, stable keys, atomic updates, sorted sets, priority queues, and generics.

JavaCollectionsConcurrencyGenerics
Software EngineerFree

Java Collections follow-up questions test what happens after a definition stops being enough. A list is unmodifiable, but can its contents still change? A map is thread-safe, but can an increment lose an update? A comparator sorts correctly, but can it silently discard a distinct value in a set?

Use these ten scenarios after the introductory Java Collections questions and runnable examples. This guide concentrates on ownership, operation boundaries, and counterexamples. The examples are independent teaching programs using synthetic data, not solutions to the linked interview questions.

The code uses Java 17-compatible features and was checked with a Java 21 toolchain. Save one Java block at a time as Example.java, then run javac Example.java and java Example. Predict the output first. Oracle's Collections Framework overview separates interfaces from implementations; remember that Map belongs to the framework but does not extend Collection.

1. Is a fixed-size list the same as an unmodifiable snapshot?

No. Ask whether callers can replace elements, whether the size can change, and whether another reference can change the backing data.

ConstructionReplace an element?Add or remove?Later backing changes visible?
Arrays.asList(array)Yes, through setNoYes, array and list share storage
Collections.unmodifiableList(list)No, through this viewNo, through this viewYes, changes through the backing list
List.copyOf(list)NoNoSubsequent changes to the source list do not change its elements or size
import java.util.*;

public class Example {
    public static void main(String[] args) {
        String[] array = {"draft", "ready"};
        List<String> fixed = Arrays.asList(array);
        List<String> view = Collections.unmodifiableList(fixed);
        List<String> snapshot = List.copyOf(fixed);
        fixed.set(0, "published");
        System.out.println(Arrays.toString(array));
        System.out.println(view);
        System.out.println(snapshot);
    }
}
[published, ready]
[published, ready]
[draft, ready]

List.of and List.copyOf reject null elements. Neither promises deep immutability of referenced objects. A list containing a mutable object can expose that object's changing state even when list mutations are forbidden. See the List factories, array-backed list contract, and unmodifiable wrapper.

Follow-up: which contract would you choose for a configuration result that must not reflect later edits to its source list? Explain separately whether its element objects need copying.

2. Does using a record automatically make a safe HashMap key?

A record generates equality and hashing from its components, but a component can reference mutable state. If that state affects equality or hashing, changing it after insertion violates the assumptions of a hash-based key.

Here the key takes ownership of a stable list of immutable String elements:

import java.util.*;

public class Example {
    record Key(List<String> tags) {
        Key {
            tags = List.copyOf(tags);
        }
    }

    public static void main(String[] args) {
        List<String> input = new ArrayList<>(List.of("java"));
        Key key = new Key(input);
        Map<Key, String> cache = new HashMap<>();
        cache.put(key, "kept");
        input.add("concurrency");
        System.out.println(key.tags());
        System.out.println(cache.get(new Key(List.of("java"))));
    }
}
[java]
kept

Equal objects must have equal hash codes; unequal objects may collide. Hashing does not uniquely identify a key. Nor must every field used by equality necessarily contribute to hashing: omitting information can increase collisions while still satisfying the equality/hash contract. Including changing or inconsistent information can break it.

The defensive copy here stabilizes the list structure; it would not freeze mutable element objects. Consult the record reference and Object equality/hash contract. Extend the scenario with the public prompt for hash collisions and Java HashMap complexity.

3. Why can a ConcurrentHashMap counter still lose increments?

Thread-safe calls do not automatically make a sequence of calls atomic. Two workers can both read zero, both calculate one, and both store one. Every individual access can be valid while the combined increment loses an update.

This deterministic simulation models that interleaving, then uses a per-key atomic operation:

import java.util.concurrent.ConcurrentHashMap;

public class Example {
    public static void main(String[] args) {
        ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
        counts.put("jobs", 0);
        int firstRead = counts.get("jobs");
        int secondRead = counts.get("jobs");
        counts.put("jobs", firstRead + 1);
        counts.put("jobs", secondRead + 1);
        System.out.println(counts.get("jobs"));

        counts.put("jobs", 0);
        counts.merge("jobs", 1, Integer::sum);
        counts.merge("jobs", 1, Integer::sum);
        System.out.println(counts.get("jobs"));
    }
}
1
2

This is an interleaving model, not a multithreaded stress test. The atomic guarantee for each merge invocation comes from the ConcurrentHashMap API. It does not make several keys or external side effects one transaction. A thread-safe map also does not automatically protect a mutable value stored inside it.

For shared mutable HashMap state, define an appropriate synchronization policy. A map fully populated before safe publication and thereafter used only by readers needs no per-access locking. Safe publication must establish the necessary visibility; see the Java memory model. Hashtable synchronizes individual calls and rejects null keys and values; ConcurrentHashMap also rejects nulls. HashMap permits them. For a synchronized wrapper, follow its documented traversal locking rules.

Practice Assess HashMap vs ConcurrentHashMap. State the required atomic boundary before discussing contention or throughput.

4. Must every HashMap collision chain become a tree at eight entries?

Do not present one implementation threshold as a universal guarantee. First describe the contract: expected constant-time basic operations require suitable hash distribution; iteration order is unspecified, and capacity affects traversal cost.

Then distinguish a version-specific implementation. OpenJDK 21 HashMap source defines a treeification threshold of 8 and a minimum table capacity of 64. Its insertion path and capacity checks matter; a short “the eighth item always creates a tree” answer misses those conditions. A small table may resize instead.

Likewise, a default load factor of 0.75 is a sizing trade-off, not a proof of a fixed latency for every insertion. Avoid promising worst-case constant-time operations or a universal logarithmic bound for every pathological key type. Follow the HashMap documentation and identify any implementation assumptions.

Follow-up: what would you measure before pre-sizing a large map? Consider expected mappings, memory, resize work, iteration frequency, and collision patterns. Continue with HashMap collisions, mutable keys, and concurrency.

5. When does LinkedList's cheap insertion fail to improve the workload?

When locating each insertion point dominates the work. Inserting beside a position already held by a ListIterator differs from inserting at an arbitrary numeric index that must first be reached.

For ArrayList, indexed reads are O(1), append is amortized O(1), and a middle insertion may shift O(n) elements. LinkedList's indexed access traverses nodes; a loop of repeated get(i) calls can therefore take O(n²) overall. Iterator traversal avoids repeatedly searching from an endpoint.

The ArrayList API and LinkedList API describe their behavior. The array holds references together; that does not mean all referenced objects live contiguously. Allocation and access locality can matter, but use workload measurements before claiming a universal speed advantage.

Follow-up: describe a benchmark's size, operation mix, warmup, and allocation behavior. If the actual requirement is FIFO processing, consider a deque instead of a list with arbitrary indexed operations. Connect the choice to the coding interview patterns you need to implement.

6. Why did TreeSet discard a different object?

Sorted sets use comparison returning zero to determine whether an element is already represented. HashSet instead uses equality and hashing. LinkedHashSet preserves ordinary insertion order while deduplicating with equality and hashing.

A price-only comparator collapses distinct products with the same price:

import java.util.*;

public class Example {
    record Product(String sku, int price) {}

    public static void main(String[] args) {
        List<Product> products = List.of(
            new Product("B", 10), new Product("A", 10));
        Comparator<Product> byPrice = Comparator.comparingInt(Product::price);
        Set<Product> collapsed = new TreeSet<>(byPrice);
        collapsed.addAll(products);
        Set<Product> distinct = new TreeSet<>(
            byPrice.thenComparing(Product::sku));
        distinct.addAll(products);
        System.out.println(collapsed.size());
        System.out.println(distinct.stream().map(Product::sku).toList());
    }
}
1
[A, B]

The second comparator compares all components of this record, so comparison equality matches its generated equality. In another domain, adding an arbitrary field may still disagree with the intended identity. Decide whether uniqueness means SKU, complete product value, or one representative per price before choosing the structure.

TreeSet's contract explains equality consistency and O(log n) basic operations. A comparator that intentionally groups unequal values is possible, but the resulting set does not obey the general equality-based Set contract.

7. How can a correct comparator chain reverse the wrong fields?

Comparable defines a natural order on the type; Comparator defines an external order. A requirement such as “department ascending, salary descending within department” usually belongs in a caller-selected comparator.

Apply reversed() to the salary comparator before attaching it. Reversing the finished department-and-salary chain reverses department order too. Explain the receiver of the method call rather than assuming indentation changes its meaning.

Also avoid subtraction to compare integers: overflow can produce the wrong sign. Use Integer.compare, Long.compare, or the corresponding comparator factories. Check transitivity, equal values, extreme numbers, and null handling where nulls are allowed. These requirements come from the Comparator contract.

Follow-up: must ties preserve input order, establish deterministic processing order, or express membership identity? These are different requirements. A tie-breaker can establish deterministic order when it uniquely distinguishes the relevant items; it does not automatically make an inconsistent equality policy correct.

8. Does an iterator surviving a write mean iteration is safe?

No. “Fail-safe” is informal shorthand that hides several different contracts.

Iterator behaviorExampleWhat you can rely on
Fail-fast, best effortArrayList iteratorSome invalid structural modifications are detected; no synchronization guarantee
Weakly consistentConcurrentHashMap iteratorConcurrent updates do not cause ConcurrentModificationException; no whole-map snapshot
SnapshotCopyOnWriteArrayList iteratorTraverses the array state captured at iterator creation

A single thread can invalidate its own ordinary iterator by structurally modifying the list through another path. Use supported iterator removal, or a suitable bulk operation such as removeIf, when changing an ordinary mutable list during a traversal. Not every iterator supports removal.

The CopyOnWriteArrayList contract specifies snapshot traversal and unsupported iterator mutation. The snapshot holds element references, not deeply frozen objects. Writes copy its backing array, so read-heavy, rarely changed lists are a more plausible fit than a write-heavy buffer.

Practice Implement iterators and write tests in Java. Test empty iteration, exhaustion, supported mutation, and the exact visibility behavior you promise; do not build correctness around whether a concurrent-modification exception happened to appear.

9. Does PriorityQueue preserve FIFO order for equal priorities?

No. Equal-priority ordering is unspecified unless your comparison expresses an additional order. A PriorityQueue also does not promise sorted iteration. Repeated poll selects successive heads; a for-each loop is a different operation.

This example uses a unique submission sequence to break equal-deadline ties:

import java.util.*;

public class Example {
    record Job(String name, int deadline, long sequence) {}

    public static void main(String[] args) {
        PriorityQueue<Job> jobs = new PriorityQueue<>(
            Comparator.comparingInt(Job::deadline)
                .thenComparingLong(Job::sequence));
        jobs.add(new Job("later", 20, 0));
        jobs.add(new Job("first", 10, 1));
        jobs.add(new Job("second", 10, 2));
        while (!jobs.isEmpty()) {
            System.out.println(jobs.poll().name());
        }
    }
}
first
second
later

The PriorityQueue API documents O(log n) offer/poll, O(1) peek, and O(n) arbitrary-value search/removal. The immutable job fields avoid changing an ordering key while it remains queued. A real concurrent scheduler would also need an appropriate concurrent structure and a safe sequence-allocation policy.

For plain FIFO or LIFO work, ArrayDeque expresses queue or stack operations without priority ordering. It rejects nulls and needs external protection for concurrent access. Stack is a legacy alternative; prefer the Deque abstraction for new stack code.

Extend this scenario with a priority queue supporting custom ordering and stable ties.

10. Does PECS make an extends list immutable?

No. Producer Extends, Consumer Super describes type-safe access through a reference. It does not establish ownership, thread safety, or an unmodifiable runtime object.

A List of Integer cannot be assigned to a List of Number: that would allow inserting a Double into an Integer list. A wildcard lets a method accept a broader set of safe arguments:

import java.util.*;

public class Example {
    static <T> void appendAll(List<? super T> destination,
                              List<? extends T> source) {
        for (T value : source) {
            destination.add(value);
        }
    }

    public static void main(String[] args) {
        List<Integer> source = List.of(1, 2);
        List<Number> destination = new ArrayList<>();
        Example.<Number>appendAll(destination, source);
        destination.add(2.5);
        System.out.println(destination);
    }
}
[1, 2, 2.5]

This method appends and requires a destination supporting add. It assumes distinct source and destination collections with no backing alias and no concurrent mutation. With an extends-bounded reference, reading as T is safe, but adding an arbitrary non-null T is not; some operations such as clear can still be callable and may succeed on a mutable implementation. With a super-bounded reference, inserting T is type-safe, while reading only guarantees Object.

Oracle's wildcard guidelines describe these access directions. Do not confuse the example with Collections.copy, which replaces existing destination elements and requires enough destination size, not merely capacity.

Rehearse a requirement, a counterexample, and a limit

For each scenario, give a short answer, predict one observable result, and name a condition that changes the design. For example: a read-only view reflects backing edits; a shallow copy still shares element objects; a per-key update does not commit a multi-key transaction.

Use the Spring Boot interview guide to connect these contracts to service code, and the foundational collections guide for the broader implementation comparisons. Additional topic lists from CodeBegun and GeeksforGeeks offer further revision prompts; resolve API guarantees against the version-specific Oracle documentation linked above.

Finish practice by changing one requirement: permit nulls, require a stable processing order, share state between workers, or freeze the configuration result. Explain which assumption broke before choosing a replacement collection.

Keep practicing


Comments (0)