Java LinkedList
A complete guide to Java LinkedList β doubly linked list internals, all List and Deque methods, LinkedList vs ArrayList comparison, time complexity, iterators, generics with custom objects, and best practices with real-world examples.
Last Updated
March 2026
Read Time
18 min
Level
Beginner to Intermediate
What is Java LinkedList?
Java LinkedList is a class in the java.util package that implements a doubly linked list data structure. Unlike ArrayList, which stores elements in a contiguous dynamic array, LinkedList stores each element inside a Node object. Each node holds three things: the actual data, a reference to the previous node, and a reference to the next node.
LinkedList implements both the List and Deque interfaces, making it uniquely versatile. You can use it as a list (index-based access), a queue (FIFO), a stack (LIFO), or a double-ended queue (add/remove from both ends). This dual-interface design is what sets LinkedList apart from every other collection in the Java standard library.
It is part of the Java Collections Framework and has been available since Java 1.2. It allows null elements, maintains insertion order, and is not synchronized (not thread-safe by default).
Internal Structure β How LinkedList Works Under the Hood
Every element in a LinkedList is wrapped inside a private static inner class called Node<E>. Understanding this node structure is key to understanding why LinkedList behaves so differently from ArrayList.
// Simplified representation of LinkedList's internal Node class
private static class Node<E> {
E item; // The actual data stored in this node
Node<E> next; // Reference to the next node in the list
Node<E> prev; // Reference to the previous node in the list
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
// LinkedList maintains only two pointers internally:
transient Node<E> first; // pointer to the head node
transient Node<E> last; // pointer to the tail node
transient int size = 0; // total number of elementsVisual representation of a LinkedList with 3 elements [10, 20, 30]:
HEAD TAIL
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ
β prev β 10 βnextβββββΆβ prev β 20 βnextβββββΆβ prev β 30 βnextβ
β null β β ββββββ β β ββββββ β βnullβ
ββββββββββββββββββ ββββββββββββββββββ ββββββββββββββββββ
- Adding to head or tail: O(1) β just update first/last pointer
- Accessing index 2: O(n) β must traverse from head node by nodeBecause nodes are scattered across heap memory (not contiguous like an array), LinkedList uses more memory per element than ArrayList β each node carries two extra object references (prev and next) in addition to the data. This overhead is the main trade-off you accept when choosing LinkedList.
LinkedList Class Hierarchy
Understanding where LinkedList sits in the Java Collections hierarchy explains which methods it inherits and why it can be used as both a List and a Deque.
Key interfaces implemented: List<E> (positional access, iteration, search), Deque<E> (double-ended queue operations), Cloneable (supports shallow copying via clone()), and java.io.Serializable (can be serialized).
Creating a LinkedList in Java
You can create a LinkedList in multiple ways. Always use the interface type on the left side for flexible, loosely coupled code.
import java.util.*;
public class CreateLinkedList {
public static void main(String[] args) {
// 1. Empty LinkedList (most common)
LinkedList<String> names = new LinkedList<>();
// 2. Using List interface reference (recommended for flexibility)
List<Integer> numbers = new LinkedList<>();
// 3. Using Deque interface reference (when used as queue/stack)
Deque<String> taskQueue = new LinkedList<>();
// 4. Initialize from an existing collection
List<String> source = Arrays.asList("Apple", "Banana", "Cherry");
LinkedList<String> fruits = new LinkedList<>(source);
System.out.println(fruits); // [Apple, Banana, Cherry]
// 5. LinkedList of custom objects
LinkedList<Employee> employees = new LinkedList<>();
}
}LinkedList Methods β Complete Reference
LinkedList provides a rich set of methods from both the List and Deque interfaces. Here is a complete reference with descriptions and examples.
import java.util.*;
public class LinkedListMethods {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
// --- Adding elements ---
list.add("Banana"); // [Banana]
list.addFirst("Apple"); // [Apple, Banana]
list.addLast("Cherry"); // [Apple, Banana, Cherry]
list.add(1, "Avocado"); // [Apple, Avocado, Banana, Cherry]
System.out.println("After adds: " + list);
// --- Accessing elements ---
System.out.println("First: " + list.getFirst()); // Apple
System.out.println("Last: " + list.getLast()); // Cherry
System.out.println("Index 2: " + list.get(2)); // Banana
// --- Updating ---
list.set(1, "Apricot");
System.out.println("After set: " + list);
// --- Removing ---
list.removeFirst();
list.removeLast();
System.out.println("After remove first/last: " + list);
// --- Search ---
System.out.println("Contains Banana: " + list.contains("Banana"));
System.out.println("Index of Banana: " + list.indexOf("Banana"));
// --- Size ---
System.out.println("Size: " + list.size());
}
}Output
After adds: [Apple, Avocado, Banana, Cherry] First: Apple Last: Cherry Index 2: Banana After set: [Apple, Apricot, Banana, Cherry] After remove first/last: [Apricot, Banana] Contains Banana: true Index of Banana: 1 Size: 2LinkedList as Queue and Deque
One of the most powerful and underused features of LinkedList is that it fully implements the Deque interface, allowing it to serve as a Queue (FIFO), a Stack (LIFO), or a Double-Ended Queue (add/remove from both ends). This makes it the go-to choice for implementing BFS traversal, undo/redo systems, and task scheduling.
import java.util.*;
public class LinkedListAsQueue {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
// offer() adds to the tail β O(1)
queue.offer("Task-1");
queue.offer("Task-2");
queue.offer("Task-3");
System.out.println("Queue: " + queue);
// peek() β see front without removing
System.out.println("Front: " + queue.peek()); // Task-1
// poll() β remove from front (FIFO order)
System.out.println("Processed: " + queue.poll()); // Task-1
System.out.println("Processed: " + queue.poll()); // Task-2
System.out.println("Remaining: " + queue);
}
}Output
Queue: [Task-1, Task-2, Task-3] Front: Task-1 Processed: Task-1 Processed: Task-2 Remaining: [Task-3]import java.util.*;
public class LinkedListAsStack {
public static void main(String[] args) {
Deque<String> stack = new LinkedList<>();
// push() adds to the front (head) β O(1)
stack.push("Page-1");
stack.push("Page-2");
stack.push("Page-3");
System.out.println("Stack (top first): " + stack);
// pop() removes from front (LIFO order)
System.out.println("Back: " + stack.pop()); // Page-3
System.out.println("Back: " + stack.pop()); // Page-2
System.out.println("Remaining: " + stack);
}
}Output
Stack (top first): [Page-3, Page-2, Page-1] Back: Page-3 Back: Page-2 Remaining: [Page-1]LinkedList vs ArrayList β Detailed Comparison
Choosing between LinkedList and ArrayList is one of the most common Java performance decisions. This comparison will help you choose the right one for your use case.
Quick Rule of Thumb: If you're accessing elements by index frequently β use ArrayList. If you're inserting or removing at the beginning or end frequently, or need queue/stack behavior β use LinkedList. In practice, ArrayList wins most benchmarks due to CPU cache locality, so default to it unless you have a specific reason to use LinkedList.
Time Complexity Analysis β LinkedList Operations
Understanding the time complexity of each LinkedList operation is essential for writing performant code and for answering interview questions confidently.
Optimization tip: When traversing to an index, the JDK implementation is smart enough to start from first if index < size/2, or from last if index >= size/2. This halves traversal time in the worst case β but it is still O(n).
Iterating Over a LinkedList β All Methods
There are several ways to iterate over a LinkedList. Always prefer for-each or Iterator over index-based loops β using get(i) in a for loop gives O(nΒ²) performance because each get call traverses from the head.
import java.util.*;
public class IterateLinkedList {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>(Arrays.asList("Java", "Python", "Go", "Rust"));
// 1. Enhanced for-each (recommended β uses Iterator internally)
System.out.println("--- for-each ---");
for (String lang : list) {
System.out.println(lang);
}
// 2. Iterator (best when you need to remove during iteration)
System.out.println("--- Iterator ---");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String lang = it.next();
if (lang.equals("Go")) {
it.remove(); // safe removal during iteration
} else {
System.out.println(lang);
}
}
System.out.println("After removal: " + list);
// 3. ListIterator (supports bidirectional traversal)
System.out.println("--- Reverse (ListIterator) ---");
ListIterator<String> lit = list.listIterator(list.size());
while (lit.hasPrevious()) {
System.out.println(lit.previous());
}
// 4. descendingIterator() β reverse order without ListIterator
System.out.println("--- descendingIterator ---");
Iterator<String> desc = list.descendingIterator();
while (desc.hasNext()) {
System.out.println(desc.next());
}
// 5. forEach with lambda (Java 8+)
System.out.println("--- Lambda forEach ---");
list.forEach(lang -> System.out.println(lang.toUpperCase()));
// β AVOID THIS β O(nΒ²) performance!
// for (int i = 0; i < list.size(); i++) { list.get(i); }
}
}LinkedList Operation Flow β Flowchart
The flowchart below shows how Java decides which approach to use when inserting an element into a LinkedList.
Code Execution Flow β from source to output
LinkedList with Generics and Custom Objects
LinkedList is fully generic and works with any type, including custom domain objects. When using custom objects, implementing Comparable or passing a Comparator enables sorting with Collections.sort().
import java.util.*;
class Employee implements Comparable<Employee> {
String name;
int salary;
Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
@Override
public int compareTo(Employee other) {
return Integer.compare(this.salary, other.salary); // sort by salary ascending
}
@Override
public String toString() {
return name + "(βΉ" + salary + ")";
}
}
public class LinkedListCustomObjects {
public static void main(String[] args) {
LinkedList<Employee> employees = new LinkedList<>();
employees.add(new Employee("Rahul", 75000));
employees.add(new Employee("Priya", 92000));
employees.add(new Employee("Amit", 68000));
employees.addFirst(new Employee("Neha", 105000));
System.out.println("Original: " + employees);
// Sort by salary (uses compareTo)
Collections.sort(employees);
System.out.println("By salary: " + employees);
// Sort by name using lambda Comparator
employees.sort(Comparator.comparing(e -> e.name));
System.out.println("By name: " + employees);
// Highest paid β getLast() after salary sort
Collections.sort(employees);
System.out.println("Highest paid: " + employees.getLast());
}
}Output
Original: [Neha(βΉ105000), Rahul(βΉ75000), Priya(βΉ92000), Amit(βΉ68000)] By salary: [Amit(βΉ68000), Rahul(βΉ75000), Priya(βΉ92000), Neha(βΉ105000)] By name: [Amit(βΉ68000), Neha(βΉ105000), Priya(βΉ92000), Rahul(βΉ75000)] Highest paid: Neha(βΉ105000)Best Practices for Using LinkedList
Using LinkedList effectively requires knowing not just how it works, but when to use it and how to avoid common performance pitfalls.
- βΆ
β 1. Use Interface Types on the Left Side β Declare variables as List<E>, Queue<E>, or Deque<E> rather than LinkedList<E>. This keeps your code loosely coupled and lets you switch implementations without changing dependent code.
- βΆ
β 2. Avoid Index-Based Access in Loops β Never use get(i) inside a for loop over a LinkedList. This is O(nΒ²). Always use for-each, Iterator, or stream() β they use the internal linked-list traversal efficiently.
- βΆ
β 3. Prefer LinkedList for Frequent Head/Tail Operations β When your primary operations are addFirst(), addLast(), removeFirst(), removeLast() β LinkedList is the right choice. This includes task queues, browser history, and undo stacks.
- βΆ
β 4. Use Iterator for Safe Removal During Traversal β Never call list.remove(obj) inside a for-each loop β it throws ConcurrentModificationException. Always use iterator.remove() for safe removal while iterating.
- βΆ
β 5. Synchronize Explicitly for Multi-Threaded Access β LinkedList is not thread-safe. For concurrent access use Collections.synchronizedList(new LinkedList<>()) or ConcurrentLinkedDeque from java.util.concurrent.
- βΆ
β 6. Prefer ArrayDeque Over LinkedList for Stack/Queue β If you only need queue or stack behavior (no List operations), ArrayDeque is faster than LinkedList because it avoids node object allocation overhead and has better cache locality.
- βΆ
β 7. Don't Use LinkedList as a General-Purpose List β If your workload involves mostly get(index) or iteration, ArrayList will significantly outperform LinkedList in practice due to CPU cache locality and lower memory overhead.
Real-World Code Examples
Example 1 β Browser History (Doubly Linked List Use Case)
import java.util.*;
public class BrowserHistory {
private LinkedList<String> history = new LinkedList<>();
private int currentIndex = -1;
public void visit(String url) {
// Remove all forward history on new visit
while (history.size() > currentIndex + 1) {
history.removeLast();
}
history.addLast(url);
currentIndex++;
System.out.println("Visited: " + url);
}
public void back() {
if (currentIndex > 0) {
currentIndex--;
System.out.println("Back to: " + history.get(currentIndex));
} else {
System.out.println("No more history.");
}
}
public void forward() {
if (currentIndex < history.size() - 1) {
currentIndex++;
System.out.println("Forward to: " + history.get(currentIndex));
} else {
System.out.println("No forward history.");
}
}
public static void main(String[] args) {
BrowserHistory browser = new BrowserHistory();
browser.visit("https://google.com");
browser.visit("https://techsustainify.com");
browser.visit("https://github.com");
browser.back();
browser.back();
browser.forward();
browser.visit("https://stackoverflow.com");
}
}Output
Visited: https://google.com Visited: https://techsustainify.com Visited: https://github.com Back to: https://techsustainify.com Back to: https://google.com Forward to: https://techsustainify.com Visited: https://stackoverflow.comExample 2 β Task Scheduler using LinkedList as Queue
import java.util.*;
class Task {
String name;
int priority;
Task(String name, int priority) {
this.name = name;
this.priority = priority;
}
@Override
public String toString() {
return "[" + name + ", P" + priority + "]";
}
}
public class TaskScheduler {
public static void main(String[] args) {
Deque<Task> taskQueue = new LinkedList<>();
// High-priority tasks go to the front
taskQueue.offerLast(new Task("Send Email", 2));
taskQueue.offerLast(new Task("Generate Report", 2));
taskQueue.offerFirst(new Task("DB Backup", 1)); // urgent β front
taskQueue.offerLast(new Task("Cleanup Logs", 3));
System.out.println("Task Queue: " + taskQueue);
System.out.println("\nProcessing tasks:");
while (!taskQueue.isEmpty()) {
Task t = taskQueue.poll();
System.out.println("Processing: " + t);
}
}
}Output
Task Queue: [[DB Backup, P1], [Send Email, P2], [Generate Report, P2], [Cleanup Logs, P3]] Processing tasks: Processing: [DB Backup, P1] Processing: [Send Email, P2] Processing: [Generate Report, P2] Processing: [Cleanup Logs, P3]Practice This Code β Live Editor
Advantages and Disadvantages of LinkedList
LinkedList is a powerful data structure, but it comes with real trade-offs. Knowing both sides helps you make architecture decisions with confidence.
Java LinkedList β Interview Questions
These are the most frequently asked interview questions on Java LinkedList for Java developer positions at all levels. Master all of these before your interview.
Practice Questions β Test Your Knowledge
Test your understanding of LinkedList with these practice questions. Try answering each one before revealing the answer.
1. What is the time complexity of addFirst() and get(0) in a LinkedList? Are they the same?
Easy2. What will the following code print? LinkedList<Integer> ll = new LinkedList<>(Arrays.asList(1,2,3,4,5)); ll.addFirst(0); ll.removeLast(); System.out.println(ll);
Easy3. You have a LinkedList of 1 million elements and you need to check if element 'X' exists. Is LinkedList a good choice for this? What is the time complexity?
Medium4. What is the difference between remove() and poll() in LinkedList when the list is empty?
Medium5. Write code to reverse a LinkedList in Java without using any extra data structure.
Medium6. You need a data structure for a print job spooler that processes jobs in order but also supports urgent print jobs that must jump to the front. Which LinkedList methods would you use?
Medium7. Why does Collections.binarySearch() perform worse on LinkedList than ArrayList even if both have sorted data?
Hard8. What is the memory overhead of LinkedList compared to ArrayList for storing n Integer objects?
HardConclusion β Mastering LinkedList in Java
LinkedList is one of Java's most versatile collection classes, but also one of the most frequently misused. Its superpower is O(1) insertions and deletions at the head and tail combined with its ability to function as a List, Queue, Stack, and Deque simultaneously. Its weakness is O(n) random access and higher memory overhead per element.
Professional Java developers choose LinkedList intentionally β not by default. The default choice for most list needs is ArrayList. Reach for LinkedList specifically when you need a queue, an undo/redo stack, a browser history model, or any scenario where elements are frequently added to or removed from both ends.
Your next steps: explore ArrayDeque (faster alternative for queue/stack use cases), PriorityQueue (ordered queue), and how LinkedList-backed queues are used in BFS/DFS graph algorithms. Understanding the full Collections Framework gives you the vocabulary to make precise, justified architectural choices in any Java codebase. β