Senior 4 min · March 17, 2026

LinkedList OutOfMemoryError — 32 Bytes Per Node Overhead

LinkedList nodes add ~32 bytes each — OutOfMemoryError at 10M entries.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Java's LinkedList is a doubly-linked list implementing both List and Deque.
  • O(1) insert/remove at head or tail; O(n) for random access and index-based operations.
  • Each element stores two extra pointers (prev, next) — memory overhead ~24-40 bytes per node.
  • CPU cache misses make iteration 10-100x slower than ArrayList for large lists.
  • Biggest mistake: assuming LinkedList is a general-purpose list replacement — in most real apps, ArrayList or ArrayDeque is faster.
Plain-English First

Think of LinkedList like a treasure hunt where each clue tells you where the next clue is. Adding a new clue at the start or end is easy, but finding the 5th clue means starting from the beginning and following every clue until you reach it. That's slow. ArrayList is like a numbered list on paper — you can jump straight to any item, but adding at the front means rewriting everyone's number.

LinkedList as a List

When you use LinkedList as a List, you get all the usual List operations — add, remove, get, set — but backed by a doubly linked structure. That means get(i) traverses from the head until it reaches index i. That's O(n). add(int index, element) first finds the node at that index (O(n)), then inserts by updating pointers (O(1)). The combined cost is still O(n). Use this when you rarely access by index and mostly add/remove at ends.

ExampleJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package io.thecodeforge.java.collections;

import java.util.LinkedList;
import java.util.List;

public class LinkedListBasics {
    public static void main(String[] args) {
        List<String> list = new LinkedList<>();

        list.add("banana");
        list.add("cherry");
        list.add(0, "apple");  // insert at head — O(1) for LinkedList, O(n) for ArrayList

        System.out.println(list);      // [apple, banana, cherry]
        System.out.println(list.get(1)); // banana — O(n) traversal
        list.remove(1);                 // O(n) to find, O(1) to remove
        System.out.println(list);      // [apple, cherry]
    }
}
Output
[apple, banana, cherry]
banana
[apple, cherry]
get(index) is a trap
Every call to get(i) on a LinkedList performs a linear scan from the head. In production, if you have a loop like for (int i = 0; i < list.size(); i++) { list.get(i); }, you are inadvertently running an O(n²) algorithm. Prefer for-each loops or iterator patterns that exploit intrinsic sequential access.
Production Insight
A common production bug is using get(i) inside a for-loop thinking it's O(1).
For a list with 100,000 elements, this turns a 1ms iteration into a 10-second nightmare.
Rule: never index into LinkedList inside a loop.
Key Takeaway
LinkedList.get(i) is O(n).
Use iterator or for-each for sequential access.
If you need random access, use ArrayList.

LinkedList as a Deque

This is where LinkedList has a genuine advantage — O(1) add/remove at both ends. Use it when you need a queue or deque with frequent operations at both ends. The Deque interface provides addFirst, addLast, pollFirst, pollLast and their alternatives. LinkedList implements Deque, so you can declare the variable as Deque<Integer> to restrict usage to deque operations only.

ExampleJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package io.thecodeforge.java.collections;

import java.util.Deque;
import java.util.LinkedList;

public class LinkedListDeque {
    public static void main(String[] args) {
        Deque<Integer> deque = new LinkedList<>();

        // O(1) operations at both ends
        deque.addFirst(2);   // [2]
        deque.addFirst(1);   // [1, 2]
        deque.addLast(3);    // [1, 2, 3]
        deque.addLast(4);    // [1, 2, 3, 4]

        System.out.println(deque.peekFirst()); // 1
        System.out.println(deque.peekLast());  // 4
        System.out.println(deque.pollFirst()); // 1 — remove from front
        System.out.println(deque.pollLast());  // 4 — remove from back
        System.out.println(deque);             // [2, 3]
    }
}
Output
1
4
1
4
[2, 3]
ArrayDeque is usually faster
ArrayDeque also implements Deque with a resizeable circular array. It provides the same O(1) head/tail operations with much lower constant factors and no per-node memory overhead. Use LinkedList only if you also need the List interface (e.g., you must maintain element positions and occasionally access by index).
Production Insight
Many codebases use LinkedList as a queue simply because it's the first class that comes to mind.
That's a mistake — ArrayDeque has ~2x better throughput and zero node allocation per operation.
Rule: for pure queue/deque, always prefer ArrayDeque over LinkedList.
Key Takeaway
LinkedList as a Deque gives O(1) add/remove at both ends.
But ArrayDeque does the same with less memory and faster iteration.
Only pick LinkedList when you also need the List API.

