Coaches of a train
At the railway yard near Panvel, Riya watches workers add an extra coach to the front of a train. They don't move all 20 coaches; they uncouple the engine, couple the new coach, and couple the engine back. Two links change. That's it.
Sir, an ArrayList would have shifted all 20 coaches to make room at the front. This is just two couplings!
That's exactly how a LinkedList works. Each element sits in a node that knows its previous and next node. Adding at either end, or next to a node you're already standing on, is a couple of pointer changes.
So LinkedList is always faster?
Not at all. To find coach number 15, you have to walk from the engine. An ArrayList jumps there in one step. And every node costs extra memory. Let's see both sides honestly.
What is a LinkedList?
java.util.LinkedList<E> is a doubly linked list implementation of both the List and Deque interfaces. Each element is stored in a private Node object holding the element and references to the previous and next nodes; the list keeps references to its first and last nodes and its size. Insertion and removal at either end take constant time; access by index takes linear time, traversing from whichever end is closer. It permits duplicates and null, is not synchronised, and its iterators are fail-fast.
A chain of small boxes, each pointing both ways. Cheap to add or remove at the ends; slow to jump to the middle.
Words you'll meet in this lesson
item, prev, next.prev is null.next is null.LinkedList vs ArrayList
| Operation | ArrayList | LinkedList |
|---|---|---|
get(i) / set(i) | O(1) | O(n) walk |
| Add/remove at the end | O(1) amortised | O(1) |
| Add/remove at the front | O(n) shift | O(1) |
| Add/remove in the middle by index | O(n) shift | O(n) walk, then O(1) relink |
| Add/remove at the iterator's position | O(n) shift | O(1) |
| Memory per element | one array slot | a whole node object (item + 2 links + header) |
Exam tip: LinkedList implements both List and Deque, so it has get(i) and addFirst/addLast/pollFirst/push/pop. It is a doubly linked list (Java has no built-in singly linked list class). get(i) walks from the nearer end, so it's O(n/2), still O(n).
Three ways to use a LinkedList
As a List
add, get(i), remove(i): works, but index access is slow.
As a queue
offer at the back, poll from the front: both O(1).
As a stack
push and pop at the front: O(1).
The Deque methods
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> train = new LinkedList<>();
train.add("S1");
train.add("S2");
train.addFirst("ENGINE"); // O(1): new first node
train.addLast("GUARD"); // O(1): new last node
System.out.println(train);
System.out.println(train.getFirst() + " … " + train.getLast());
train.removeFirst(); // O(1)
train.add(1, "PANTRY"); // walks to index 1, then relinks
System.out.println(train + " size " + train.size());
System.out.println(train.get(2)); // walks from the nearer end
}
}Queue and stack in one class
import java.util.Deque;
import java.util.LinkedList;
import java.util.Queue;
public class Main {
public static void main(String[] args) {
Queue<String> tokens = new LinkedList<>();
tokens.offer("T1");
tokens.offer("T2");
System.out.println("Serve " + tokens.poll() + ", next " + tokens.peek());
Deque<String> undo = new LinkedList<>();
undo.push("typed A");
undo.push("typed B");
System.out.println("Undo " + undo.pop());
}
}The trap: index loops over a LinkedList
for (int i = 0; i < list.size(); i++) {
total += list.get(i); // each get walks from an end: O(n) → the loop is O(n²)
}
for (int x : list) {
total += x; // the iterator just follows next links: O(n) total
}Never loop over a LinkedList with get(i). With 100,000 elements that's billions of node hops. Use for-each or an iterator, which simply follows the next links.
When to choose LinkedList: rarely. For a queue or stack, ArrayDeque is usually faster and uses less memory (the Java docs say so). For a general list, ArrayList. LinkedList shines when you hold an iterator in the middle of a long list and insert or remove there repeatedly, or when you need a List and a Deque in one object.
What addFirst and get do inside
An empty list
firstandlastare bothnull;sizeis 0. No array, no nodes.add("S1"): the first node
ANode{prev=null, item="S1", next=null}is created. Bothfirstandlastpoint to it.addFirst("ENGINE"): relink, don't shift
A new node is created withnext= the old first node. The old first node'sprevis set to the new node, andfirstmoves to it. Three reference writes, whatever the list's length.get(2): walk
node(2)checks whether 2 < size/2. If so, it starts atfirstand followsnexttwice; otherwise it starts atlastand followsprev. Each hop is a memory read, often a cache miss, because nodes are scattered around the heap.remove a node: unlink
The neighbours are joined:prev.next = nextandnext.prev = prev. The removed node is no longer reachable and the garbage collector frees it.
Nodes linking and unlinking: both views
Left: the list as your code sees it. Right: the node objects on the heap and the first/last references.
- An empty LinkedList.
addappends S1.addappends S2.addFirstputs E at the front.get(2)returns S2.removeFirstremoves E.
- An empty LinkedList has no nodes:
firstandlastare null. add("S1")creates one Node. Bothfirstandlastrefer to it.add("S2")creates a node, links S1.next → S2 and S2.prev → S1, and moveslast.addFirst("E")creates a node whose next is S1, sets S1.prev → E and movesfirst. S1 and S2 don't move at all.get(2): 2 is in the first half, so the walk starts atfirstand followsnexttwice. With a million nodes, a get in the middle means half a million hops.removeFirst()movesfirstto S1 and clears S1.prev. Nothing points to E any more, so the garbage collector will free it.
LinkedList and linked structures in real systems
The linked-node idea is everywhere, even when the class used isn't java.util.LinkedList.
Watch it with an example: browser back and forward
A browser's history behaves like a doubly linked list with a cursor. Watch Back and Forward follow the prev and next links.
- You visited Home, Menu, Cart and Pay. Each page links to the one before and after.
- Back follows the
prevlink: now on Cart. Forward would follownext. - Back again: Menu.
- From Menu you open Offers. The old forward pages (Cart, Pay) are unlinked and dropped; Offers becomes the new next page.
- Back from Offers goes to Menu, in O(1), however long the history is. (Simplified: real browsers store more per page.)
1. Browser and app history
Back and forward navigation is a doubly linked idea: each page knows the previous and next one. Undo/redo stacks in editors work the same way.
2. LRU caches
LinkedHashMap links its entries in a doubly linked list. In access-order mode it moves a used entry to the end in O(1), so the least-recently-used entry is always first: the basis of many caches.
3. Editing a long sequence in place
Text editors, playlist editors and route planners that insert and delete at a cursor position repeatedly benefit from linked structures and a ListIterator parked at the cursor.
4. A frequent code-review finding
Reviewers often replace LinkedList with ArrayList or ArrayDeque after spotting index loops or measuring memory, a quick win in many services.
Examples are simplified; real browsers and editors use their own data structures.
A playlist editor with a cursor
import java.util.LinkedList;
import java.util.ListIterator;
public class Main {
public static void main(String[] args) {
LinkedList<String> playlist = new LinkedList<>();
playlist.add("Intro");
playlist.add("Song A");
playlist.add("Song B");
playlist.add("Outro");
// put an ad after every song, using a cursor (ListIterator)
ListIterator<String> it = playlist.listIterator();
while (it.hasNext()) {
String track = it.next();
if (track.startsWith("Song")) {
it.add("Ad"); // O(1): links a node right here
}
}
playlist.removeLast(); // O(1)
playlist.addFirst("Welcome"); // O(1)
System.out.println(playlist);
System.out.println("Now playing: " + playlist.getFirst());
}
}listIterator()A cursor that can move both ways and insert or remove at its position (Lesson 11.7).it.add("Ad")Inserts after the current song by relinking two nodes: no shifting.removeLast()/addFirst()The ends are always O(1) thanks tofirstandlast.- No
get(i)anywhereThat's how LinkedList should be used.
The chain on the heap
This trace is recorded from the real JVM. The heap line shows the list's chain of elements (⇄ means linked both ways). Watch additions at the front and back, and a removal at the front: the other elements never move.
Worked examples: real problems, solved and traced in memory
Three tasks where LinkedList's ends and its List/Deque double life are useful, traced on the real JVM.
VIP tickets jump the queue
Problem statement
A support desk serves tickets in order, but VIP tickets go to the front. Tickets: T1, T2, VIP1, T3. Print the serving order.
T1, T2, VIP1, T3
VIP1 T1 T2 T3
Approach
- Use a LinkedList as a Deque.
- A helper
arrive(desk, id): VIP tickets withaddFirst, others withaddLast. - Serve with
pollFirstuntil empty.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Undo the last two edits
Problem statement
An editor records edits on a stack. After typing "Hello", " World" and "!", the user presses Undo twice. Print the text left.
edits: Hello, ␠World, !; undo × 2
Text: Hello
Approach
- Push each edit with
push(adds at the front). - Undo =
pop, which removes the most recent edit. - Rebuild the text from the remaining edits, oldest first, using
descendingIterator.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Add a stop after a given stop
Problem statement
A delivery route is [Depot, Kharghar, Vashi, Depot]. Insert "Belapur" right after "Kharghar" using a ListIterator, without indexes.
route, insert Belapur after Kharghar
[Depot, Kharghar, Belapur, Vashi, Depot]
Approach
- Walk the route with a ListIterator.
- When the current stop is Kharghar, call
it.add("Belapur"): an O(1) relink at the cursor. - Stop after inserting and print the route.
Solution: run it live
What happens in memory, step by step
Recorded from the real JVM: press Next to execute one line at a time. Watch method calls push and pop frames on the stack, and objects (numbered #1, #2 …) appear on the heap.
Check yourself
Quick revision
Lesson 11.4 in one screen
- LinkedList = doubly linked list of
Node{prev, item, next}withfirst,last,size. - Implements List and Deque:
get(i),addFirst/addLast,offer/poll,push/pop. - O(1): add/remove at either end, and at an iterator's position. O(n):
get(i),set(i),contains. - Never loop with
get(i)(O(n²)); use for-each or an iterator. - More memory per element than ArrayList; scattered nodes are slower to walk.
- Usually prefer ArrayList (lists) and ArrayDeque (queues/stacks).
Interview and viva questions
ArrayList vs LinkedList: when would you use each?
ArrayList for most lists: O(1) random access, compact memory, fast iteration. LinkedList when you insert/remove at the ends or at an iterator's position very often and rarely access by index, or when you need List and Deque behaviour in one object. In practice ArrayList (or ArrayDeque) wins most benchmarks because arrays are cache-friendly.
How does LinkedList.get(index) work?
It calls node(index): if the index is in the first half, it starts at first and follows next; otherwise it starts at last and follows prev. So at most n/2 hops, which is still O(n).
Is inserting in the middle of a LinkedList O(1)?
Only if you already have a reference to the position (an iterator there). add(i, x) by index first walks to position i (O(n)), then relinks (O(1)).
Can LinkedList hold null? Is it thread-safe?
It allows nulls (though using null in a queue makes poll()'s null return ambiguous). It isn't thread-safe; use ConcurrentLinkedQueue/ConcurrentLinkedDeque or a BlockingQueue for concurrent work.
How would you reverse a linked list?
With Collections.reverse(list), or, in the classic interview version with your own singly linked nodes, by walking the list once and flipping each node's next pointer to the previous node, keeping three references: prev, current, next. O(n) time, O(1) extra space.
Practice problems
Use LinkedList through its List and Deque methods, and avoid its traps. The last three are business tasks.
Both ends
Start with [2, 3], add 1 at the front and 4 at the back, then print the first and last elements.
[2, 3]
[1, 2, 3, 4] first 1 last 4
Show solution and explanation
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<Integer> l = new LinkedList<>(List.of(2, 3));
l.addFirst(1);
l.addLast(4);
System.out.println(l + " first " + l.getFirst() + " last " + l.getLast());
}
}How it works: the list keeps direct references to its first and last nodes, so all four calls are O(1).
Safe peek on an empty list
Print the next ticket of an empty queue without crashing, then add one and peek again.
empty, then T9
Next: null Next: T9
Show solution and explanation
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> q = new LinkedList<>();
System.out.println("Next: " + q.peekFirst());
q.offer("T9");
System.out.println("Next: " + q.peekFirst());
}
}How it works: peekFirst returns null for an empty list; getFirst would throw NoSuchElementException.
Iterate backwards
Print the stations [Panvel, Kharghar, Belapur, Vashi] in reverse using descendingIterator.
4 stations
Vashi Belapur Kharghar Panvel
Show solution and explanation
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<String> st = new LinkedList<>(List.of("Panvel", "Kharghar", "Belapur", "Vashi"));
Iterator<String> it = st.descendingIterator();
while (it.hasNext()) System.out.print(it.next() + " ");
System.out.println();
}
}How it works: the iterator starts at last and follows the prev links, possible because the list is doubly linked.
Rotate a list by one
Move the first element to the end three times: [A, B, C, D] → ?
[A, B, C, D], 3 rotations
[D, A, B, C]
Show solution and explanation
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<String> l = new LinkedList<>(List.of("A", "B", "C", "D"));
for (int i = 0; i < 3; i++) {
l.addLast(l.removeFirst());
}
System.out.println(l);
}
}How it works: each rotation is two O(1) operations at the ends. On an ArrayList, removing the first element would shift everything.
Remove every other element
Remove elements at odd positions from [10, 11, 12, 13, 14] with an iterator.
[10, 11, 12, 13, 14]
[10, 12, 14]
Show solution and explanation
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<Integer> l = new LinkedList<>(List.of(10, 11, 12, 13, 14));
Iterator<Integer> it = l.iterator();
int pos = 0;
while (it.hasNext()) {
it.next();
if (pos % 2 == 1) it.remove();
pos++;
}
System.out.println(l);
}
}How it works: it.remove() unlinks the current node in O(1) and keeps the iterator valid. No ConcurrentModificationException.
Last 3 notifications
A phone shows only the 3 most recent notifications, newest first. Add N1…N5 and print what's shown.
N1, N2, N3, N4, N5
[N5, N4, N3]
Show solution and explanation
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> shown = new LinkedList<>();
for (int i = 1; i <= 5; i++) {
shown.addFirst("N" + i);
if (shown.size() > 3) shown.removeLast();
}
System.out.println(shown);
}
}How it works: new items go to the front; the oldest drops off the back. Both ends are O(1).
Token desk with no-shows
Tokens T1–T4 wait. T2 doesn't show up and is skipped (sent to the back once). Print the serving order.
T1, T2(no-show once), T3, T4
T1 T3 T4 T2
Show solution and explanation
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<String> q = new LinkedList<>(List.of("T1", "T2", "T3", "T4"));
boolean t2Skipped = false;
StringBuilder served = new StringBuilder();
while (!q.isEmpty()) {
String t = q.pollFirst();
if (t.equals("T2") && !t2Skipped) {
t2Skipped = true;
q.addLast(t); // try again later
continue;
}
served.append(t).append(" ");
}
System.out.println(served.toString().trim());
}
}How it works: a skipped token is moved from the front to the back in O(1), and served on its second turn.
Merge two sorted delivery lists
Two drivers have delivery times sorted: [9, 12, 15] and [10, 11, 16]. Merge them into one sorted list.
[9, 12, 15], [10, 11, 16]
[9, 10, 11, 12, 15, 16]
Show solution and explanation
import java.util.LinkedList;
import java.util.List;
public class Main {
public static void main(String[] args) {
LinkedList<Integer> a = new LinkedList<>(List.of(9, 12, 15));
LinkedList<Integer> b = new LinkedList<>(List.of(10, 11, 16));
LinkedList<Integer> merged = new LinkedList<>();
while (!a.isEmpty() && !b.isEmpty()) {
merged.addLast(a.peekFirst() <= b.peekFirst() ? a.pollFirst() : b.pollFirst());
}
merged.addAll(a);
merged.addAll(b);
System.out.println(merged);
}
}How it works: always take the smaller front element; every step is O(1) at the ends, so the merge is O(n + m).
Real-world scenario questions (asked in company interviews)
LinkedList questions in interviews often test whether you can resist the textbook answer and reason about real costs.
“LinkedList because we insert a lot”
A teammate chose LinkedList for a list of 200,000 transactions “because we insert a lot”. Inserts are always at the end, and the screen reads rows by index for paging. What do you say?
The interviewer is checking: matching the data structure to the actual access pattern.
Show a strong answer
- Appending is O(1) amortised on ArrayList too, so LinkedList brings no benefit for these inserts.
- Paging uses
get(i)/subList, which is O(n) per access on LinkedList but O(1) on ArrayList. - LinkedList also costs much more memory per element (a node object each). Switch to ArrayList and measure.
Avoid: “LinkedList is always better for inserts.” Only inserts at the front or at an iterator's position are cheaper.
Pending ride requests
A service keeps pending ride requests in a queue: new ones at the back, drivers take from the front, sometimes a request is cancelled from the middle. LinkedList, ArrayDeque or something else?
The interviewer is checking: trade-offs between deque implementations.
Show a strong answer
- For offer/poll alone, ArrayDeque is the best fit: O(1) and compact.
- Cancelling from the middle is O(n) in both. If cancellations are frequent, keep a
LinkedHashMap<RequestId, Request>: O(1) removal by ID and insertion-order iteration for “oldest first”. - If several threads take requests, use a concurrent queue (Module 12).
Avoid: picking LinkedList only because it has remove(Object). It still searches linearly.
Find the middle in one pass
Given the head of a singly linked list of unknown length, return its middle node in one pass without counting first.
The interviewer is checking: the slow/fast pointer technique.
Show a strong answer
- Two references start at the head:
slowmoves one node,fastmoves two nodes per step. - When
fastreaches the end (null or last node),slowis at the middle. O(n) time, O(1) space. - The same idea detects cycles (if fast ever meets slow, there's a loop).
Avoid: copying the nodes into an ArrayList. It works, but uses O(n) extra memory and misses the point of the question.
Build an LRU cache
Design a cache of fixed capacity where get and put are O(1) and the least-recently-used entry is evicted when full.
The interviewer is checking: combining a hash map with a doubly linked list.
Show a strong answer
- A HashMap from key to node for O(1) lookup, plus a doubly linked list ordered by recency: move a node to the tail on every access, evict from the head.
- In Java,
LinkedHashMapwithaccessOrder = trueand an overriddenremoveEldestEntrygives exactly this in a few lines. - For multi-threaded use, add locking or use a library cache.
Avoid: a plain list scanned for the oldest entry: O(n) per operation.