Beginner 5 min · March 05, 2026

Singly Linked List — The Bug That Lost 8% of User Tabs

8% of users lost browser tabs due to a head pointer traversal bug.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Node = data + next pointer. List = head pointer + chain of nodes.
  • Insert at head: O(1). Just rewire head pointer.
  • Insert at tail: O(n) without tail pointer. Must traverse to end.
  • Delete: O(n) to find, O(1) to unlink. Just redirect previous node's pointer.
  • Random access by index: O(n). No direct memory jump like arrays.
  • O(1) front insertion vs O(n) random access. Choose based on access pattern.
  • Java's LinkedList implements Deque — O(1) addFirst/removeFirst for queue operations.
  • Losing the head reference orms the entire list. Always traverse with a separate variable.
  • Forgetting null check before dereferencing nextNode. Causes NullPointerException at the last node.
Plain-English First

Picture a treasure hunt where each clue doesn't tell you the final answer — it just tells you where the NEXT clue is hidden. Each clue is a 'node': it holds a piece of information AND a pointer to the next location. The last clue says 'dead end' because there's nowhere left to go. That's a singly linked list — a chain of nodes where each one knows only about the one right after it, never the one before.

A singly linked list is a dynamic data structure where each element (node) stores a value and a pointer to the next node. Unlike arrays, nodes are not contiguous in memory — each node can be anywhere on the heap. This gives O(1) insertion and deletion at the head, but O(n) random access.

The structure solves the rigid-size problem of arrays. No pre-allocated block. No resizing overhead. Nodes are allocated on demand and linked via pointers. Deletion is a arrays.

The common misconception is that linked lists are always better than arrays. They are not. Linked lists have poor cache locality (nodes scattered on the heap), O(n) random access, and per-node pointer overhead. Use them when front insertion/deletion is frequent and random access is rare.

Worked Example — Tracing Insertion, Deletion, and Reversal

Build list: insert 1,2,3 at head → 3→2→1→None. Insert 4 at tail: traverse to tail (node 1), set node1.next = Node(4) → 3→2→1→4→None.

Delete node with value 2: 1. prev=None, curr=3. 3!=2, prev=3, curr=2. 2. 2==2: prev.next = curr.next = 1. Skip node 2. List: 3→1→4→None.

Reverse 3→1→4→None: 1. prev=None, curr=3. Save next=1. Set 3.next=None. prev=3, curr=1. 2. Save next=4. Set 1.next=3. prev=1, curr=4. 3. Save next=None. Set 4.next=1. prev=4, curr=None. 4. New head=prev=4. List: 4→1→3→None.

Find middle of 4→1→3→None: Slow=4, fast=4. Step 1: slow=1, fast=3. Step 2: fast.next=None, stop. Middle=slow=1 (second of three nodes).

Three Pointer Pattern for Reversal
  • prev = null (reversed prefix starts empty). curr = head. next = null.
  • Loop: save next = curr.next. Set curr.next = prev. Move prev = curr, curr = next.
  • After loop: prev is the new head. The entire list is reversed.
  • Time: O(n). Space: O(1). No extra data structure needed.
  • This pattern appears in: reverse list, palindrome check, reorder list, reverse k-group.
Production Insight
A document editor used linked list reversal to implement undo/redo. Each edit was a node in a singly linked list. To undo, the editor reversed the list from the current position back to the last checkpoint. With 10,000 edits, reversal took 0.02ms — O(n) with excellent constant factors because each step is just a pointer swap. The alternative (copying the list to an array, reversing, copying back) would have taken 0.5ms and allocated 80KB of temporary memory.
Key Takeaway
The three-pointer reversal pattern (prev, curr, next) is the foundation for most linked list interview problems. Master it and you can solve reversal, cycle detection, palindrome check, and merge. Time O(n), space O(1).

How a Singly Linked List Works — Plain English and Operations

