15

Linked Lists

Nodes that hold a value and a reference to the next node — cheap to rewire, slow to index.

beginner~90 min7 simulations18 problems

▶ guided lesson⚡ 1-minute summary↻ 14 recall cards

before this: Arrays & Strings · Two Pointers · Big-O

01 · why it exists

Why this exists, and the weight it carries

since1956

Singly linked list

Allen Newell, J. C. (Cliff) Shaw and Herbert A. Simon

In 1956 Newell, Shaw and Simon were building the Logic Theory Machine, a program that searched for proofs in logic. Its data grew in shapes and sizes nobody could predict, so a fixed block of memory per list would either overflow or sit half empty. Their fix: each memory cell points to the next one, and new cells come from a shared pool of free cells — the first list-processing languages (IPL). A 1960 paper already named the price: the links ate about half the fast memory, and reaching the k-th item got slow.

the problem it solvedStoring data whose size and shape you can't know in advance, without reserving big fixed blocks that overflow or sit empty.

its jobs today

  • Hash tables: Java's HashMap keeps colliding entries in a short linked chain per bucket (and turns very long chains into small trees since Java 8).
  • Free lists: memory allocators thread a linked list through the free blocks themselves, so grabbing or returning a block is O(1).
  • A building block for bigger structures: adjacency lists in graphs, the chain inside an LRU cache, and the linked queue of the next chapter.

the weight it carries

Illustrative: a sequence of 10 lakh (1,000,000) items. Insert at the front: linked list = 1 new node + 2 reference writes; ArrayList.add(0, x) = shift all 1,000,000 items. Read item #500,000: linked list = 500,000 hops; array = 1 address calculation (base + 500,000 × size).

without it

Every growing, splicing structure would need big up-front arrays and O(n) shifts — no cheap hash-table chains, free lists or O(1) cache eviction.

when · why · how

when
Lots of inserts and deletes at the front or next to nodes you already hold — and every interview problem that hands you a ListNode.
why
Splicing is O(1) because nothing shifts; the price is O(n) to reach the k-th node, plus an object and a reference per item — far less cache-friendly than an array.
how
A tiny class ListNode { int val; ListNode next; } and a head reference. For everyday sequences prefer ArrayList / ArrayDeque; java.util.LinkedList is the library's doubly linked version.

02 · plain english

The idea in 30 seconds

A linked list is a chain of nodes. Each node holds a value and a reference (next) to the node after it.

Nodes live anywhere in memory. Only the references hold the order — so inserting or deleting is just rewiring arrows, O(1) once you are at the spot.

The price: no indexes. To reach the 5th node you must walk 4 hops from head — O(n).

picture this

A web series where every episode page has only a Next episode link. Slipping a bonus episode in after episode 3 means changing one link. But to reach episode 7 from the start, you click Next six times — there is no "jump to 7".

03 · remember

What you must know

  • A node is val + next. The list itself is just a reference to the first node, head. The last node's next is null.
  • Singly linked: one next. Doubly linked: prev + next, so you can remove a node you hold in O(1). Circular: the tail points back to the head.
  • Insert / delete is O(1) if you already hold the node before the spot. Finding that spot is O(n). Access by index is O(n) — the opposite trade-off from arrays.
  • Dummy head trick: put a fake node before head. Every real node now has a predecessor, so "change the head" stops being a special case. Return dummy.next.
  • Slow / fast pointers: fast moves 2, slow moves 1. Finds the middle, detects a cycle (Floyd), and finds the cycle's entry — all in O(1) space.
  • Order of rewiring matters: save curr.next before you overwrite it, or the rest of the list is lost forever.
  • Any "next" rule is a linked list: i → nums[i] turns an array into a chain, so Floyd can find a repeated value without extra memory (LC 287).
  • In Java, a node nobody references is garbage-collected — "deleting" means making it unreachable.
  • java.util.LinkedList is doubly linked. For interview problems you almost always write your own tiny ListNode; for a stack or queue, prefer ArrayDeque.

04 · see it

Play with it in 3D

Press Space to play, ←/→ to step, drag to move, ? for all shortcuts. Try your own input.

sim

Insert at a position

Walk `prev` to the node before the spot, then change exactly two references. The order of the two changes matters.

loading simulation…
sim

Delete by value with a dummy head

Deleting = skipping. The removed node drops away because nothing points to it any more.

loading simulation…
sim

Reverse — flip one arrow per step

`prev`, `curr`, `next`: save, flip, advance. The green part is already reversed.

loading simulation…
sim

Floyd's cycle detection

Set `pos` to make the tail loop back (−1 = no loop). Inside a loop, fast gains one node per step, so it must catch slow.

