LinkedList in Java
Introduction
In Java, data structures dictate how efficiently you can store, retrieve, and manipulate data. While ArrayList is the undisputed king of index-based, read-heavy operations, it struggles when you frequently need to insert or delete elements at the beginning or middle of the list.
Enter LinkedList. Based on the classic doubly-linked list computer science concept, Java's LinkedList excels at modifications (insertions and deletions) but trades away the fast random-access reads provided by arrays. Understanding when to use LinkedList over ArrayList is a crucial skill for any Java developer.
Core Concept: Deep Dive and Architecture
What is LinkedList?
java.util.LinkedList implements both the List interface and the Deque (Double Ended Queue) interface. This dual-implementation means it can act as a standard list, a stack (LIFO), or a queue (FIFO).
How It Works Internally
Unlike an ArrayList, which stores its elements in a contiguous block of memory (an array), a LinkedList stores elements in isolated, distinct nodes scattered across the heap memory.
Each Node in a LinkedList contains three things:
item: The actual data/value.next: A pointer/reference to the next node in the list.prev: A pointer/reference to the previous node in the list.
Because it is a doubly-linked list, the structure maintains references to both the head (first element) and the tail (last element).
The Advantage:
If you want to insert a new element at the front of the list, you simply create a new Node and update a couple of memory references (the prev of the old head, and the next of the new node). This is an O(1) operation. In contrast, an ArrayList would have to physically shift every single element one slot to the right, an O(N) operation.
The Disadvantage:
If you want to access the 50,000th element (list.get(50000)), an ArrayList does math and jumps straight to it in O(1) time. A LinkedList must start at the head and hop node-to-node 50,000 times, resulting in O(N) time.
Code Examples
1. Beginner Example: As a Standard List
import java.util.LinkedList;
import java.util.List;
public class LinkedListBasic {
public static void main(String[] args) {
List<String> trainCars = new LinkedList<>();
trainCars.add("Engine");
trainCars.add("Passenger Car");
trainCars.add("Caboose");
// Inserting into the middle
trainCars.add(1, "Dining Car");
System.out.println(trainCars);
// Output: [Engine, Dining Car, Passenger Car, Caboose]
// Accessing an element (slow operation for LinkedList!)
System.out.println("Second car is: " + trainCars.get(1));
}
}
2. Intermediate Example: As a Queue / Deque
Because LinkedList implements Deque, it provides specific methods for operating on the ends of the list.
import java.util.LinkedList;
import java.util.Deque;
public class LinkedListQueue {
public static void main(String[] args) {
Deque<String> tasks = new LinkedList<>();
// Adding to the ends
tasks.addFirst("Urgent Task");
tasks.addLast("Low Priority Task");
tasks.addFirst("CRITICAL Task");
System.out.println(tasks);
// Output: [CRITICAL Task, Urgent Task, Low Priority Task]
// Polling (removing and returning from the front)
String currentTask = tasks.pollFirst();
System.out.println("Executing: " + currentTask);
System.out.println("Remaining tasks: " + tasks);
}
}
Performance Comparison: LinkedList vs ArrayList
| Operation | LinkedList | ArrayList | Why? |
| :--- | :--- | :--- | :--- |
| get(index) | O(N) | O(1) | ArrayList uses array math; LinkedList must traverse nodes. |
| add(element) (End) | O(1) | O(1) (Amortized) | Both append quickly, but ArrayList occasionally resizes. |
| add(0, element) (Start) | O(1) | O(N) | LinkedList updates 2 pointers; ArrayList shifts all elements. |
| remove(0) (Start) | O(1) | O(N) | LinkedList updates pointers; ArrayList shifts elements back. |
| Memory Footprint | High | Low | LinkedList creates a Node object with 2 pointers for every item. |
Common Mistakes Beginners Make
1. Using LinkedList for get(int index) Operations
The Mistake: Using a normal for loop (for (int i = 0; i < list.size(); i++)) with a LinkedList and calling list.get(i).
The Fix: This results in O(N²) time complexity because each get() starts from the head! Always use an Iterator or the Enhanced For-Loop (for (Object item : list)) which traverses the nodes properly in O(N) total time.
2. Defaulting to LinkedList for everything
The Mistake: Believing LinkedList is generally faster because insertions are fast.
The Fix: Modern CPU caching (cache locality) highly favors contiguous memory like arrays. Even for insertions in the middle, ArrayList often outperforms LinkedList in real-world scenarios due to CPU cache efficiency. Default to ArrayList unless you are building a Queue or inserting at the head specifically.
Interview Q&A
Q1: Does a LinkedList require contiguous memory?
Answer: No. Its elements (Nodes) are dynamically allocated in the heap wherever memory is available and are linked together via references.
Q2: How does LinkedList optimize get(int index) slightly?
Answer: Before traversing, it checks if the requested index is in the first half or the second half of the list. If it's in the second half, it traverses backward from the tail. If in the first half, it traverses forward from the head.
Q3: Which interfaces does LinkedList implement?
Answer: It implements List, Deque, Cloneable, and Serializable.
Summary
LinkedListis a doubly-linked list implementation in Java.- It provides O(1) insertions and deletions at both ends, making it an excellent choice for Stacks and Queues.
- It suffers from O(N) random access, making it a poor choice for read-heavy applications.
- Always iterate over it using an Iterator or Enhanced-For loop, never a standard
for(int i)loop. - It has a significantly higher memory overhead than
ArrayListdue to the creation of Node objects and pointer management.