Skip to content

Commit 046634b

Browse files
committed
# Internal Working of doubly linked list and circular linked lists
Signed-off-by: https://github.com/Someshdiwan <[email protected]>
1 parent 6604332 commit 046634b

File tree

1 file changed

+295
-0
lines changed

1 file changed

+295
-0
lines changed
Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
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

Comments
 (0)