Internal Node Structure and Memory Overhead

LinkedList stores each element in a Node object. The Node class has three fields: item (the element), next (reference to next node), prev (reference to previous node). The JVM adds object header (usually 12-16 bytes compressed OOPs). On a 64-bit JVM with compressed OOPs, each Node takes about 24 bytes (header: 12, three references: 12). Without compressed OOPs, it's 32 bytes plus the reference to the data. For 1 million integers, that's ~24 MB overhead just for the nodes — plus the Integer objects themselves. ArrayList stores the same data in a contiguous Object[] with no per-element overhead (just the array itself). The difference becomes dramatic at scale.

ExampleJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package io.thecodeforge.java.collections;

import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;

public class MemoryOverhead {
    public static void main(String[] args) throws Exception {
        int N = 1_000_000;
        List<Integer> linked = new LinkedList<>();
        List<Integer> array = new ArrayList<>(N);

        // Fill both
        for (int i = 0; i < N; i++) {
            linked.add(i);
            array.add(i);
        }

        // Measure approximate memory (requires -XX:+UseCompressedOops)
        Runtime rt = Runtime.getRuntime();
        rt.gc();
        long memLinked = rt.totalMemory() - rt.freeMemory();

        // Clear linked, keep array reference alive
        linked.clear();
        rt.gc();
        long memArray = rt.totalMemory() - rt.freeMemory();

        System.out.printf("LinkedList with %d ints: ~%d MB%n", N, memLinked / (1024*1024));
        System.out.printf("ArrayList   with %d ints: ~%d MB%n", N, memArray / (1024*1024));
        System.out.println("Difference is node overhead (prev + next + header).");
    }
}
Output
LinkedList with 1000000 ints: ~48 MB
ArrayList with 1000000 ints: ~24 MB
Difference is node overhead (prev + next + header).
Pointer-Heap Scatter vs Contiguous Array
  • Each LinkedList node is a separate object allocated on the heap — pointer chasing from one to the next hurts cache.
  • ArrayList's backing array is a single contiguous block — iterating it triggers hardware prefetching.
  • Memory fragmentation: LinkedList nodes can be allocated far apart; the GC has to scan each node independently.
  • Benchmark: iterating a 1M-element LinkedList takes 50-100ms; ArrayList takes 1-5ms on modern hardware.
Production Insight
Node overhead is not just about heap usage — it also stresses the garbage collector.
Each node is a separate allocation that must be tracked and freed.
High allocation rate can trigger more frequent GC pauses.
Rule: for large collections, contiguous storage (ArrayList, ArrayDeque) minimizes GC pressure.
Key Takeaway
Each LinkedList node adds ~24 bytes of overhead.
At scale, this causes OOM and GC churn.
If your collection exceeds 10,000 elements, profile memory before choosing LinkedList.

Node Structure: Prev, Data, Next

A LinkedList node is a small object with three fields: a reference to the stored element (Data), a reference to the previous node (Prev), and a reference to the next node (Next). This triple reference is what enables O(1) insertion and removal at both ends — but also adds memory overhead. The diagram below shows a single node and how nodes link together in a doubly linked list.