loading simulation…
sim

Find the middle in one pass

When fast has run the whole list, slow has run half of it.

loading simulation…
sim

Merge two sorted lists

Nodes are not copied — `tail.next` re-links them onto the bottom row one by one.

loading simulation…
sim

A doubly linked list at work: the LRU cache

Top: the HashMap's keys. Bottom: the list between two sentinels, most recent first. Every get and put is a lookup plus a few arrow changes.

loading simulation…

05 · cost

Time & space, explained

Linked list vs array list
Singly linkedDoubly linkedArrayListwhy
Get the k-th elementO(n)O(n)O(1)A list has no addresses to compute — you hop k times. An array computes base + k × size directly.
Insert / delete at headO(1)O(1)O(n)The list rewires one or two references. The array must shift every element one slot.
Insert at tailO(1)*O(1)O(1) amortized*Only if you keep a tail reference. ArrayList appends in O(1) except an occasional O(n) resize.
Delete at tailO(n)O(1)O(1)Singly linked must walk to the second-to-last node to set its next = null. Doubly linked has tail.prev.
Insert / delete next to a node you holdO(1)O(1)O(n)Just rewire. Singly linked can only cheaply delete the node after the one you hold; doubly linked can delete the node itself.
Search by valueO(n)O(n)O(n)No order to exploit — check elements one by one (binary search doesn't work on lists: no O(1) middle).
Extra memory per element1 reference + object header2 references + object header1 slot (amortized)Every node is a separate object. Arrays store values side by side, which is also far friendlier to the CPU cache.
The core techniques
TimeExtra spacewhy
Reverse (iterative)O(n)O(1)Each node's arrow is flipped once with three references.
Reverse (recursive)O(n)O(n)Same work, but n stack frames — can overflow on long lists.
Reverse a section / k at a timeO(n)O(1)Each node is flipped once; finding each block's k-th node adds one more walk over it.
Find middle (slow / fast)O(n)O(1)fast walks the list once; slow walks half.
Cycle detection (Floyd)O(n)O(1)slow enters the cycle within n steps; fast then closes the gap one node per step.
Cycle detection (HashSet)O(n)O(n)Stores every visited node.
Merge two sorted listsO(n + m)O(1)Each step re-links one node; nothing is copied.
Palindrome check (middle + reverse + compare)O(n)O(1)A few linear passes; the second half is reversed in place and restored.
Rotate by kO(n)O(1)One pass counts the nodes and finds the tail; k % n drops whole laps, so at most one more pass reaches the cut.

06 · java

Raw vs Collections Framework

The node — the only class you need

javaListNode (LeetCode's definition)java.util.*
class ListNode {    int val;        // the data    ListNode next;  // reference to the next node (null at the end)     ListNode() {}    ListNode(int val) { this.val = val; }    ListNode(int val, ListNode next) { this.val = val; this.next = next; }}
  • LeetCode gives you this class. next holds a reference (an arrow), not a copy of the next node.
  • A list is just ListNode head. An empty list is head == null — check for it first in every problem.
  • == on nodes compares identity (same object). That is exactly what you want for cycles and intersections.

Doubly linked: build it vs use java.util.LinkedList

javahand-written with sentinelsjava.util.*
class DoublyLinkedList {    static class Node {        int val;        Node prev, next;        Node(int val) { this.val = val; }    }     // Two sentinels: the list is never "empty" internally, so no null checks.    private final Node head = new Node(0), tail = new Node(0);    private int size;     DoublyLinkedList() {        head.next = tail;        tail.prev = head;    }     Node addFirst(int val) { return insertAfter(head, new Node(val)); } // O(1)    Node addLast(int val) { return insertAfter(tail.prev, new Node(val)); } // O(1)     private Node insertAfter(Node p, Node x) {        x.prev = p;        x.next = p.next;        p.next.prev = x;        p.next = x;        size++;        return x;    }     /** O(1) — we already hold the node, and it knows both neighbours. */    void remove(Node x) {        x.prev.next = x.next;        x.next.prev = x.prev;        size--;    }     int removeLast() {        if (size == 0) throw new NoSuchElementException();        Node x = tail.prev;        remove(x);        return x.val;    }     int size() { return size; }     List<Integer> toList() {        List<Integer> out = new ArrayList<>();        for (Node p = head.next; p != tail; p = p.next) out.add(p.val);        return out;    }}
  • Two sentinel nodes (head, tail) that are never removed mean insertAfter and remove never check for null.
  • Removing a node you hold is O(1) in a doubly linked list. LinkedList hides its nodes, so you only get that via a ListIterator.
  • list.get(i) on a LinkedList walks from the nearer end: O(n). Calling it in a loop is a hidden O(n²).
  • For a stack or queue, the Java docs recommend ArrayDeque: it is usually faster than LinkedList (no node objects, better cache use).
  • Interviews that say "design" (LRU cache, browser history) want the hand-written doubly linked list — that is the whole point of the question.
Java API you will actually use
calldoescost
list.addFirst(x) / addLast(x)Insert at either end (LinkedList is a Deque)O(1)
list.removeFirst() / removeLast()Remove at either end; throws if emptyO(1)
list.peekFirst() / pollFirst()Look at / remove the head; return null if emptyO(1)
list.get(i) / list.set(i, x)Walks from the nearer end to index iO(n)
list.add(i, x) / list.remove(i)Walk to i, then rewireO(n)
list.contains(x) / remove(Object)Linear search by equals()O(n)
listIterator().add(x) / .remove()Insert / delete at the iterator's cursorO(1)
new ArrayDeque<>()Array-backed deque: the default stack / queueO(1) amortized ops
new LinkedHashMap<>(16, 0.75f, true)HashMap + doubly linked list in access order (LRU)O(1) average

A LeetCode linked-list problem

→ Your own ListNode + references

The problem gives you nodes; the skill tested is rewiring them.

A stack or a queue

→ ArrayDeque

O(1) at both ends, less memory and faster than LinkedList.

Indexed reads in a loop

→ ArrayList

O(1) get(i); LinkedList would be O(n) per read.

Many inserts/removals while iterating

→ LinkedList + ListIterator

O(1) at the cursor; ArrayList shifts O(n) per change.

O(1) lookup and O(1) reordering (LRU)

→ HashMap + doubly linked list (or LinkedHashMap)

The map finds the node; the list keeps the order.

07 · recognise

Spot the pattern

"Middle of the list", "split in half", "palindrome list"

slow / fast pointers (fast moves 2)

"Cycle", "loop", "where does the loop start"

Floyd's tortoise and hare (then reset one pointer to head)

"n-th from the end" in one pass

Two pointers with a fixed gap of n

The head might change (delete head, merge, partition)

Dummy head node — return dummy.next

"Reverse" all or part of a list, "reorder", "k-group"

prev / curr / next rewiring

Two sorted lists → one sorted list

Merge with dummy + tail (merge-sort step)

Split by a rule but keep the order (odd/even positions, < x vs ≥ x)

Two dummy-headed chains, then join — and end the second with null

"Rotate the list by k" with a huge k

Count n, use k % n, close into a ring, cut once

n + 1 values in 1..n, find the repeat, O(1) space, don't modify

Treat i → nums[i] as a linked list; Floyd finds the cycle's entry

"O(1) get and put", "most recently used"

HashMap + doubly linked list (LRU)

Deep copy with extra pointers

HashMap old → new, or interleave copies to get O(1) space

08 · traps & tricks

Mistakes to avoid, tricks to use

traps

  • Overwriting curr.next before saving it — the rest of the list becomes unreachable.
  • Dereferencing null: fast.next.next needs fast != null && fast.next != null checked first, in that order.
  • Forgetting to set the new tail's next = null (after reversing with a stack, reordering, splitting) — creates an accidental cycle.
  • Returning head when the head itself was deleted or moved — return dummy.next instead.
  • Comparing node values when the problem needs node identity (cycles, intersections): use == on nodes.
  • Calling LinkedList.get(i) inside a loop: each call walks the list, so the loop is O(n²).
  • Losing the original head because you moved head itself while walking — walk with a separate curr.
  • Using k as given when it can be far larger than the length (Rotate List): reduce it with k % n — after checking the list isn't empty.
  • Recursion on lists of 10⁴–10⁵ nodes can throw StackOverflowError; prefer the iterative version.

tricks

  • Draw boxes and arrows for 3 nodes before coding. Re-draw after each line you write. Most bugs show up on paper.
  • Say your invariants out loud: "prev is the head of the reversed part; curr is the first unreversed node."
  • Default to a dummy head whenever the first node could change — it costs one line and removes a whole class of bugs.
  • Test every list solution on: empty list, one node, two nodes, odd and even length, and the target at the head and at the tail.
  • Many hard list problems are combos: Reorder List = find middle + reverse + merge; Palindrome = find middle + reverse + compare; k-group = reverse a block, repeat. Learn the moves cold.
  • In an interview, state up front whether you may modify the input list — it changes which solution is allowed.

09 · why & how

Questions you might be too shy to ask

If linked lists are slower to index, why do they exist at all?

Because inserting or deleting in the middle is O(1) once you're there — no shifting. That is why they power LRU caches, hash-table chains and memory free lists. In everyday Java code, though, ArrayList and ArrayDeque usually win because arrays are cache-friendly.

When I write curr = curr.next, am I changing the list?

No. curr is just a local arrow. Moving it changes where you are looking, not the list. The list changes only when you assign to a field: curr.next = something. So walking with curr is safe, but curr.next = null cuts the list right there. Bas itna yaad rakho: x = … moves a pointer, x.next = … rewires the list.

What does dummy actually buy me?

Every real node gets a node in front of it, so "insert before the head" and "delete the head" use the same code as any other position. Without it you need if (head == null) and if (target == head) branches — the classic source of bugs.

Should my loop be while (curr != null) or while (curr.next != null)?

Ask where the loop must stop. curr != null visits every node, then curr ends as null — use it to print, count, or reverse. curr.next != null stops on the last node — use it to find the tail (Rotate List) or when the body reads curr.next.val. The second one crashes on an empty list, so check head == null first.

Which middle does slow land on, and which loop condition should I use?

On [1, 2, 3, 4] there are two middles, 2 and 3. while (fast != null && fast.next != null) stops slow on the second middle (3) — that is what LC 876 wants. while (fast.next != null && fast.next.next != null) stops slow on the first middle (2) — use it to split a list in half (Palindrome, Reorder List, merge sort), because the first half then ends at slow. It reads fast.next straight away, so check head == null first. Odd lengths give the same single middle with both.

Why does Floyd's algorithm always meet, and why can't fast jump over slow?

Once both are in the loop, fast gains exactly one node on slow each step (2 − 1). The gap shrinks 3, 2, 1, 0 — it can't skip from 1 to −1. So they meet within one lap, and the whole run is O(n).

Why does resetting one pointer to head find the cycle's start?

Let a = steps from head to the entry and b = steps from the entry to the meeting point. slow walked a + b, fast walked twice that, and the difference a + b is a whole number of laps. So walking a more steps from the meeting point lands exactly on the entry — the same a steps a pointer from head needs.

How can an array (LC 287) have a "cycle"?

Read index i as a node whose next is nums[i]. Every node has exactly one arrow out, and there are finitely many nodes, so a walk from index 0 must eventually repeat — that's a loop. The value stored at two indices has two arrows coming in, so it is where the loop is entered.

Do I need to free memory when I delete a node in Java?

No. Once nothing references a node, the garbage collector reclaims it. Your only job is to make it unreachable by rewiring next (and prev). In C/C++ you'd call free/delete.

Can I just swap the values instead of moving nodes?

Sometimes it passes, but it dodges the skill being tested, and several problems forbid it ("you may not modify the values" in Reorder List and k-Group). Real nodes often carry more than an int, so moving the node is the honest answer. Say this out loud if you're tempted.

For LRU Cache, why a doubly linked list? Wouldn't a singly linked one work?

To unlink a node you must change the next of the node before it. The HashMap hands you the node itself. With prev you reach its neighbour in one step, so unlinking is O(1). With only next you would walk from the head to find the node before it — O(n), and the O(1) promise breaks.

Should I use java.util.LinkedList in interviews?

Rarely. For list problems, you're given ListNode and must rewire it yourself. For stacks/queues, ArrayDeque is better. Use LinkedList only when you truly need cheap insert/remove at an iterator position.

Iterative or recursive reversal?

Iterative is O(1) space and can't overflow the stack — lead with it. Know the recursive version too: interviewers often ask for it as a follow-up to check you understand head.next.next = head.

I follow the reversal when I read it, but my own code keeps losing nodes. How do I practise?

Make a small table with columns prev, curr, next and fill one row per loop step for [1, 2, 3]. Chalo, dry-run karte hain — on paper, not in your head. Then step the reverse sim and check that every row matches yours. Write the four lines (save, flip, move prev, move curr) from memory once a day for a week. After that, Reverse Linked List II and k-Group are the same four lines with a hook at each end.

10 · practice

Interview problems

Every problem has brute → better → optimal solutions in Java, with complexity and a dry run.

  1. #206
    Reverse Linked Listmust-do
    EasyAdobeAppleMicrosoftBloombergOracle+1
  2. #21
    Merge Two Sorted Listsmust-do
    EasyBloombergAmazonMicrosoftGoogleApple+1
  3. #876
    Middle of the Linked List
    EasyWalmartAmazonMicrosoftApple
  4. #141
    Linked List Cyclemust-do
    EasyBloombergMicrosoftAmazonGoogleMeta
  5. #234
    Palindrome Linked Listmust-do
    EasyAmazonMetaMicrosoft
  6. #19
    Remove Nth Node From End of Listmust-do
    MediumMetaMicrosoftAmazonBloombergApple+1
  7. #142
    Linked List Cycle IImust-do
    MediumMicrosoftAmazonBloombergGoogle
  8. #287
    Find the Duplicate Numbermust-do
    MediumNVIDIAAmazonBloombergGoogleMicrosoft
  9. #92
    Reverse Linked List II
    MediumAmazonMicrosoftMeta
  10. #328
    Odd Even Linked List
    MediumAmazonMicrosoft
  11. #86
    Partition List
    MediumAmazonMicrosoft
  12. #82
    Remove Duplicates from Sorted List II
    MediumBloombergMicrosoftAmazon
  13. #61
    Rotate List
    MediumBloombergAmazonMicrosoft
  14. #143
    Reorder Listmust-do
    MediumAmazonMicrosoftMeta
  15. #2
    Add Two Numbersmust-do
    MediumAdobeBloombergMicrosoftAmazonGoogle+1
  16. #138
    Copy List with Random Pointer
    MediumMetaAmazonNVIDIAMicrosoftBloomberg+1
  17. #25
    Reverse Nodes in k-Groupmust-do
    HardMicrosoftAmazonGoogleMeta
  18. #146
    LRU Cachemust-do
    MediumAppleOracleSamsungByteDance / TikTokWalmart+1

Also practise this here

Each problem has one home page in the course; these use this chapter's idea too. The note says what to look for.

  1. #24
    Swap Nodes in Pairs
    Mediumin Recursion

    Reverse Nodes in k-Group with k = 2 — a good warm-up before LC 25.

  2. #23
    Merge k Sorted Lists
    Hardin Heaps

    This chapter's merge, run on k lists at once with a min-heap of the current heads.

  3. #148
    Sort List
    Mediumin Sorting

    Merge sort on a linked list: split with slow/fast, sort both halves, merge — three moves from this chapter.

  4. #707
    Design Linked List
    Mediumin Java OOP

    Build the list class yourself: get, addAtHead, addAtTail, addAtIndex and deleteAtIndex with a dummy head.

11 · revise

The whole chapter on one screen

10 cards, about a minute. Read them the night before an interview, or right after the chapter to lock it in.

  1. what it is

    A chain of nodes: each holds val and a next reference, and the list is just head. Splicing is O(1) once you're there; reaching the k-th node is O(k).

  2. use it when

    Inserts and deletes next to nodes you hold, LRU caches, hash chains — and every interview problem that hands you a ListNode.

  3. template

    Dummy head: ListNode dummy = new ListNode(0, head); walk with prev, then return dummy.next; — no special case when the head changes.

  4. template

    Reverse: next = curr.next; curr.next = prev; prev = curr; curr = next; then return prev. Save before you flip.

  5. template

    Slow/fast: while (fast != null && fast.next != null) → middle (2nd), cycle (they meet), n-th from end (gap of n + 1).

  6. cost

    Index O(n) · splice O(1) once there · reverse / middle / cycle O(n) time, O(1) space · recursion over a list = O(n) stack.

  7. trap

    Overwriting curr.next before saving it loses the rest of the list. Forgetting tail.next = null after splitting creates a cycle.

  8. trap

    Cycles need node identity (==), not equal values. And fast.next.next needs fast != null && fast.next != null checked first.

  9. pro tip

    Hard list problems are combos: Reorder = middle + reverse + merge · Palindrome = middle + reverse + compare · k-group = reverse a block, repeat.

  10. pro tip

    O(1) get and put with recency → HashMap + doubly linked list (or LinkedHashMap in access order).

12 · recall

Flip, remember, repeat

card 1 / 14
recall

Why put a dummy node in front of head?

tap or Space to flip ↻

Question 1: Why put a dummy node in front of head?

13 · check yourself

Quick quiz

  1. You hold a reference to a node in a singly linked list. Which operation is O(1)?

  2. In iterative reversal, why must `next = curr.next` come before `curr.next = prev`?

  3. What does a dummy head node save you from?

  4. Floyd's algorithm: inside a cycle, how much does fast gain on slow each step?

  5. List [1,2,3,4,5,6]. slow/fast with `while (fast != null && fast.next != null)` — where does slow stop?

  6. Why does an LRU cache need a *doubly* linked list?

  7. `LinkedList<Integer> list` has n elements. What does `for (int i = 0; i < n; i++) list.get(i);` cost?

  8. Reverse Nodes in k-Group on [1,2,3,4,5] with k = 3. What is the result?

  9. Find the Duplicate Number as a linked list (i → nums[i]): which index is the head of the walk?