Chapter 15: Data Structure Selection and Implementation Trade-offs
15.03Beginner / Intermediate5 topics14 min2 interactive animations

Practical choices

Choosing between the standard collections: ArrayList or LinkedList, HashMap or TreeMap, HashSet or TreeSet, ArrayDeque or Stack, and a heap or an ordered map.

Learning goals

  • Choose ArrayList over LinkedList in almost every case, and know the exceptions
  • Pick a map or set by the operations it must support
  • Replace the legacy Stack with ArrayDeque
  • Decide between a PriorityQueue and a TreeMap

Prerequisites

Operation matricesMemory models
15.03.1

ArrayList vs LinkedList

Default to ArrayList. It has O(1) indexed access, amortised O(1) append, 4 bytes per element reference and contiguous storage. LinkedList offers O(1) insertion at a node you already hold, but you rarely hold one; finding the position costs O(n) and each element costs a 24-byte node. In practice ArrayList wins even for many insertions in the middle, because shifting a contiguous array is very fast.

Reach for LinkedList only when you need null-friendly queue/deque methods and do not mind the cost, and even then ArrayDeque is usually better. Compare element access in the animation.

NeedChoose
Random access, iteration, appendingArrayList
Queue or dequeArrayDeque
Insert/remove while iterating with an IteratorLinkedList (rarely worth it)
Fixed number of primitivesint[]

Reading element k: array vs linked list

Interactive · edit the input

Your input

Try

array memory (value, address)

12@1000
7@1004
31@1008
4@1012
19@1016
8@1020
25@1024
Setup

The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.

Step 1 / 3
Java
1static int getFromArray(int[] arr, int k) {
2 return arr[k]; // address = base + k * 4: one memory read
3}
4
5static int getFromList(Node head, int k) {
6 Node cur = head;
7 for (int i = 0; i < k; i++) {
8 cur = cur.next; // pointer chase: one read per hop
9 }
10 return cur.value;
11}

Live state

0 memory reads

k

5

base

1000

No condition evaluated on this step
Execution trace (dry run table)Show 3 steps
StepLineStateWhat happens
1L1k=5 base=1000The 7 values sit in consecutive memory cells, 4 bytes apart, starting at address 1000.
2L2k=5 address=1020Address = 1000 + 5 × 4 = 1020. One multiplication and one addition give the location, no matter how large k is.
3L2return=8return 8 with a single memory read: O(1) random access.
15.03.2

HashMap vs TreeMap

Use HashMap when you only look keys up: O(1) expected, smaller, faster. Use TreeMap when you need sorted iteration or order queries: firstKey, floorKey, ceilingKey, subMap, headMap. LinkedHashMap is the third choice: hash speed plus predictable insertion (or access) order.

The key requirements differ: HashMap needs hashCode and equals; TreeMap needs Comparable keys or a Comparator, and treats two keys as the same when the comparison returns 0.

HashMapTreeMap
get / put / removeO(1) expectedO(log n)
Iteration orderUnspecifiedSorted by key
floor / ceiling / rangeNot availableO(log n) (+ k)
Memory per entry (measured, excluding boxed key/value)32 bytes + table slot40 bytes
Null keyAllowedNot allowed
Java
1static void choose() {
2 Map<String, Integer> lookup = new HashMap<>(); // only exact lookups
3 TreeMap<Integer, String> schedule = new TreeMap<>(); // need "the next event at or after t"
4 schedule.put(900, "standup"); schedule.put(1400, "review");
5 System.out.println(schedule.ceilingEntry(1000).getValue()); // review
6}
15.03.3

HashSet vs TreeSet

The same split applies to sets. HashSet for membership and duplicate removal; TreeSet when elements must come out sorted or you need first, last, floor, ceiling and range views; LinkedHashSet when insertion order must be preserved.

A TreeSet uses the comparator, not equals, to detect duplicates. With a comparator that compares only string length, "aa" and "bb" are the same element and the second add is ignored: the set printed [c, aa] after adding aa, bb and c.

Java
1static void comparatorDecidesDuplicates() {
2 TreeSet<String> byLength = new TreeSet<>(Comparator.comparing(String::length));
3 byLength.add("aa"); byLength.add("bb"); byLength.add("c");
4 System.out.println(byLength); // [c, aa]: "bb" compared equal to "aa"
5}
15.03.4

ArrayDeque vs Stack

java.util.Stack is a legacy class: it extends Vector, so every method is synchronised (needless locking), it exposes list operations such as get(i) and add(i, x) that break the stack abstraction, and its iteration order is bottom to top. Use ArrayDeque through the Deque interface instead: push, pop and peek work at the front, it is faster and smaller, and it does not pretend to be a list.

One visible difference: after pushing 1, 2, 3, Stack prints [1, 2, 3] (bottom to top) while ArrayDeque prints [3, 2, 1] (top first). Both peek() the value 3. Stack.pop() on an empty stack throws EmptyStackException; ArrayDeque.pop() throws NoSuchElementException.

Java
1static void stacks() {
2 Deque<Integer> stack = new ArrayDeque<>();
3 stack.push(1); stack.push(2); stack.push(3);
4 System.out.println(stack + " " + stack.peek()); // [3, 2, 1] 3
5 int top = stack.pop(); // 3; pop() on an empty deque throws NoSuchElementException
6}
15.03.5