A singly linked list is a chain of nodes where each node stores a value and a pointer to the next node. The last node's next pointer is None. A head pointer tracks the first node.

Unlike an array, nodes are not contiguous in memory — each node can be anywhere on the heap. This means random access requires traversal (O(n)), but insertion and deletion at a known position are O(1) — just update pointers.

Key operations: 1. Insert at head (O(1)): new.next = head; head = new. 2. Insert at tail (O(n) without tail pointer; O(1) with): traverse to last, last.next = new. 3. Delete node with value v (O(n)): traverse until prev.next.val == v; prev.next = prev.next.next. 4. Search for value v (O(n)): traverse comparing each node's value.

Worked example — insert 1, then 2 at head, then delete 1: Insert 1: head -> [1] -> None. Insert 2 at head: head -> [2] -> [1] -> None. Delete 1: traverse — head.next.val=1. Set head.next = head.next.next = None. Result: head -> [2] -> None.

Pointer Rewiring is the Core Operation
  • Insert at head: newNode.next = head; head = newNode. Two pointer assignments.
  • Delete: prev.next = curr.next. One pointer assignment. The deleted node becomes unreachable.
  • Reverse: at each node, flip next to point backward. n pointer flips.
  • Every operation is O(1) per pointer change. The cost is finding where to change.
  • Master pointer rewiring and every linked list problem becomes mechanical.
Production Insight
A message queue used a singly linked list for its internal buffer. Messages were inserted at the tail (O(1) with tail pointer) and consumed from the head (O(1)). The queue processed 50,000 messages per second. With an ArrayList, each dequeue would require shifting 50,000 elements — O(n) per dequeue. With the linked list, dequeue was O(1): just advance the head pointer. The ArrayList queue throughput dropped to 2,000 messages per second.
Key Takeaway
Pointer rewiring is the core of linked lists. Insert: connect new node. Delete: bypass target node. Reverse: flip all pointers. The cost is traversal to find the position — O(n). The pointer change itself is O(1).

What a Node Actually Is — The Building Block of Everything

Before you can understand a linked list, you need to deeply understand a node — because the list IS just a collection of nodes.

