Skip to main content

Java Collections Framework: Key Differences

This guide provides a detailed comparison of common data structures in Java with performance characteristics and internal mechanics.

1. Array vs. ArrayList

FeatureArrayArrayList
SizeStatic (fixed at creation)Dynamic (auto-resizes)
Data TypesStores both primitives and objectsStores only objects (primitives are autoboxed)
PerformanceFaster (no boxing, no resize overhead)Slower during resizing operations
Length Check.length (field).size() (method)
DimensionsCan be multi-dimensional (int[][])Always single-dimensional (but can nest: List<List<>>)
Type SafetyRuntime ArrayStoreExceptionCompile-time generics checking
MemoryContiguous block, minimal overheadObject header + internal array + metadata

ArrayList Internal Resizing

When an ArrayList runs out of space, it creates a new array 1.5ร— the old size and copies all elements:

// Simplified from OpenJDK ArrayList.grow()
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5ร— growth
elementData = Arrays.copyOf(elementData, newCapacity); // O(n) copy!
}

Growth sequence: 10 โ†’ 15 โ†’ 22 โ†’ 33 โ†’ 49 โ†’ 73 โ†’ ...

Production tip: If you know the expected size, specify it to avoid O(n) resize copies:

// BAD: 10,000 elements โ†’ 17 resize operations from default capacity 10
List<String> list = new ArrayList<>();

// GOOD: Zero resize operations
List<String> list = new ArrayList<>(10_000);

2. ArrayList vs. Vector

FeatureArrayListVector
SynchronizationNot Synchronized (Not Thread-Safe)Synchronized (Thread-Safe)
PerformanceFast (no lock overhead)Slow (every method acquires a lock)
Growth FactorIncreases by 50% (oldCap + oldCap >> 1)Increases by 100% (doubles)
LegacyJava 1.2 (Collections Framework)Java 1.0 (legacy class)
IterationIterator onlyIterator and Enumeration
Modern Alternativeโ€”Collections.synchronizedList() or CopyOnWriteArrayList

Why Vector is deprecated in practice

Vector synchronizes every individual method call. But thread safety usually requires synchronizing compound operations (e.g., check-then-act), which Vector doesn't help with:

// Still BROKEN with Vector โ€” the compound operation is NOT atomic
Vector<String> v = new Vector<>();
if (!v.contains("item")) { // Thread A checks: false
// Thread B: also checks false, also enters this block
v.add("item"); // Duplicate added!
}

Modern alternatives:

  • Single-threaded: Use ArrayList
  • Read-heavy concurrent: Use CopyOnWriteArrayList
  • Write-heavy concurrent: Use Collections.synchronizedList(new ArrayList<>()) with external synchronization for compound operations

3. ArrayList vs. LinkedList

FeatureArrayListLinkedList
Internal StructureDynamic Array (contiguous memory)Doubly Linked List (scattered nodes)
Random Access get(i)O(1) โ€” direct index calculationO(n) โ€” traversal from head or tail
Add at endO(1) amortizedO(1)
Add in middleO(n) โ€” System.arraycopy() shiftO(n) traverse + O(1) insert
Remove in middleO(n) โ€” shift elements leftO(n) traverse + O(1) unlink
Memory per element~4-8 bytes (reference only)~40 bytes (Node: value + prev + next + header)
InterfacesList, RandomAccessList, Deque, Queue
Cache FriendlinessExcellent (contiguous memory)Poor (nodes scattered on heap)
Default Capacity10None (starts empty)

The Cache Locality Advantage (Critical for Interviews)

Modern CPUs have a prefetcher that loads adjacent memory into L1/L2 cache lines (typically 64 bytes). ArrayList's contiguous array benefits enormously:

ArrayList (contiguous): [elem0][elem1][elem2][elem3][elem4]...
โ†’ CPU cache line loads 8-16 references at once โ†’ sequential access is FAST

LinkedList (scattered): Node@0x100 โ†’ Node@0x500 โ†’ Node@0x200 โ†’ Node@0x800
โ†’ Each next() is a potential cache miss โ†’ 100-300 cycle penalty per access

Benchmarks consistently show: For lists under ~100,000 elements, ArrayList is faster than LinkedList for ALL operations โ€” including insertions in the middle. The O(n) System.arraycopy() is a highly optimized native memory block copy that is faster than traversing a linked list with cache misses.

When LinkedList actually wins

  1. Frequent removal during iteration โ€” Iterator.remove() is O(1) (just pointer update) vs. O(n) array shift for ArrayList
  2. Queue/Deque operations โ€” addFirst(), removeFirst(), addLast() are all O(1). ArrayList's add(0, e) is O(n).
  3. Very large lists with frequent mid-list mutations โ€” when the cost of shifting millions of elements exceeds the cache miss penalty

4. Comprehensive Comparison Table

FeatureArrayArrayListLinkedListVector
TypeFixed-sizeDynamicDynamicDynamic
Thread SafeNoNoNoYes
Random AccessO(1)O(1)O(n)O(1)
Add (end)N/AO(1)*O(1)O(1)*
Add (middle)N/AO(n)O(n)O(n)
RemoveN/AO(n)O(n)O(n)
Memory OverheadMinimalLowHigh (~5ร—)Low
Primitivesโœ…โŒ (autoboxing)โŒโŒ
GrowthNone1.5ร—N/A2ร—

* amortized โ€” occasional O(n) for resize

Decision Matrix: When to use what?

ScenarioBest ChoiceWhy
General purpose, most use casesArrayListO(1) random access, cache-friendly, good enough for everything
Need a Queue/DequeArrayDeque (NOT LinkedList)ArrayDeque is faster than LinkedList for both stack and queue operations
Thread-safe list, read-heavyCopyOnWriteArrayListLock-free reads, snapshot iterators
Thread-safe list, write-heavyCollections.synchronizedList()Lower overhead than CopyOnWrite for frequent writes
Fixed-size, primitive dataArrayNo autoboxing overhead, direct memory access
Constant-time removal during iterationLinkedListIterator.remove() is O(1)

๐Ÿ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%