io/thecodeforge/java/collections/NodeStructure.javaJAVA
1
2
3
4
5
6
7
// The Node class from OpenJDK's LinkedList:
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;
    Node(Node<E> prev, E element, Node<E> next) {\n        this.item = element;\n        this.next = next;\n        this.prev = prev;\n    }
}
Production Insight
Understanding the node structure helps explain why LinkedList iteration is slow. Each time you move to the next node, you follow a pointer to a potentially far-away memory location (cache miss). With ArrayList, the next element is right next to the current one in the array.
Key Takeaway
Each LinkedList node stores prev/data/next in a separate object, causing pointer-chasing and poor cache locality.

LinkedList vs ArrayList — Performance Trade-offs

The decision between LinkedList and ArrayList ultimately comes down to access pattern. Below is a benchmark comparing random access and head insertion for 100,000 elements. ArrayList dominates random access because of cache locality; LinkedList is faster for head insertion but only when you use the Deque methods (addFirst) rather than list.add(0, elem) which still has to shift in ArrayList.

ExampleJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package io.thecodeforge.java.collections;

import java.util.*;

public class Benchmark {
    static final int N = 100_000;

    public static void main(String[] args) {
        // Scenario 1: Random access — ArrayList wins decisively
        List<Integer> arrayList = new ArrayList<>(Collections.nCopies(N, 1));
        List<Integer> linkedList = new LinkedList<>(Collections.nCopies(N, 1));

        long start = System.nanoTime();
        for (int i = 0; i < 10_000; i++) arrayList.get(N / 2);
        System.out.printf("ArrayList random access:  %,dns%n", System.nanoTime() - start);

        start = System.nanoTime();
        for (int i = 0; i < 10_000; i++) linkedList.get(N / 2);
        System.out.printf("LinkedList random access: %,dns (much slower)%n", System.nanoTime() - start);

        // Scenario 2: Insert at head — LinkedList wins
        start = System.nanoTime();
        for (int i = 0; i < 10_000; i++) arrayList.add(0, 0);
        System.out.printf("ArrayList head insert:  %,dns%n", System.nanoTime() - start);

        Deque<Integer> deque = new LinkedList<>();
        start = System.nanoTime();
        for (int i = 0; i < 10_000; i++) deque.addFirst(0);
        System.out.printf("LinkedList head insert: %,dns%n", System.nanoTime() - start);
    }
}
Output
ArrayList random access: 50,000ns
LinkedList random access: 8,000,000ns (much slower)
ArrayList head insert: 15,000,000ns
LinkedList head insert: 120,000ns
Production Insight
Cache misses dominate modern CPU performance.
While LinkedList head insertion is O(1) in theory, the actual throughput difference is often less than 2x vs ArrayList's O(n) shift.
But random access is 100-200x slower. Choose based on your dominant operation.
Rule: profile, don't assume.
Key Takeaway
ArrayList wins for iteration and random access.
LinkedList wins for head/tail insertion only.
If both operations matter, reconsider your design or use ArrayDeque.

Advantages and Disadvantages of LinkedList

Like any data structure, LinkedList has strengths and weaknesses that depend on your usage pattern. Below is a breakdown to help you decide when to use it and when to avoid it.

Advantages - O(1) insertion and removal at both ends (head and tail) when using Deque methods. - O(1) removal during iteration via iterator.remove() — no shifting of elements. - Doubly linked structure allows constant time insertion before or after a given node if you have a reference to that node (e.g., via ListIterator). - Implements both List and Deque interfaces, providing flexibility. - No capacity limit beyond heap memory; nodes are allocated lazily.

Disadvantages - O(n) random access — get(index) requires traversal from head/tail. - High memory overhead: each node is a separate object with two extra references (prev, next) plus JVM header (~24 bytes per element for the node alone). - Poor cache locality: nodes are scattered across heap, causing CPU cache misses during iteration. - Not thread-safe — requires external synchronization for concurrent access. - Iteration performance is typically 10-100x slower than ArrayList due to pointer chasing. - Insertion in the middle still requires O(n) to find the insertion point (unless you have a positioned iterator).

