Java Collection FAQ
Scope — the collections framework: the hierarchy,
ArrayListvsLinkedList,HashMapandConcurrentHashMapinternals, and the Big-O of every operation. See also:java_generics.md— the type parameters these APIs use;java_functional.md— streaming over them;java_multi_thread.md— the concurrent variants.
1) The Collection Hierarchy
text
Iterable
└─ Collection
├─ List → ordered, indexed, allows duplicates
│ ├─ ArrayList (resizable array)
│ ├─ LinkedList (doubly linked list; also a Deque)
│ └─ Vector (legacy, synchronized)
├─ Set → no duplicates
│ ├─ HashSet (unordered, backed by HashMap)
│ ├─ LinkedHashSet (insertion order)
│ └─ TreeSet (sorted, backed by TreeMap)
└─ Queue / Deque → FIFO / double-ended
├─ ArrayDeque (fast stack/queue)
├─ LinkedList
└─ PriorityQueue (heap-ordered)
Map (NOT a Collection)
├─ HashMap (unordered)
├─ LinkedHashMap (insertion / access order)
├─ TreeMap (sorted by key)
├─ Hashtable (legacy, synchronized)
└─ ConcurrentHashMap (thread-safe, high concurrency)
2) ArrayList vs LinkedList
| Operation | ArrayList | LinkedList |
|---|---|---|
| Get by index | O(1) |
O(n) |
| Add/remove at end | O(1) amortized |
O(1) |
| Add/remove at head/middle | O(n) (shift) |
O(1) if node known, else O(n) to find |
| Memory | Compact (array) | Extra pointers per node |
| Cache locality | Good | Poor |
Rule of thumb: use ArrayList by default. LinkedList is rarely the right
choice; use ArrayDeque for stack/queue needs instead.
ArrayListgrows by ~1.5x when full — presize withnew ArrayList<>(n)if the size is known to avoid repeated copies.
3) HashMap Internals
Backed by an array of buckets (Node[] table). Each bucket holds entries
whose keys hash to that index.
text
index = (n - 1) & hash(key) // n = table length (always a power of 2)
- hash spreading:
hash = h ^ (h >>> 16)mixes high bits into low bits so more of the hash influences the bucket index. - Collisions are chained:
- Java 7: linked list (new nodes prepended).
- Java 8+: linked list, then treeified to a red-black tree when a bucket has
≥ 8 entries and table size ≥ 64. Lookup in that bucket becomes
O(log n)instead ofO(n). Untreeifies back to a list when it shrinks to ≤ 6.
- Load factor (default
0.75): whensize > capacity * loadFactor, the table resizes (doubles capacity) and entries are rehashed. 0.75 balances space vs collision rate. - Default capacity: 16.
nullkeys: allowed (stored in bucket 0);nullvalues allowed too.- Not thread-safe — concurrent writes can corrupt the structure.
equals / hashCode contract (critical):
- If
a.equals(b)thena.hashCode() == b.hashCode(). - Unequal objects may share a hash (collision) but ideally don’t.
- Override both or hash-based collections break. Use immutable fields for keys.
4) HashSet, LinkedHashMap, TreeMap
- HashSet: a thin wrapper over a
HashMapwhere elements are keys and values are a dummy constant.O(1)average add/contains/remove; no order. - LinkedHashMap:
HashMap+ a doubly linked list threading entries in insertion (or access) order. Access-order mode makes it a ready-made LRU cache (overrideremoveEldestEntry). - TreeMap: red-black tree, keys kept sorted (natural order or
Comparator).O(log n)ops; supports range queries (floorKey,ceilingKey,subMap).
5) ConcurrentHashMap
Thread-safe map for high concurrency, far better than Hashtable /
Collections.synchronizedMap (which lock the whole map).
- Java 7: segment locking (lock striping) — the map split into segments, each independently locked.
- Java 8+: dropped segments. Uses CAS + synchronized on the head node of a bucket, so locking is per-bucket → high write concurrency. Reads are lock-free.
- Does not allow
nullkeys or values (ambiguous with “absent” in concurrent reads). - Atomic helpers:
putIfAbsent,computeIfAbsent,merge.
6) Big-O Cheat Sheet
| Structure | Access | Search | Insert | Delete | Notes |
|---|---|---|---|---|---|
| ArrayList | O(1) |
O(n) |
O(1)* |
O(n) |
*amortized at end |
| LinkedList | O(n) |
O(n) |
O(1) |
O(1) |
at known position |
| HashMap / HashSet | — | O(1) |
O(1) |
O(1) |
average; worst O(log n) since Java 8 |
| TreeMap / TreeSet | — | O(log n) |
O(log n) |
O(log n) |
sorted |
| ArrayDeque | O(1) ends |
O(n) |
O(1) ends |
O(1) ends |
best stack/queue |
| PriorityQueue | O(1) peek |
O(n) |
O(log n) |
O(log n) |
binary heap |
7) Common Gotchas
- Fail-fast iterators: modifying a collection during iteration (except via
Iterator.remove()) may throwConcurrentModificationException— the check is best-effort, so never write logic that depends on it. UseremoveIffor conditional removal. Arrays.asList()returns a fixed-size list backed by the array —add/removethrow.List.of(...)is fully immutable and rejectsnull;new ArrayList<>(List.of(...))when you need a mutable copy.- Prefer interfaces in declarations:
List<T> x = new ArrayList<>(). - Choose
ArrayDequeoverStack(legacy, synchronized) for stacks. - A mutable key breaks the map: change a field that
hashCode()uses after inserting and the entry can never be found again. - Collections store objects, so
List<Integer>boxes every element — use anint[]or a primitive stream in hot paths. HashMapiteration order is unspecified and changes on resize; useLinkedHashMapwhen order matters andTreeMapwhen sorting does.
8) Choosing, in One Table
| You need | Use |
|---|---|
| Indexed access, iteration | ArrayList |
| Stack, queue, deque | ArrayDeque |
Uniqueness, O(1) membership |
HashSet |
| Uniqueness + sorted / range queries | TreeSet |
| Key → value lookup | HashMap |
| Lookup + insertion order (or LRU) | LinkedHashMap |
Lookup + sorted keys, floor/ceiling |
TreeMap |
| Top-K, priority scheduling | PriorityQueue |
| A map shared across threads | ConcurrentHashMap |
| A list read constantly, written rarely | CopyOnWriteArrayList (every write copies the array) |
| Hand-off between producer and consumer threads | A bounded BlockingQueue |