← All cheat sheets ·

Collections & Complexity

Java collection implementations, Big-O costs, selection rules, fail-fast behaviour, ConcurrentHashMap and immutable factories.

Implementations at a glance

Interface Implementation Backing structure Order Nulls Thread-safe
List ArrayList resizable Object[] (grows ~1.5x) insertion yes no
List LinkedList doubly linked nodes (also Deque) insertion yes no
List CopyOnWriteArrayList array copied on each write insertion yes yes
List Vector / Stack synchronized array (legacy) insertion yes yes (coarse)
Set HashSet HashMap keys none one null no
Set LinkedHashSet hash + linked list insertion one null no
Set TreeSet red-black tree sorted no (natural order) no
Set EnumSet bit vector enum ordinal no no
Map HashMap table of buckets (list, tree at 8+) none 1 null key, null values no
Map LinkedHashMap hash + linked list (access-order option) insertion/access yes no
Map TreeMap red-black tree sorted by key no null key no
Map ConcurrentHashMap CAS + per-bin synchronized none no null key/value yes
Map Hashtable synchronized buckets (legacy) none no nulls yes (coarse)
Queue ArrayDeque circular array FIFO/LIFO no no
Queue PriorityQueue binary min-heap by priority (iteration not sorted) no no
Queue ConcurrentLinkedQueue lock-free linked FIFO no yes
Queue LinkedBlockingQueue / ArrayBlockingQueue linked / bounded array FIFO no yes

Big-O table

Structure get / contains add remove Notes
ArrayList O(1) by index; O(n) contains O(1) amortized end; O(n) middle O(n) shifts via System.arraycopy
LinkedList O(n) O(1) at ends / at iterator O(1) at iterator; O(n) by index poor cache locality, rarely wins
HashSet / HashMap O(1) avg O(1) avg O(1) avg worst O(log n) with treeified bins (Java 8+), O(n) before
LinkedHashMap O(1) O(1) O(1) extra pointer per entry
TreeSet / TreeMap O(log n) O(log n) O(log n) also floor/ceiling/subMap
ArrayDeque O(n) search O(1) both ends O(1) both ends faster than LinkedList as stack/queue
PriorityQueue O(1) peek O(log n) offer O(log n) poll; O(n) remove(Object) heapify from collection is O(n)
CopyOnWriteArrayList O(1) O(n) O(n) reads lock-free, snapshot iterators
ConcurrentHashMap O(1) avg O(1) avg O(1) avg size() is an estimate under concurrency

When to use which

  • Default list: ArrayList. Default map: HashMap. Default set: HashSet. Default queue/stack: ArrayDeque.
  • Need insertion order for a set/map: LinkedHashSet / LinkedHashMap.
  • Need sorted order or range queries (headMap, ceilingKey): TreeMap / TreeSet (or ConcurrentSkipListMap for concurrent).
  • LRU cache: LinkedHashMap(cap, 0.75f, true) overriding removeEldestEntry.
  • Enum keys: EnumMap / EnumSet (array-backed, fastest).
  • Top-K / scheduling by priority: PriorityQueue; wrap comparator for max-heap (Comparator.reverseOrder()).
  • Read-mostly listener list, rare writes: CopyOnWriteArrayList.
  • Producer/consumer hand-off: BlockingQueue (bounded to get back-pressure).
  • Stack and Vector are legacy; use ArrayDeque.
Map<K, V> lru = new LinkedHashMap<>(16, 0.75f, true) {
    @Override protected boolean removeEldestEntry(Map.Entry<K, V> e) {
        return size() > MAX;
    }
};

HashMap internals

  • Index = (n - 1) & (h ^ (h >>> 16)); table length is always a power of two (default 16, load factor 0.75).
  • Resize doubles the table when size > threshold; entries split into "low" and "high" buckets (no rehash of hashCode).
  • Bucket becomes a red-black tree when chain >= 8 and table >= 64 (else it resizes instead); un-treeifies at <= 6.
  • Keys need consistent equals/hashCode; mutating a key after insertion loses the entry.
  • Pre-size to avoid resizes: HashMap.newHashMap(n) (Java 19+) or new HashMap<>((int) (n / 0.75f) + 1).
  • Java 7 and earlier: head insertion plus concurrent resize could create an infinite loop; fixed by tail insertion in Java 8, but HashMap is still not thread-safe.