Think of a node like a Post-it note stuck to a wall. The Post-it has two things written on it: the actual information you care about (say, a person's name) and an arrow pointing to the next Post-it on the wall. That arrow is the 'next pointer'. Without it, each Post-it is just a lonely, disconnected piece of paper.

In Java, a node is a small class with exactly two fields. First: the data it holds (could be a number, a name, anything). Second: a reference — Java's version of a pointer — to another node object. When that reference is null, you've hit the end of the list. Java uses null the same way the last clue in our treasure hunt says 'dead end'.

Here's the key insight that trips beginners up: the node doesn't know its own position. It has no idea it's node number 5. It only knows two things — its own data, and who comes next. That simplicity is what makes the whole structure so flexible.

io/thecodeforge/ds/Node.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package io.thecodeforge.ds;

/**
 * A Node is the fundamental building block of a singly linked list.
 * Each node holds a value and a reference to the next node.
 * When nextNode is null, this is the last node in the chain.
 */
public class Node {

    int data;
    Node nextNode;

    public Node(int data) {
        this.data = data;
        this.nextNode = null;
    }

    public static void main(String[] args) {
        Node firstNode = new Node(42);
        Node secondNode = new Node(99);

        firstNode.nextNode = secondNode;

        System.out.println("First node data:  " + firstNode.data);
        System.out.println("Second node data: " + firstNode.nextNode.data);
        System.out.println("End of chain:     " + firstNode.nextNode.nextNode);
    }
}
Output
First node data: 42
Second node data: 99
End of chain: null
Key Insight: Node Has No Position
  • Node has two fields: data (the value) and nextNode (the pointer).
  • nextNode = null means end of list. No sentinel node needed.
  • Node does not know its index. Position is determined by traversal from head.
  • In production: encapsulate with getters/setters. For learning: public fields are fine.
  • The simplicity of the node (data + pointer) is what makes the list flexible.
Production Insight
A graph adjacency list implementation used nodes with an additional neighbors field (a list of references to other nodes). The node structure was the same — data + pointers — but the pointers formed a graph, not a chain. Understanding that a linked list is just a special case of a graph (each node has at most one outgoing edge) helped the team reason about cycle detection, reachability, and memory management.
Key Takeaway
A node is data + a pointer. It has no position, no index, no knowledge of the list. The list is an emergent property of the chain of pointers starting at head. Keep node design simple — two fields are enough for singly linked lists.

Building a Singly Linked List — Insert, Traverse and Delete

Now that you understand a node, let's build an actual linked list. A singly linked list needs one thing to function: a reference to the very first node, called the 'head'. If you lose the head reference, you lose the entire list — like dropping the first clue in the treasure hunt and having no idea where to start.

The three operations you must master are: inserting a node (at the beginning, end, or middle), traversing the list (walking through it from head to tail), and deleting a node.

Inserting at the front is O(1) — lightning fast. You create a new node, point it at the current head, then make it the new head. Done. Inserting at the end is O(n) because you have to walk all the way to the last node first.

Deletion is where linked lists really shine compared to arrays. To remove a node, you just redirect the previous node's pointer to skip over the target node. The target becomes unreachable and Java's garbage collector cleans it up. No shifting of elements like in an array. No copy operations. Just a pointer redirect.

The code below builds a complete, working linked list with all three operations. Read the comments carefully — they tell the story of what's happening at each step.

io/thecodeforge/ds/SinglyLinkedList.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package io.thecodeforge.ds;

/**
 * Production-grade singly linked list with insert, delete, traverse,
 * reverse, cycle detection, and tail-pointer optimization.
 */
public class SinglyLinkedList {

    static class Node {
        int data;
        Node nextNode;

        Node(int data) {
            this.data = data;
            this.nextNode = null;
        }
    }

    private Node head;
    private Node tail;
    private int size;

    public SinglyLinkedList() {
        this.head = null;
        this.tail = null;
        this.size = 0;
    }

    public void insertAtFront(int value) {
        Node newNode = new Node(value);
        newNode.nextNode = head;
        head = newNode;
        if (tail == null) {
            tail = newNode;
        }
        size++;
    }

    public void insertAtEnd(int value) {
        Node newNode = new Node(value);
        if (head == null) {
            head = newNode;
            tail = newNode;
        } else {
            tail.nextNode = newNode;
            tail = newNode;
        }
        size++;
    }

    public boolean deleteByValue(int value) {
        if (head == null) return false;

        if (head.data == value) {
            head = head.nextNode;
            if (head == null) {
                tail = null;
            }
            size--;
            return true;
        }

        Node previousNode = head;
        Node currentNode = head.nextNode;

        while (currentNode != null) {
            if (currentNode.data == value) {
                previousNode.nextNode = currentNode.nextNode;
                if (currentNode == tail) {
                    tail = previousNode;
                }
                size--;
                return true;
            }
            previousNode = currentNode;
            currentNode = currentNode.nextNode;
        }

        return false;
    }

    public void reverse() {
        Node prev = null;
        Node current = head;
        tail = head;

        while (current != null) {
            Node next = current.nextNode;
            current.nextNode = prev;
            prev = current;
            current = next;
        }

        head = prev;
    }

    public int findMiddle() {
        if (head == null) throw new IllegalStateException("List is empty");

        Node slow = head;
        Node fast = head;

        while (fast.nextNode != null && fast.nextNode.nextNode != null) {
            slow = slow.nextNode;
            fast = fast.nextNode.nextNode;
        }

        return slow.data;
    }

    public boolean hasCycle() {
        Node slow = head;
        Node fast = head;

        while (fast != null && fast.nextNode != null) {
            slow = slow.nextNode;
            fast = fast.nextNode.nextNode;
            if (slow == fast) return true;
        }

        return false;
    }

    public void printList() {
        if (head == null) {
            System.out.println("[ Empty List ]");
            return;
        }

        Node currentNode = head;
        StringBuilder sb = new StringBuilder();

        while (currentNode != null) {
            sb.append(currentNode.data);
            if (currentNode.nextNode != null) {
                sb.append(" -> ");
            }
            currentNode = currentNode.nextNode;
        }

        sb.append(" -> null");
        System.out.println(sb);
    }

    public int size() {
        return size;
    }

    public static void main(String[] args) {
        SinglyLinkedList list = new SinglyLinkedList();

        list.insertAtEnd(10);
        list.insertAtEnd(20);
        list.insertAtEnd(30);
        list.printList();

        list.insertAtFront(5);
        list.printList();

        list.deleteByValue(20);
        list.printList();

        System.out.println("Middle: " + list.findMiddle());
        System.out.println("Has cycle: " + list.hasCycle());

        list.reverse();
        System.out.print("Reversed: ");
        list.printList();
    }
}
Output
10 -> 20 -> 30 -> null
5 -> 10 -> 20 -> 30 -> null
5 -> 10 -> 30 -> null
Middle: 10
Has cycle: false
Reversed: 30 -> 10 -> 5 -> null
Watch Out: Three Non-Negotiable Edge Cases
  • Empty list: head == null. Check before any operation. Return early if empty.
  • Head deletion: head.data == value. Handle before the main loop. No previous node exists.
  • Traversal: use currentNode, not head. Never modify head during traversal.
  • Bonus: tail pointer update. If you delete the last node, update tail to previousNode.
  • These three cases appear in every linked list problem. Internalize them.
Production Insight
A logging service used a singly linked list as a bounded buffer for log entries. The service inserted at the tail (O(1) with tail pointer) and dropped from the head when the buffer was full (O(1)). With 100,000 log entries per second, the linked list sustained the throughput because both insert and delete were O(1). The team initially used an ArrayList but found that remove(0) (shifting all elements) caused 15ms latency spikes every time the buffer was full. The linked list eliminated these spikes entirely.
Key Takeaway
Build the list with head AND tail pointers. Insert at head is O(1). Insert at tail is O(1) with tail pointer. Delete requires traversal (O(n)) but the pointer rewiring is O(1). Always handle three edge cases: empty list, head deletion, traversal without mutating head.

Singly Linked List vs Array — When to Use Which

Understanding WHEN to use a singly linked list is just as important as knowing how to build one. The honest answer: arrays are often the better default choice, but linked lists win in specific scenarios.

Arrays store elements in contiguous memory, which means accessing element at index 7 is instant — O(1). The CPU can calculate the memory address directly. Linked lists can't do this. To reach node 7, you must start at the head and hop through 7 pointers one by one — O(n). If you're doing a lot of random access by index, an array will run circles around a linked list.

However, linked lists dominate when you're doing frequent insertions and deletions — especially at the front of the list. Adding to the front of an array means shifting every existing element one slot to the right — O(n). Adding to the front of a linked list is three lines of code and O(1). No shifting. No resizing.

The memory story is also interesting. Arrays can waste memory if you pre-allocate a large block and only use half of it. Linked lists only allocate exactly as much memory as they need — but each node pays a tax: it needs extra memory to store the 'next' pointer itself. For small data types like integers, that pointer overhead can actually be larger than the data you're storing.

Use a linked list when: insertion/deletion at the front is frequent, the list size is truly unpredictable, and you never need random index access.

io/thecodeforge/benchmark/LinkedListVsArray.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package io.thecodeforge.benchmark;

import java.util.LinkedList;
import java.util.ArrayList;

/**
 * Benchmark: LinkedList vs ArrayList for front insertion and random access.
 * Demonstrates why linked lists win at front insertion and lose at random access.
 */
public class LinkedListVsArray {

    public static void main(String[] args) {
        int insertionCount = 100_000;

        ArrayList<Integer> arrayList = new ArrayList<>();
        long arrayStart = System.nanoTime();
        for (int i = 0; i < insertionCount; i++) {
            arrayList.add(0, i);
        }
        long arrayDuration = (System.nanoTime() - arrayStart) / 1_000_000;

        LinkedList<Integer> linkedList = new LinkedList<>();
        long linkedStart = System.nanoTime();
        for (int i = 0; i < insertionCount; i++) {
            linkedList.addFirst(i);
        }
        long linkedDuration = (System.nanoTime() - linkedStart) / 1_000_000;

        System.out.println("Front insertion (100k elements):");
        System.out.println("  ArrayList:  " + arrayDuration + " ms");
        System.out.println("  LinkedList: " + linkedDuration + " ms");

        long arrAccessStart = System.nanoTime();
        int arrVal = arrayList.get(50_000);
        long arrAccessTime = System.nanoTime() - arrAccessStart;

        long llAccessStart = System.nanoTime();
        int llVal = linkedList.get(50_000);
        long llAccessTime = System.nanoTime() - llAccessStart;

        System.out.println("\nRandom access (index 50,000):");
        System.out.println("  ArrayList:  " + arrAccessTime + " ns (value=" + arrVal + ")");
        System.out.println("  LinkedList: " + llAccessTime + " ns (value=" + llVal + ")");
    }
}
Output
Front insertion (100k elements):
ArrayList: 1843 ms
LinkedList: 4 ms
Random access (index 50,000):
ArrayList: 8421 ns (value=49999)
LinkedList: 2109873 ns (value=49999)
Pro Tip: Cache Locality Matters More Than You Think
  • Array: contiguous memory. CPU prefetcher loads ahead. Cache hits on sequential access.
  • Linked list: scattered nodes. Each pointer chase is a potential cache miss.
  • For sequential traversal of 10,000 elements: array ~0.1ms, linked list ~0.5ms.
  • Big-O is the same O(n). Constant factors differ by 5-10x due to cache.
  • In production: profile before choosing. If traversal is the bottleneck, arrays win even with O(n) shifts.
Production Insight
A real-time trading system used linked lists for order book management (frequent insert/delete at sorted positions). The team profiled and found that sequential traversal of the order book (for price aggregation) was 8x slower with linked lists than with arrays, despite both being O(n). The cache misses from pointer chasing dominated the runtime. The solution: use a hybrid — linked list for insert/delete, but periodically snapshot to an array for traversal-heavy operations. This gave O(1) insert/delete and cache-friendly traversal.
Key Takeaway
Arrays win on random access (O(1)) and cache locality (5-10x faster sequential traversal). Linked lists win on front insertion/deletion (O(1) vs O(n)). Choose based on your access pattern. In production, profile before deciding — cache behavior can override Big-O advantages.
LinkedList vs ArrayList Decision Flow
IfFrequent insert/delete at front
UseUse LinkedList. O(1) vs O(n) for ArrayList.
IfFrequent random access by index
UseUse ArrayList. O(1) vs O(n) for LinkedList.
IfSequential traversal is the bottleneck
UseUse ArrayList. Cache locality gives 5-10x speedup over LinkedList.
IfUnpredictable size, no pre-allocation
UseLinkedList grows node-by-node. ArrayList over-allocates (1.5x growth factor).
IfNeed queue operations (add back, remove front)
UseUse LinkedList (or ArrayDeque). Both ends are O(1).
IfData fits in L1/L2 cache
UseUse ArrayList. Cache effects dominate Big-O at small sizes.
● Production incidentPOST-MORTEMseverity: high

Browser History Service Lost All Tabs: Traversed with Head Instead of CurrentNode

Symptom
Browser history service lost all tabs before the first expired tab after cleanup ran. 8% of users reported missing history entries. The service's tab count metric dropped from 50 to 12 after a single cleanup cycle.
Assumption
The team assumed a database corruption bug was deleting history records. They spent 4 hours checking the persistence layer, WAL logs, and replication state.
Root cause
The cleanup method traversed the list using head = head.nextNode instead of a separate currentNode variable. When the first expired tab was found and removed, the head pointer moved past it. All nodes before the expired tab became unreachable — they were still allocated on the heap but no reference pointed to them. The garbage collector reclaimed them on the next cycle. The head pointer now pointed to the node after the expired tab, and the list contained only the remaining tabs after that point.
Fix
1. Replaced head = head.nextNode traversal with currentNode = currentNode.nextNode using a separate variable. The head pointer never moves during traversal. 2. Added a unit test: insert 5 tabs, expire tab 3, verify all 5 tabs are still in the list (4 active, 1 expired). 3. Added a pre-cleanup snapshot: before cleanup, save the head reference. After cleanup, verify the head reference is unchanged. If changed, rollback and alert. 4. Added a metric: history_list_head_pointer_changed_during_traversal_total to catch future regressions. 5. Documented in code comments: 'NEVER modify head during traversal. Use currentNode. See incident #4821.'
Key lesson
  • Never modify head during traversal. Use a separate currentNode variable. Head is the entry point — lose it, lose the list.
  • The head pointer is the single point of failure. Every operation that traverses the list must preserve it.
  • Test with: insert N items, delete an item in the middle, verify all N items are still accessible from head.
  • Add a head-unchanged assertion after any traversal operation. If head changed, something is wrong.
  • In production: log the head pointer value before and after any list operation. If they differ unexpectedly, investigate.
Production debug guideSymptom-first investigation path for linked list bugs.6 entries
Symptom · 01
NullPointerException when traversing the list.
Fix
Check if the traversal loop dereferences nextNode without a null guard. The last node's nextNode is null. Accessing null.data throws NPE. Add currentNode != null as the loop condition and check currentNode.nextNode != null before accessing nextNode's fields.
Symptom · 02
List loses elements after a traversal or deletion operation.
Fix
Check if the head pointer is being modified during traversal. If head = head.nextNode is used to advance, the head moves and earlier nodes become unreachable. Use a separate currentNode variable for traversal.
Symptom · 03
Deleting the head node does not work (head still points to the deleted node).
Fix
Check if the deletion method handles the head case separately. The head has no previous node, so the general deletion loop (which tracks prev.nextNode) cannot rewire it. Add explicit check: if (head.data == value) { head = head.nextNode; return; } before the loop.
Symptom · 04
Infinite loop when traversing the list (program hangs).
Fix
Check for a cycle. If a node's nextNode points to an earlier node, the traversal never reaches null. Use Floyd's cycle detection: two pointers (slow by 1, fast by 2). If they meet, a cycle exists.
Symptom · 05
Memory keeps growing after repeated insertions and deletions.
Fix
Check if deleted nodes are actually unreachable. If any reference to a deleted node still exists (e.g., a dangling prev pointer), the garbage collector cannot reclaim it. Verify that prev.nextNode = currentNode.nextNode fully unlinks the deleted node.
Symptom · 06
Insert at tail is extremely slow (O(n) on large lists).
Fix
Check if the list maintains a tail pointer. Without it, every insert-at-tail requires a full traversal from head. Add a tail reference and update it on every insert/delete. This makes insert-at-tail O(1).
★ Singly Linked List TriageRapid checks to isolate linked list bugs.
NullPointerException on traversal.
Immediate action
Check null guard on currentNode and currentNode.nextNode.
Commands
Add logging: System.out.println("currentNode=" + currentNode + " next=" + (currentNode != null ? currentNode.nextNode : "NPE"))
If currentNode is null at NPE time, the loop condition is wrong. Use `while (currentNode != null)` not `while (currentNode.nextNode != null)`.
Fix now
Change loop condition to while (currentNode != null). Check currentNode.nextNode != null before accessing nextNode fields.
List loses elements after traversal.+
Immediate action
Check if head is being modified during traversal.
Commands
Log head before and after: System.out.println("head before=" + head + " after=" + head)
If head changed, the traversal is using `head = head.nextNode` instead of `currentNode = currentNode.nextNode`.
Fix now
Replace head = head.nextNode with currentNode = currentNode.nextNode. Never modify head during traversal.
Infinite loop — program hangs on traversal.+
Immediate action
Check for a cycle in the list.
Commands
Add iteration counter: int count = 0; while (current != null && count < 10000) { count++; ... } — if it hits limit, cycle confirmed.
Run Floyd's cycle detection: slow and fast pointers. If they meet, cycle exists.
Fix now
Find the cycle entry point. Fix the nextNode reference that creates the loop. Common cause: reusing a node without nullifying its nextNode.
Singly Linked List vs Array (ArrayList)
Feature / AspectSingly Linked ListArray (ArrayList)
Access by indexO(n) — must walk from headO(1) — direct memory jump
Insert at frontO(1) — just rewire head pointerO(n) — shift all elements right
Insert at endO(n) without tail; O(1) with tailO(1) amortised — append directly
Delete from frontO(1) — move head to next nodeO(n) — shift all elements left
Memory allocationDynamic — grows node by nodeContiguous block — may over-allocate
Extra memory costEach node stores a 'next' pointerNo pointer overhead per element
Cache performancePoor — nodes scattered in memoryExcellent — data sits side by side
Reverse traversalNot possible — one direction onlySimple — iterate index backwards
Best used whenFrequent front insert/delete, unknown sizeFrequent reads by index, known size
Sequential traversal speed5-10x slower than array (cache misses)5-10x faster than linked list (prefetch)
Memory overhead per element8 bytes (pointer) + object header0 bytes (contiguous, no pointer)
Garbage collection impactEach node is a separate GC objectOne backing array, fewer GC objects

Key takeaways

1
A node is just data + a pointer to the next node
the list is nothing more than a chain of these, starting at 'head' and ending at null.
2
Singly linked lists give you O(1) front insertion and deletion
no shifting, no copying — which is why they're used in queues, undo stacks, and symbol tables.
3
You cannot access a node by index in O(1)
every random access costs O(n) hops from the head, which makes linked lists the wrong tool when you need frequent index lookups.
4
Three non-negotiable edge cases
empty list (head is null), deleting the head node, and traversing without mutating the head reference — get these right and 90% of linked list bugs disappear.
5
Cache locality matters
arrays are 5-10x faster for sequential traversal despite the same Big-O. Linked list nodes are scattered on the heap — each pointer chase is a potential cache miss.
6
Maintain both head and tail pointers. Insert at head is O(1). Insert at tail is O(1) with tail pointer, O(n) without.
7
The three-pointer reversal pattern (prev, curr, next) is the foundation for reversal, palindrome check, and merge problems.
8
In production
profile before choosing. If traversal is the bottleneck, arrays win even with O(n) insertion shifts.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

FAQ · 9 QUESTIONS

Frequently Asked Questions

01
What is the difference between a singly linked list and a doubly linked list?
02
Why can't we access elements by index in a linked list like we do in an array?
03
When should I actually use a linked list instead of an array?
04
When should I use a linked list over an array?
05
How do you find the middle of a linked list?
06
What is the time complexity of common linked list operations?
07
How do you detect a cycle in a linked list efficiently?
08
Why does cache locality matter for linked lists?
09
How do you find the entry point of a cycle once you detect one?
🔥

That's Linked List. Mark it forged?

5 min read · try the examples if you haven't

Previous
Maximum Product Subarray
1 / 10 · Linked List
Next
Doubly Linked List