Java.Collections.Is the operation of adding an element to the middle (list.add(list.size()/2, newElement)) slower for ArrayList or for LinkedList?

✅ Adding an element to the middle is generally slower in LinkedList than in ArrayList

Wait… what? You might’ve heard that LinkedList is good for insertions — but here’s the full picture:


🔬 Let’s break it down:

📦 ArrayList.add(index, element) (middle)

  • Time complexity: O(n – index)
    • Needs to shift elements to the right to make space
  • But accessing index is fast: O(1)
  • Memory locality makes shifting very fast in practice (arrays are cache-friendly)

✅ Efficient in real-world scenarios for small/mid-sized lists


🧱 LinkedList.add(index, element) (middle)

  • Time complexity: O(n)
    • It must traverse the list from the beginning or end to find the node at that index
    • Then it links in the new node — that part is O(1)
  • But that traversal dominates performance

❌ Slower in practice, especially for large lists


🧪 Real-world test case:

List<Integer> arrayList = new ArrayList<>();
List<Integer> linkedList = new LinkedList<>();

for (int i = 0; i < 1_000_000; i++) {
    arrayList.add(arrayList.size() / 2, i); // Works fine, relatively fast
    linkedList.add(linkedList.size() / 2, i); // Much slower
}

This will kill performance on LinkedList because it has to walk through half the list each time and lookup objects.

🧠 Summary:

OperationArrayListLinkedList
Access middle (by index)✅ O(1)❌ O(n)
Insert in middle (index-based)⚠️ O(n) (shifting)❌ O(n) (traversing)
Practical performance✅ Faster❌ Slower

✅ Bottom line:

Even though LinkedList has constant-time insertion once you have the node, the cost of finding that node makes add(index, element) in the middle slower than ArrayList.

🔍 Why is ArrayList faster for inserting in the middle?

Even though ArrayList.add(index, element) needs to shift elements, it’s still usually faster than LinkedList, because:

✅ 1. Fast index access

  • Accessing any index in an array is O(1) — no traversal.
  • Shifting elements is just copying memory.

✅ 2. Tight memory layout = better CPU cache usage

This is where caching (aka cache locality) comes in 👇


🚀 What is CPU caching / cache locality?

TL;DR:

CPU cache is small, fast memory that holds recently used data to avoid slow RAM access.

How it helps ArrayList:

  • ArrayList stores elements in a contiguous block of memory (like an array).
  • So when the CPU accesses list[i], it likely also loads nearby elements into cache.
  • This makes reading/writing/shifting fast, because:
    • Memory is close together
    • CPU can bulk-load elements into cache lines (~64 bytes at a time)

👉 This is called spatial locality — memory near what you’re accessing is also brought in and reused.


❌ LinkedList suffers here:

  • Each node is a separate object on the heap.
  • Nodes can be scattered randomly in memory.
  • CPU can’t predict or prefetch nodes.
  • So it incurs cache misses, which are expensive.

➜ Even though insertion is O(1) after locating a node, the cost of traversing the list dominates due to bad cache behavior.

📈 Example:

ArrayList<Integer> list = new ArrayList<>();

for (int i = 0; i < 1_000_000; i++) {
    list.add(list.size() / 2, i);
}

This runs faster than the same code with LinkedList, even though each ArrayList.add() might shift thousands of elements — because those elements are right next to each other in memory.


🧠 Summary:

FeatureArrayList ✅LinkedList ❌
Index AccessO(1)O(n) (traverse)
Memory LayoutContiguousFragmented
CPU Cache Usage✅ Excellent❌ Poor
Insert in Middle (Practical)⚠️ Still faster❌ Slower

Does it have to do with memory alignment ?

🧠 Recap: What is memory alignment?

Memory alignment means storing data at memory addresses that are multiples of their size or system word size (e.g. 4 or 8 bytes). This ensures:

  • Fast memory access
  • Proper cache line usage
  • Less chance of misaligned access (which CPUs hate)

✅ How ArrayList benefits from alignment:

  1. Contiguous memory layout
    • Elements are stored in a raw array: Object[] elementData
    • Array elements are placed back-to-back in aligned memory
  2. Aligned references
    • Each object reference (like to an Integer, String, etc.) is stored at aligned memory addresses (e.g., every 8 bytes on a 64-bit JVM)
    • CPU can read multiple references in one cache line load
  3. Efficient prefetching
    • Because memory is aligned and predictable, the CPU can prefetch upcoming elements into cache automatically

💡 This is why shifting elements during add(index, element) in ArrayList is relatively fast — you’re just copying aligned memory blocks, which the CPU loves.


❌ In contrast: LinkedList

  • Each node is a separate object in memory with:
    • Its own object header
    • Pointers to prev, next, and the item
  • These nodes are scattered in memory (due to heap allocation)
  • CPU cannot prefetch
  • You get cache misses and slow traversal
  • Memory alignment still applies — but doesn’t help when the data is all over the place

📈 Visualization

ArrayList in memory (aligned):

| ref to 0 | ref to 1 | ref to 2 | ref to 3 | ...
   ↑         ↑          ↑          ↑
  aligned   aligned   aligned   aligned

LinkedList in memory (fragmented):

Node A: [data] -> [next]   (somewhere in RAM)
Node B: [data] -> [next]   (somewhere else)
Node C: [data] -> [next]   (somewhere else again)

🧠 Final thoughts:

✔️ ArrayList = fast cache-friendly memory, thanks to alignment + contiguity
❌ LinkedList = heap-scattered nodes, suffers from cache misses despite being “O(1)” for insertion

This entry was posted in Без рубрики. Bookmark the permalink.