Fail-fast vs fail-safe

Kind Examples Behaviour on structural modification during iteration
Fail-fast ArrayList, HashMap, HashSet, TreeMap, LinkedList ConcurrentModificationException via modCount check (best effort, not guaranteed)
Weakly consistent ConcurrentHashMap, ConcurrentLinkedQueue, ConcurrentSkipListMap never throws CME; may or may not reflect concurrent changes
Snapshot CopyOnWriteArrayList, CopyOnWriteArraySet iterates the array as of iterator creation; iterator remove() unsupported
list.removeIf(x -> x < 0);            // safe, single pass
for (Iterator<Integer> it = list.iterator(); it.hasNext();)
    if (it.next() < 0) it.remove();    // safe
// for (Integer x : list) list.remove(x);  -> CME (except removing the second-to-last element)
  • Changing a value of an existing key (put on an existing key) is not structural; no CME.

ConcurrentHashMap

  • Java 8+: no segments; CAS on empty bin head, synchronized on the bin head node otherwise. Java 7 used Segment locks.
  • Null keys and values are rejected (ambiguity of get() == null).
  • Individual operations are atomic; compound check-then-act sequences are not. Use atomic methods.
map.merge(word, 1, Integer::sum);                 // atomic counter
map.computeIfAbsent(k, key -> new ArrayList<>()).add(v);
map.putIfAbsent(k, v);
map.compute(k, (key, old) -> old == null ? 1 : old + 1);
long n = map.mappingCount();                       // prefer over size()
  • computeIfAbsent mapping function must be short and must not modify the same map (can deadlock or throw IllegalStateException).
  • LongAdder values (map.computeIfAbsent(k, x -> new LongAdder()).increment()) beat Integer merge under heavy contention.
  • Bulk ops with parallelism threshold: forEach(1, ...), reduce, search.

Immutable collections

Factory Java Nulls Mutation Notes
List.of(...), Set.of(...), Map.of(...) 9 NPE UnsupportedOperationException truly immutable; Set.of/Map.of reject duplicates (IllegalArgumentException); iteration order of Set/Map is randomized per JVM run
Map.ofEntries(entry(k,v), ...) 9 NPE UOE for more than 10 pairs
List.copyOf(c) / Set.copyOf / Map.copyOf 10 NPE UOE returns same instance if already immutable
stream.toList() 16 allowed UOE unmodifiable list, allows nulls
Collectors.toUnmodifiableList() 10 NPE UOE
Collections.unmodifiableList(l) 1.2 allowed UOE a view: changes to backing list show through
Arrays.asList(arr) 1.2 allowed set ok, add/remove UOE fixed-size, write-through to array
  • List.of(...).contains(null) throws NPE.
  • Java 21: SequencedCollection / SequencedMap add getFirst, getLast, reversed(), addFirst to List, Deque, LinkedHashSet, LinkedHashMap, SortedSet.

Sorting and comparators

list.sort(Comparator.comparing(Person::lastName)
                    .thenComparing(Person::age, Comparator.reverseOrder())
                    .thenComparing(Person::name, Comparator.nullsLast(Comparator.naturalOrder())));
Collections.sort(list);        // stable merge sort/TimSort, O(n log n)
Arrays.sort(int[])             // dual-pivot quicksort, not stable; Object[] uses TimSort (stable)
  • Comparator must be consistent with equals for TreeSet/TreeMap; they treat compare == 0 as the same key.
  • Never compute a - b in a comparator (overflow); use Integer.compare.

Pitfalls

  • ArrayList.remove(int) vs remove(Object): list.remove(1) removes index 1; use remove(Integer.valueOf(1)) for the value.
  • subList is a view; structural change to the parent invalidates it (CME).
  • HashSet<MutableKey>: mutating fields used in hashCode makes the element unfindable.
  • TreeMap with a non-comparable key throws ClassCastException on first put.
  • Collections.synchronizedList still needs manual locking for iteration.
  • PriorityQueue iterator/toString order is heap order, not sorted; poll repeatedly for sorted output.
  • Equal hashCode is required for equal objects; the reverse is not.