The absolute basics: Linked Lists
Mental Model
Arrays are fast at reading data because they store everything in a single, perfectly lined-up block of memory. But inserting at the front means shifting every single element — O(N).
Linked Lists solve this by sacrificing contiguous memory. Data lives in independent Nodes scattered across memory, connected by pointers. Insertion is O(1) — just update two pointers instead of shifting everything.
Why Linked Lists are fast for insertion: Because the items aren't physically locked next to each other. You create a new Node anywhere and tell surrounding items to update their pointers. It's an instant change!
Data Shifting Required: O(N)
Only 1 Pointer Changes: O(1)
Direct Memory Jump: O(1)
Sequential Traversal: O(N)
Creating and Accessing a Linked List
A ListNode is just an object with a val and a next pointer. To build a list, link nodes together. To access elements, loop until null.
Dynamic Visualizer: Core Operations
O(N): Traversing node by node to find the target. No direct index.
In-Place Reversal
The 3-Pointer Dance
Reverse direction without using extra memory by flipping arrows as you go. Save next → reverse → shift.
- Save
curr.nextbefore breaking it - Reverse the arrow:
curr.next = prev - Shift both pointers forward
- Return
prevas the new head
Dummy Node Strategy
The Safe Anchor
Provides a safe anchor when the head might change or is created dynamically. Prevents null-pointer edge cases.
- Create a
dummynode (value doesn't matter) - Attach new nodes to
tail.next - Return
dummy.next— skips the anchor - Essential for merging, partitioning, removal
Fast & Slow Pointers
A linked list has no indices, so you can't jump to "the middle" or check "does this loop back on itself" directly. Move two pointers at different speeds instead — one step at a time (Slow) and two at a time (Fast). When Fast reaches the end, Slow is at the middle. If the list has a cycle, Fast eventually laps Slow from behind.
Common Pitfalls
Losing the Future
When reversing arrows, doing curr.next = prev before saving the original curr.next loses the rest of the list. Always cache next first.
Forgetting a Dummy Node
Deleting the head node is a special case — there's no "previous" node to re-point. A dummy node placed before head removes this special case entirely, so head-deletion and middle-deletion use identical code.
Losing the Tail While Appending
Building a list by repeatedly walking from head to find the last node turns every append into O(N). Keep a running tail pointer so appends stay O(1).
Comparing Nodes vs Values
slow === fast checks if they're the same node (pointer identity) — this is what cycle detection needs. slow.val === fast.val checks equal values, which two different nodes can share and gives a false positive.
How to Spot This Pattern
🔎 "Restructure the list in place" with O(1) extra space.
🔎 "Reverse in groups of K" or "reverse between positions."
🔎 "Merge" two or more already-sorted lists.
🔎 "Nth node from the end" — a two-pointer gap, no length pre-count needed.
"Save the future, break the present, and move into the past."