Production Insight
In production, LinkedList’s disadvantages usually outweigh its advantages. The O(1) head/tail insert is well-matched by ArrayDeque without the overhead. Only use LinkedList when you specifically need O(1) iterator removal or the dual List+Deque capability.
Key Takeaway
LinkedList excels at head/tail operations and iterator removal, but suffers from poor cache locality and high memory overhead. Prefer ArrayDeque for queues and ArrayList for list operations.

Thread-Safe Alternatives: ConcurrentLinkedDeque and Synchronized Wrappers

LinkedList is not thread-safe. If multiple threads access it concurrently and at least one modifies it, it must be synchronized externally. Java provides two main options for thread-safe deque operations:

  1. ConcurrentLinkedDeque — A lock-free, non-blocking deque implementation from java.util.concurrent. It uses a doubly linked list internally (similar to LinkedList) but with atomic operations for thread safety. Best for high-throughput concurrent queue/deque usage where you don't need random access.
  2. Collections.synchronizedList() — Wraps a LinkedList (or any List) with synchronized methods. This uses intrinsic locking and is simpler but can become a bottleneck under high contention. Use when you need the List interface in a concurrent setting.

Most modern applications should prefer ConcurrentLinkedDeque for queue/deque scenarios. If you need list semantics, consider CopyOnWriteArrayList or synchronized list.

io/thecodeforge/java/collections/ThreadSafeDeque.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package io.thecodeforge.java.collections;

import java.util.Deque;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class ThreadSafeDeque {
    public static void main(String[] args) {
        // Lock-free concurrent deque — preferred
        Deque<String> concurrentDeque = new ConcurrentLinkedDeque<>();
        concurrentDeque.addFirst("task1");
        concurrentDeque.addLast("task2");
        // Can be shared safely across threads
        
        // Synchronized LinkedList — use only if you need List interface
        List<String> syncList = Collections.synchronizedList(new LinkedList<>());
        syncList.add("item");
        // Access must be done within synchronized block for iteration
        synchronized (syncList) {
            for (String s : syncList) {
                System.out.println(s);
            }
        }
    }
}
Output
task1
task2
item
ConcurrentLinkedDeque vs SynchronizedList
ConcurrentLinkedDeque is thread-safe without locking, offering better scalability. However, it does not implement the List interface. If you need both List and thread safety, consider synchronizedList or CopyOnWriteArrayList (which uses copy-on-write and is suitable for read-heavy workloads).
Production Insight
In high-concurrency systems, using synchronizedList(new LinkedList<>()) can become a bottleneck because each method call acquires the lock. ConcurrentLinkedDeque uses CAS operations and is lock-free, making it a better choice for most concurrent queue/deque applications. Always measure contention before choosing.
Key Takeaway
Use ConcurrentLinkedDeque for thread-safe deque operations without locking. Reserve synchronizedList for when you must keep the List interface in a concurrent context.

5 Practice Problems Using LinkedList

These problems test your understanding of LinkedList's capabilities, especially its strength in head/tail operations and iterator-based removal. Solutions are provided in Java using LinkedList or Deque.

Problem 1: Implement a Queue using LinkedList Use LinkedList as a FIFO queue. Implement enqueue, dequeue, peek, and isEmpty.

Problem 2: Implement a Stack using LinkedList Use LinkedList as a LIFO stack. Implement push, pop, peek, and isEmpty. (Note: Java has ArrayDeque for stack, but this exercise demonstrates how LinkedList can also serve as a stack.)

Problem 3: Reverse a LinkedList in O(n) time Reverse the order of elements by traversing once and updating pointers. (This is a classic interview problem.)

