|
| 1 | +# Internal Working of doubly linked list and circular linked lists |
| 2 | + |
| 3 | +This document explains how `java.util.LinkedList` works internally. |
| 4 | + |
| 5 | +It is based on **doubly linked list** principles but also compares with **circular linked lists** for conceptual |
| 6 | +understanding. |
| 7 | + |
| 8 | +It includes node structure, internal mechanics of add/remove operations, traversal behavior, complexity analysis, |
| 9 | +memory layout, iteration, concurrency aspects, and use cases. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## Overview |
| 14 | + |
| 15 | +- `LinkedList` in Java implements both `List` and `Deque` interfaces. |
| 16 | +- Internally, it is a **doubly linked list** — each node holds references to its predecessor and successor. |
| 17 | +- Provides efficient insertions and deletions at both ends. |
| 18 | +- Unlike `ArrayList`, elements are stored as individual node objects, not in a contiguous array. |
| 19 | +- Maintains: |
| 20 | + - `first` → head pointer |
| 21 | + - `last` → tail pointer |
| 22 | + - `size` → count of elements |
| 23 | +- **Strengths:** cheap insertions/removals at ends, efficient iterator-based modifications. |
| 24 | +- **Weaknesses:** slower random access compared to arrays. |
| 25 | + |
| 26 | +--- |
| 27 | + |
| 28 | +## Node Structure |
| 29 | + |
| 30 | +Conceptual inner class: |
| 31 | + |
| 32 | +```java |
| 33 | +private static class Node<E> { |
| 34 | + E item; |
| 35 | + Node<E> next; |
| 36 | + Node<E> prev; |
| 37 | + Node(Node<E> prev, E element, Node<E> next) { |
| 38 | + this.item = element; |
| 39 | + this.next = next; |
| 40 | + this.prev = prev; |
| 41 | + } |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +Each node contains: |
| 46 | +- The **value** stored (`item`). |
| 47 | +- Pointer to the **previous node** (`prev`). |
| 48 | +- Pointer to the **next node** (`next`). |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +## ASCII Diagram — Doubly Linked List |
| 53 | + |
| 54 | +Example with 3 nodes (A, B, C): |
| 55 | + |
| 56 | +``` |
| 57 | +head → [A | prev=null | next= ] ⇄ [B | prev | next ] ⇄ [C | prev | next=null] ← tail |
| 58 | +``` |
| 59 | + |
| 60 | +- Bidirectional traversal is possible: forward from `head` to `tail`, backward from `tail` to `head`. |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | +## Core Operations — Internal Mechanics |
| 65 | + |
| 66 | +### `add(E e)` (append at tail) |
| 67 | + |
| 68 | +1. Create new node: `newNode = new Node(last, e, null)`. |
| 69 | +2. If empty → update `first = newNode`. |
| 70 | +3. Else link `last.next = newNode`. |
| 71 | +4. Update `last = newNode`, increment size. |
| 72 | + |
| 73 | +Cost: **O(1)** |
| 74 | + |
| 75 | +ASCII: |
| 76 | + |
| 77 | +``` |
| 78 | +Before: [A] <-> [B] |
| 79 | +After add(C): [A] <-> [B] <-> [C] |
| 80 | +``` |
| 81 | + |
| 82 | +--- |
| 83 | + |
| 84 | +### `addFirst(E e)` (insert at head) |
| 85 | + |
| 86 | +1. Create node: `newNode = new Node(null, e, first)`. |
| 87 | +2. If empty → update `last = newNode`. |
| 88 | +3. Else link `first.prev = newNode`. |
| 89 | +4. Update `first = newNode`, increment size. |
| 90 | + |
| 91 | +Cost: **O(1)** |
| 92 | + |
| 93 | +ASCII: |
| 94 | + |
| 95 | +``` |
| 96 | +Before: [B] <-> [C] |
| 97 | +After addFirst(A): [A] <-> [B] <-> [C] |
| 98 | +``` |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +### `add(int index, E e)` (insert at position) |
| 103 | + |
| 104 | +1. Validate index. |
| 105 | +2. Locate successor node using `node(index)` (chooses shortest traversal path). |
| 106 | +3. Insert `newNode` between `pred` and `succ`. |
| 107 | +4. Rewire neighbors’ `next`/`prev`. |
| 108 | + |
| 109 | +Cost: **O(n)** due to traversal. |
| 110 | + |
| 111 | +ASCII: |
| 112 | + |
| 113 | +``` |
| 114 | +Before: [A] <-> [C] |
| 115 | +Insert X at index 1 → [A] <-> [X] <-> [C] |
| 116 | +``` |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +### `get(int index)` |
| 121 | + |
| 122 | +- Traverses from head or tail depending on index position. |
| 123 | +- Returns node’s `item`. |
| 124 | +- Cost: **O(n)** |
| 125 | + |
| 126 | +ASCII (get(2)): |
| 127 | + |
| 128 | +``` |
| 129 | +[A] <-> [B] <-> [C] <-> [D] |
| 130 | +Traversal: A → B → C |
| 131 | +``` |
| 132 | + |
| 133 | +--- |
| 134 | + |
| 135 | +### `remove(int index)` |
| 136 | + |
| 137 | +1. Locate target node. |
| 138 | +2. Rewire neighbors: `pred.next = next`, `next.prev = pred`. |
| 139 | +3. Null out target’s fields for GC. |
| 140 | +4. Update size. |
| 141 | + |
| 142 | +Cost: **O(n)**, except head/tail removal: **O(1)**. |
| 143 | + |
| 144 | +ASCII: |
| 145 | + |
| 146 | +``` |
| 147 | +Before: [A] <-> [X] <-> [B] <-> [C] |
| 148 | +remove(2): unlink B |
| 149 | +After: [A] <-> [X] <-> [C] |
| 150 | +``` |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## Circular Linked List — Comparison |
| 155 | + |
| 156 | +- In a **circular doubly linked list**, tail’s `next` → head, and head’s `prev` → tail. |
| 157 | +- Benefits: endless iteration, round-robin scheduling, buffer management. |
| 158 | +- Java’s `LinkedList` is not circular, but iteration can simulate circular traversal with modular indexing. |
| 159 | + |
| 160 | +ASCII: |
| 161 | + |
| 162 | +``` |
| 163 | +[A] ⇄ [B] ⇄ [C] |
| 164 | + ^ | |
| 165 | + |_________________| |
| 166 | +``` |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## ⚡ Complexity Summary |
| 171 | + |
| 172 | +Below is a side-by-side comparison of **asymptotic complexities** for a standard **doubly linked list** |
| 173 | +(the model used by Java's `LinkedList`) and |
| 174 | +a **circular doubly linked list** (a conceptual variant where `tail.next` → `head` and `head.prev` → `tail`). |
| 175 | + |
| 176 | +> **Important:** changing a list from linear doubly-linked to circular **does not** change the big‑O time complexity of |
| 177 | +> core operations — it changes traversal semantics and enables certain patterns |
| 178 | +> (like easy wrap-around or constant-time rotation) which can simplify algorithms. |
| 179 | +
|
| 180 | +| Operation | Doubly Linked List (Java `LinkedList`) | Circular Doubly Linked List (conceptual) | |
| 181 | +| -------------------------------- | -------------------------------------- | ---------------------------------------- | |
| 182 | +| `get(index)` | O(n) — traverse from nearest end; average ~n/2 steps. | O(n) — same asymptotic cost; traversal can wrap-around but still linear to target. | |
| 183 | +| `set(index, e)` | O(n) — locate node then replace item. | O(n) — same. | |
| 184 | +| `add(e)` / `addLast(e)` | O(1) — append at tail using `last` pointer. | O(1) — append at tail; must update circular links (`tail.next = head`). | |
| 185 | +| `addFirst(e)` | O(1) — prepend at head using `first` pointer. | O(1) — prepend at head; maintain circular links. | |
| 186 | +| `add(index, e)` | O(n) — traversal + insertion. | O(n) — traversal + insertion; insertion logic similar but must maintain circular links. | |
| 187 | +| `removeFirst()` / `removeLast()` | O(1) — direct removal at ends. | O(1) — direct removal; keep circular pointers consistent. | |
| 188 | +| `remove(index)` | O(n) — traversal needed then unlink. | O(n) — traversal needed then unlink and update circular links. | |
| 189 | +| `contains(e)` / `indexOf(e)` | O(n) — linear search from head. | O(n) — linear search; ensure termination by limiting steps to `size` (avoid infinite loop). | |
| 190 | + |
| 191 | +### Notes & Practical Differences |
| 192 | +- **Wrap-around iteration:** Circular lists allow easy wrap-around traversal without extra bounds checks (useful for round-robin scheduling, buffers, or games). |
| 193 | +For example, rotating the list by one position can be done in **O(1)** by moving a single pointer reference to a different node in a circular list — |
| 194 | +an operation that is also O(1) conceptually on a doubly-linked list if you maintain an external cursor, but circular lists make rotation semantics explicit. |
| 195 | +- **Termination safety:** When iterating a circular list, you must explicitly stop after `size` steps or detect revisiting the start node; otherwise traversal can become infinite. |
| 196 | +- **Memory & GC:** Both variants have the same per-node memory footprint (object header + 3 references + item). Circular lists add no asymptotic memory overhead; they simply set `tail.next = head` and `head.prev = tail`. |
| 197 | +- **When to prefer circular:** choose circular when your algorithm benefits from natural wrap-around or cheap rotations (e.g., round-robin task schedulers, ring buffers). |
| 198 | +For most general-purpose use, the standard doubly-linked layout (as in Java) is sufficient and simpler. |
| 199 | + |
| 200 | +✅ **Summary:** The table shows that **asymptotic complexities remain the same** between doubly linked and |
| 201 | +circular doubly linked lists. |
| 202 | + |
| 203 | +The choice between them is driven by traversal semantics and algorithmic convenience rather than raw performance. |
| 204 | + |
| 205 | +## Memory & GC Considerations |
| 206 | + |
| 207 | +- Each node consumes extra memory (object header + 3 references + item). |
| 208 | +- Larger overhead vs `ArrayList`’s contiguous storage. |
| 209 | +- After removal, references are nulled to ensure garbage collection. |
| 210 | + |
| 211 | +--- |
| 212 | + |
| 213 | +## Iterators |
| 214 | + |
| 215 | +- Provides fail-fast iterators. |
| 216 | +- `ListIterator` allows **bidirectional traversal** and modifications. |
| 217 | +- Structural modification outside iterator → `ConcurrentModificationException`. |
| 218 | + |
| 219 | +ASCII: |
| 220 | + |
| 221 | +``` |
| 222 | +Start -> [A] -> [B] -> [C] -> End |
| 223 | +``` |
| 224 | + |
| 225 | +--- |
| 226 | + |
| 227 | +## Concurrency |
| 228 | + |
| 229 | +- Not synchronized by default. |
| 230 | +- For thread safety: `Collections.synchronizedList(new LinkedList<>())`. |
| 231 | +- For lock-free alternatives: use `ConcurrentLinkedDeque`, `ConcurrentLinkedQueue`. |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +## Practical Guidance |
| 236 | + |
| 237 | +**Use when:** |
| 238 | + |
| 239 | +- Frequent insertions/removals at head or tail. |
| 240 | +- Heavy use of `ListIterator`. |
| 241 | + |
| 242 | +**Avoid when:** |
| 243 | + |
| 244 | +- Workload is random-access heavy (use `ArrayList`). |
| 245 | +- Memory overhead is critical. |
| 246 | + |
| 247 | +--- |
| 248 | + |
| 249 | +## Performance & Debugging Tips |
| 250 | + |
| 251 | +- Avoid `get(i)` in loops → can lead to O(n^2). |
| 252 | +- Use iterators or enhanced for-loops. |
| 253 | +- Profile allocations when handling large lists. |
| 254 | +- For concurrent scenarios, use specialized concurrent collections. |
| 255 | + |
| 256 | +--- |
| 257 | + |
| 258 | +## Extended Visual Examples |
| 259 | + |
| 260 | +1. **Start empty → addFirst(A) → addLast(B) → addLast(C):** |
| 261 | + |
| 262 | +``` |
| 263 | +[A] |
| 264 | +[A] <-> [B] <-> [C] |
| 265 | +``` |
| 266 | + |
| 267 | +2. **add(1, X) on [A, B, C]:** |
| 268 | + |
| 269 | +``` |
| 270 | +[A] <-> [X] <-> [B] <-> [C] |
| 271 | +``` |
| 272 | + |
| 273 | +3. **remove(2) on [A, X, B, C] → remove B:** |
| 274 | + |
| 275 | +``` |
| 276 | +[A] <-> [X] <-> [C] |
| 277 | +``` |
| 278 | + |
| 279 | +4. **Circular conceptual example:** |
| 280 | + |
| 281 | +``` |
| 282 | +[A] ⇄ [B] ⇄ [C] |
| 283 | + ^ | |
| 284 | + |_________________| |
| 285 | +``` |
| 286 | + |
| 287 | +--- |
| 288 | + |
| 289 | +## FAQ |
| 290 | + |
| 291 | +- **Q:** Does it implement `Deque`? → **Yes.** |
| 292 | +- **Q:** Is it thread-safe? → **No.** External synchronization required. |
| 293 | +- **Q:** Better than `ArrayList`? → **Depends on workload.** |
| 294 | + |
| 295 | +--- |
0 commit comments