Heap vs ordered map

A PriorityQueue and a TreeMap both give you the smallest element quickly, but they are different tools. The heap does exactly one thing: peek the best element in O(1) and poll it in O(log n); it has the smallest overhead of the two (an array, 4 bytes per slot plus the element). The tree does everything the heap does and more (any element can be removed in O(log n), and floor, ceiling and ranges work), at the price of 40-byte entries and O(log n) for the minimum.

Rule of thumb: if you only ever take the minimum or maximum (top-k, scheduling, merging sorted lists, Dijkstra), use a PriorityQueue. If you also need to remove or update arbitrary elements, look up by key, or ask about neighbours and ranges, use a TreeMap/TreeSet.

OperationPriorityQueueTreeMap / TreeSet
Peek minimumO(1)O(log n)
Remove minimumO(log n)O(log n)
Remove arbitrary elementO(n)O(log n)
InsertO(log n)O(log n)
Floor / ceiling / rangeNot supportedO(log n) (+ k)
Iterate in sorted orderNot supported (repeated poll)O(n)

PriorityQueue: offer, poll and peek on a binary heap

Interactive · edit the input

Your input

Try

The heap is empty.

int[] arr · length 0

Being comparedJust movedRoot (next to leave)

polled so far (in order)

none yet
Setup

An empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2.

Step 1 / 26
Java
1Comparator<Integer> cmp = Comparator.naturalOrder(); // Comparator.reverseOrder() makes a max-queue
2PriorityQueue<Integer> pq = new PriorityQueue<>(cmp);
3pq.offer(x); // append at the end, then sift up
4Integer top = pq.poll(); // last element to the root, then sift down
5Integer next = pq.peek(); // the root only; nothing is removed

Live state

0 comparisons0 heap swaps

size

0

order

min first

No condition evaluated on this step
Execution trace (dry run table)Show 26 steps
StepLineStateWhat happens
1L2size=0 order=min firstAn empty min-queue. It is a binary heap stored in an array: the children of index i are 2i+1 and 2i+2.
2L3op=offer(5) size=1offer(5): append at the end (index 0), the only place that keeps the tree complete.
3L3op=offer(3) size=2offer(3): append at the end (index 1), the only place that keeps the tree complete.
4L3op=offer(3) child=3 parent=5The child has higher priority than its parent, so they swap.
5L3op=offer(3)Swapped: 3 moves up to index 0.
6L3op=offer(8) size=3offer(8): append at the end (index 2), the only place that keeps the tree complete.
7L3op=offer(8) child=8 parent=3The parent keeps priority over the child (ties do not swap): sift up stops.
8L3op=offer(1) size=4offer(1): append at the end (index 3), the only place that keeps the tree complete.
9L3op=offer(1) child=1 parent=5The child has higher priority than its parent, so they swap.
10L3op=offer(1)Swapped: 1 moves up to index 1.
11L3op=offer(1) child=1 parent=3The child has higher priority than its parent, so they swap.
12L3op=offer(1)Swapped: 1 moves up to index 0.
13L4op=poll() returned=1 size=3poll() returns the root 1. The last element (5) moves to the root to fill the gap, then sifts down.
14L4op=poll() node=5 better child=3The better child (3) beats the node, so they swap.
15L4op=poll()Swapped: 3 moves up to index 0.
16L3op=offer(4) size=4offer(4): append at the end (index 3), the only place that keeps the tree complete.
17L3op=offer(4) child=4 parent=5The child has higher priority than its parent, so they swap.
18L3op=offer(4)Swapped: 4 moves up to index 1.
19L3op=offer(4) child=4 parent=3The parent keeps priority over the child (ties do not swap): sift up stops.
20L4op=poll() returned=3 size=3poll() returns the root 3. The last element (5) moves to the root to fill the gap, then sifts down.
21L4op=poll() node=5 better child=4The better child (4) beats the node, so they swap.
22L4op=poll()Swapped: 4 moves up to index 0.
23L4op=poll() returned=4 size=2poll() returns the root 4. The last element (8) moves to the root to fill the gap, then sifts down.
24L4op=poll() node=8 better child=5The better child (5) beats the node, so they swap.
25L4op=poll()Swapped: 5 moves up to index 0.
26L5size=2 return=[5, 8]Heap array: [5, 8]. Only the root is guaranteed to be the best value; the rest is just heap-ordered, not sorted.

Practice ladder

Now try it yourself

Easy

  • Why is ArrayList usually faster than LinkedList for inserting in the middle?
  • Replace java.util.Stack with a Deque

Medium

  • My Calendar I
  • Kth Largest Element in a Stream

Advanced

  • Design a Leaderboard with update and top-K
  • Sliding Window Median

Key takeaways

  • Default to ArrayList, HashMap, HashSet and ArrayDeque; switch when a requirement names order or ranges.
  • TreeMap/TreeSet buy order and neighbour queries for O(log n) operations and bigger entries.
  • Prefer a PriorityQueue when only the best element matters; a TreeMap when arbitrary removals or ranges matter.