Problem 4: Find the Middle Element of a LinkedList Use the slow-fast pointer technique (Floyd's algorithm) to find the middle in one pass.

Problem 5: Detect a Cycle in a LinkedList Again use slow-fast pointers to detect if the list has a cycle.

io/thecodeforge/java/collections/LinkedListPracticeProblems.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package io.thecodeforge.java.collections;

import java.util.LinkedList;

public class LinkedListPracticeProblems {
    public static void main(String[] args) {
        // Problem 1: Queue using LinkedList
        LinkedList<String> queue = new LinkedList<>();
        queue.addLast("A"); // enqueue
        queue.addLast("B");
        queue.addLast("C");
        System.out.println("Queue front: " + queue.peekFirst()); // A
        queue.pollFirst(); // dequeue
        System.out.println("Queue after dequeue: " + queue); // [B, C]

        // Problem 2: Stack using LinkedList
        LinkedList<Integer> stack = new LinkedList<>();
        stack.addFirst(1); // push
        stack.addFirst(2);
        stack.addFirst(3);
        System.out.println("Stack top: " + stack.peekFirst()); // 3
        stack.pollFirst(); // pop
        System.out.println("Stack after pop: " + stack); // [2, 1]

        // Problem 3: Reverse a LinkedList
        LinkedList<Integer> list = new LinkedList<>();
        list.add(1); list.add(2); list.add(3); list.add(4);
        LinkedList<Integer> reversed = new LinkedList<>();
        for (int val : list) reversed.addFirst(val);
        System.out.println("Reversed: " + reversed); // [4, 3, 2, 1]

        // Problem 4: Find middle using slow-fast
        LinkedList<Integer> list2 = new LinkedList<>();
        for (int i = 1; i <= 7; i++) list2.add(i);
        var slow = list2.listIterator();
        var fast = list2.listIterator();
        while (fast.hasNext()) {
            fast.next();
            if (fast.hasNext()) fast.next();
            else break;
            slow.next();
        }
        System.out.println("Middle element: " + slow.next()); // 4

        // Problem 5: Detect cycle (using reference manipulation, but here we check by definition)
        // For a cycle detection, we need custom node class. Pseudocode provided.
        System.out.println("Cycle detection: Use slow-fast pointers — if they meet, there is a cycle.");
    }
}
Output
Queue front: A
Queue after dequeue: [B, C]
Stack top: 3
Stack after pop: [2, 1]
Reversed: [4, 3, 2, 1]
Middle element: 4
Cycle detection: Use slow-fast pointers — if they meet, there is a cycle.
Production Insight
These problems illustrate common interview scenarios, but in production you should prefer built-in collections. For example, ArrayDeque is a better choice for stack/queue in single-threaded code, and ConcurrentLinkedDeque for multithreaded. However, understanding the underlying mechanics helps debug performance issues.
Key Takeaway
LinkedList can be used to implement queue, stack, and solve classic problems, but production code should prefer ArrayDeque or ConcurrentLinkedDeque for better performance.

When to Actually Use LinkedList in Production

Despite its drawbacks, LinkedList has three genuine production use cases where it beats the alternatives:

  1. Frequent middle removal via iterator: If you need to remove elements while iterating (and you don't know the index), LinkedList's iterator.remove() is O(1) vs ArrayList's O(n) for removal (shifts elements). Example: a task scheduler that periodically removes completed tasks from a list.
  2. Round-robin or circular buffer: Because it's a doubly linked list, you can implement a circular traversal by following next pointer and resetting when you hit the original node. But careful — ArrayDeque also supports circular array efficiently.
  3. When you need both List and Deque interface on the same object: Some APIs require a List (e.g., for parameter passing) and you also use queue operations on the same collection. In that case, LinkedList is the only built-in class that satisfies both.
ExampleJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package io.thecodeforge.java.collections;

import java.util.*;

public class WhenToUseLinkedList {
    public static void main(String[] args) {
        // Use case 1: iterator removal
        LinkedList<String> tasks = new LinkedList<>(List.of("task1", "task2", "task3", "task4"));
        var it = tasks.iterator();
        while (it.hasNext()) {
            String task = it.next();
            if (task.equals("task2")) {
                it.remove(); // O(1) in LinkedList, O(n) in ArrayList
            }
        }
        System.out.println("After removal: " + tasks);

        // Use case 2: Deque + List interface
        List<String> list = new LinkedList<>();
        Deque<String> deque = (Deque<String>) list; // same object
        deque.addFirst("front");
        deque.addLast("back");
        // list is now [front, back]
        System.out.println("List view: " + list.get(0)); // front
    }
}
Output
After removal: [task1, task3, task4]
List view: front
Prefer interface segregation
Always declare your variable as the most specific interface. If you only need queue operations, use Deque<String> deque = new LinkedList<>(). If you need list operations, use List<String> list = new LinkedList<>(). This makes the intended usage obvious to future maintainers.
Production Insight
The number one mistake is using LinkedList because it feels 'fast' for insertions.
In real-world production code, 90%+ of LinkedList usages should be replaced with ArrayList or ArrayDeque.
If you have a performance bug that involves iterating a LinkedList, your fix is almost always to switch the data structure.
Rule: LinkedList is a specialized tool — treat it like one.
Key Takeaway
Use LinkedList only when you need O(1) middle removal via iterator, or when the List and Deque interfaces must share the same object.
For everything else, choose ArrayList or ArrayDeque.
LinkedList is not a general-purpose optimal list.
● Production incidentPOST-MORTEMseverity: high

LinkedList OutOfMemoryError in a High-Throughput Queue

Symptom
Application runs out of heap memory intermittently during peak load. Heap dump shows millions of LinkedList$Node instances.
Assumption
LinkedList is efficient for a queue — it's O(1) for add/remove at ends.
Root cause
Each LinkedList node stores two pointers (prev, next) plus the object reference. Overhead is ~32 bytes per element (24 bytes for Node object + 8 for reference). For 10 million entries, that's ~320 MB just for node objects, plus the data. ArrayList uses a contiguous array with no per-element overhead.
Fix
Replaced LinkedList with ArrayDeque for the ring buffer. ArrayDeque uses a circular array with resize-on-demand, eliminating per-element overhead. Memory dropped by 60%.
Key lesson
  • LinkedList nodes have significant memory overhead — never use it for millions of elements.
  • ArrayDeque is almost always a better choice for queue/deque usage.
  • Profile memory before choosing a data structure in production.
Production debug guideDiagnose and fix LinkedList-related performance issues in production4 entries
Symptom · 01
Slow iteration over the entire list
Fix
Check if the list is used with for(;;) or for-each. If yes, CPU cache misses are likely. Replace with ArrayList or ArrayDeque if iteration is the primary operation.
Symptom · 02
Unexpected OutOfMemoryError with large lists
Fix
Take a heap dump and analyze instance counts. Look for LinkedList$Node objects. Each node consumes ~24 bytes overhead. Replace with ArrayDeque or custom ring buffer.
Symptom · 03
Slow random access (list.get(index)) in loops
Fix
Check for get(i) inside loops. LinkedList.get(i) is O(n). Refactor to use iterator or convert to ArrayList first.
Symptom · 04
Concurrent modification during iteration
Fix
LinkedList is not synchronized. Use java.util.concurrent.ConcurrentLinkedDeque for thread-safe deque operations or wrap with synchronized list.
★ LinkedList Debugging Cheat SheetQuick commands to identify and fix LinkedList-related production issues
High GC pressure and memory usage
Immediate action
Take a heap dump and search for LinkedList$Node
Commands
jmap -histo:live <pid> | grep 'LinkedList\$Node'
jcmd <pid> GC.class_histogram | grep 'LinkedList'
Fix now
Replace with ArrayDeque<char> or ArrayList — reduces node overhead by 60-80%
Slow iteration in a hot path+
Immediate action
Profile the code to confirm LinkedList is the bottleneck
Commands
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInlining | grep 'LinkedList'
perf stat -e cache-misses,cache-references java ...
Fix now
Switch to ArrayList and use index-based loops or for-each (which is fast for contiguous memory)
Expected O(1) removal but code is slow+
Immediate action
Check if removing by index or value (not via iterator)
Commands
Use VisualVM or async-profiler to see what methods dominate CPU
Log the LinkedList size and check if it's growing unexpectedly
Fix now
Use iterator.remove() for O(1) removal, or use a HashSet for lookups if order doesn't matter
LinkedList vs ArrayList vs ArrayDeque — Quick Comparison
PropertyLinkedListArrayListArrayDeque
Random access (get)O(n)O(1)O(1)
Add at endO(1)O(1) amortizedO(1) amortized
Add at headO(1)O(n)O(1) amortized
Remove interior via iteratorO(1)O(n)O(n) (shifts)
Memory per element overhead~24 bytes (Node)~4 bytes (reference in array)~0 bytes (contiguous array)
Cache locality for iterationPoor (scattered nodes)Excellent (contiguous)Excellent (contiguous)
Implements List?YesYesNo
Implements Deque?YesNoYes

Key takeaways

1
LinkedList is a doubly-linked list that also implements Deque
it shines as a queue.
2
Random access (get(i)) is O(n)
LinkedList has to traverse from the head.
3
Head/tail insert and remove are O(1)
the genuine advantage over ArrayList.
4
For most workloads ArrayList is faster because of CPU cache locality.
5
Prefer ArrayDeque over LinkedList even for queue use
it is faster and uses less memory.
6
LinkedList has a narrow spot
O(1) middle removal via iterator and dual List+Deque interface.

Common mistakes to avoid

4 patterns
×

Using LinkedList for index-based access in loops

Symptom
Application becomes very slow when iterating with get(i). CPU profiling shows LinkedList.get() consuming >80% of time.
Fix
Replace LinkedList with ArrayList. If you need random access, ArrayList is O(1). If you need sequential access, use for-each which exploits the iterator's efficient traversal.
×

Assuming LinkedList is always faster for insertions anywhere

Symptom
Inserting in the middle is still O(n) because of the search to find the insertion point, even though the pointer update is O(1).
Fix
Use a ListIterator with a positioned cursor to avoid the search. For batch insertions, consider using ArrayList and then sort/shifting once.
×

Not using Deque methods when queue is the intent

Symptom
Code uses add() and remove() from List interface when the logical usage is FIFO. This confuses readers and misses performance optimizations available in Deque (like offer/poll).
Fix
Declare the variable as Deque<...> instead of LinkedList<...> or List<...>. This exposes queue-specific methods and warns the reader that queue operations are intended.
×

Using LinkedList in a multithreaded environment without synchronization

Symptom
ConcurrentModificationException or data corruption during concurrent iteration and modification.
Fix
Use ConcurrentLinkedDeque (for non-blocking queue) or wrap with Collections.synchronizedList() if you must keep list semantics.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the time complexity of get(i) for LinkedList vs ArrayList?
Q02SENIOR
When would you choose LinkedList over ArrayDeque for a queue?
Q03SENIOR
Why is iterating over ArrayList typically faster than LinkedList despite...
Q04SENIOR
How does LinkedList handle concurrency? Is it thread-safe?
Q05SENIOR
What is the memory overhead of a LinkedList node compared to an ArrayLis...
Q01 of 05JUNIOR

What is the time complexity of get(i) for LinkedList vs ArrayList?

ANSWER
LinkedList.get(i) is O(n) — it must traverse from the head (or tail if i is closer to the end) until it reaches index i. ArrayList.get(i) is O(1) because it directly indexes into the backing array. This difference is crucial: in a loop calling get(i) repeatedly, LinkedList becomes O(n²) while ArrayList stays O(n).
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
When does LinkedList beat ArrayList in practice?
02
Why is LinkedList slower for iteration despite O(n) complexity on both?
03
Is LinkedList ever the right choice for a high-performance queue in Java?
04
How does LinkedList handle insertion in the middle?
🔥

That's Collections. Mark it forged?

4 min read · try the examples if you haven't

Previous
ArrayList in Java
3 / 21 · Collections
Next
HashMap in Java