Doubly Linked List
Data Structure · Core
Step through it: Fast & Slow Pointers pattern
both start at node 1
Step 1 of 6
Question: does this chain of nodes loop back on itself? Walking it one node at a time never answers that, because inside a loop you just keep going. Instead send two walkers from the start: a slow one moving 1 node per step and a fast one moving 2.
Step 2 of 6
After one step the slow walker is on node 2 and the fast one on node 3.
Step 3 of 6
Again: slow on 3, fast on 5, the last node.
Step 4 of 6
The last node points back to 3, so fast carries on 5 → 3 → 4 while slow steps to 4. They land on the same node.
Step 5 of 6
Why they must meet: inside a loop the fast walker gains one node on the slow one every step, so it catches up. Without a loop, fast would simply fall off the end (null) first. Two walkers at different speeds is called fast and slow pointers.
Step 6 of 6
Your turn.
Complexity
Time Complexity
| Access | Search | Insertion | Deletion |
|---|---|---|---|
| O(n) | O(n) | O(1) | O(n) |
Space Complexity
O(n)
In computer science, a doubly linked list is a linked data structure that consists of a set of sequentially linked records called nodes. Each node contains two fields, called links, that are references to the previous and to the next node in the sequence of nodes. The beginning and ending nodes' previous and next links, respectively, point to some kind of terminator, typically a sentinel node or null, to facilitate the traversal of the list. If there is only one sentinel node, then the list is circularly linked via the sentinel node. It can be conceptualized as two singly linked lists formed from the same data items, but in opposite sequential orders.

Made with okso.app
The two node links allow traversal of the list in either direction. While adding or removing a node in a doubly linked list requires changing more links than the same operations on a singly linked list, the operations are simpler and potentially more efficient (for nodes other than first nodes) because there is no need to keep track of the previous node during traversal or no need to traverse the list to find the previous node, so that its link can be modified.
Pseudocode for Basic Operations
Insert
Add(value)
Pre: value is the value to add to the list
Post: value has been placed at the tail of the list
n ← node(value)
if head = ø
head ← n
tail ← n
else
n.previous ← tail
tail.next ← n
tail ← n
end if
end AddDelete
Remove(head, value)
Pre: head is the head node in the list
value is the value to remove from the list
Post: value is removed from the list, true; otherwise false
if head = ø
return false
end if
if value = head.value
if head = tail
head ← ø
tail ← ø
else
head ← head.next
head.previous ← ø
end if
return true
end if
n ← head.next
while n != ø and value !== n.value
n ← n.next
end while
if n = tail
tail ← tail.previous
tail.next ← ø
return true
else if n != ø
n.previous.next ← n.next
n.next.previous ← n.previous
return true
end if
return false
end RemoveReverse Traversal
ReverseTraversal(tail)
Pre: tail is the node of the list to traverse
Post: the list has been traversed in reverse order
n ← tail
while n != ø
yield n.value
n ← n.previous
end while
end Reverse TraversalCode
import DoublyLinkedListNode from './DoublyLinkedListNode';
import Comparator from '../../utils/comparator/Comparator';
export default class DoublyLinkedList {
/**
* @param {Function} [comparatorFunction]
*/
constructor(comparatorFunction) {
/** @var DoublyLinkedListNode */
this.head = null;
/** @var DoublyLinkedListNode */
this.tail = null;
this.compare = new Comparator(comparatorFunction);
}
/**
* @param {*} value
* @return {DoublyLinkedList}
*/
prepend(value) {
// Make new node to be a head.
const newNode = new DoublyLinkedListNode(value, this.head);
// If there is head, then it won't be head anymore.
// Therefore, make its previous reference to be new node (new head).
// Then mark the new node as head.
if (this.head) {
this.head.previous = newNode;
}
this.head = newNode;
// If there is no tail yet let's make new node a tail.
if (!this.tail) {
this.tail = newNode;
}
return this;
}
/**
* @param {*} value
* @return {DoublyLinkedList}
*/
append(value) {
const newNode = new DoublyLinkedListNode(value);
// If there is no head yet let's make new node a head.
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return this;
}
// Attach new node to the end of linked list.
this.tail.next = newNode;
// Attach current tail to the new node's previous reference.
newNode.previous = this.tail;
// Set new node to be the tail of linked list.
this.tail = newNode;
return this;
}
/**
* @param {*} value
* @return {DoublyLinkedListNode}
*/
delete(value) {
if (!this.head) {
return null;
}
let deletedNode = null;
let currentNode = this.head;
while (currentNode) {
if (this.compare.equal(currentNode.value, value)) {
deletedNode = currentNode;
if (deletedNode === this.head) {
// If HEAD is going to be deleted...
// Set head to second node, which will become new head.
this.head = deletedNode.next;
// Set new head's previous to null.
if (this.head) {
this.head.previous = null;
}
// If all the nodes in list has same value that is passed as argument
// then all nodes will get deleted, therefore tail needs to be updated.
if (deletedNode === this.tail) {
this.tail = null;
}
} else if (deletedNode === this.tail) {
// If TAIL is going to be deleted...
// Set tail to second last node, which will become new tail.
this.tail = deletedNode.previous;
this.tail.next = null;
} else {
// If MIDDLE node is going to be deleted...
const previousNode = deletedNode.previous;
const nextNode = deletedNode.next;
previousNode.next = nextNode;
nextNode.previous = previousNode;
}
}
currentNode = currentNode.next;
}
return deletedNode;
}
/**
* @param {Object} findParams
* @param {*} findParams.value
* @param {function} [findParams.callback]
* @return {DoublyLinkedListNode}
*/
find({ value = undefined, callback = undefined }) {
if (!this.head) {
return null;
}
let currentNode = this.head;
while (currentNode) {
// If callback is specified then try to find node by callback.
if (callback && callback(currentNode.value)) {
return currentNode;
}
// If value is specified then try to compare by value..
if (value !== undefined && this.compare.equal(currentNode.value, value)) {
return currentNode;
}
currentNode = currentNode.next;
}
return null;
}
/**
* @return {DoublyLinkedListNode}
*/
deleteTail() {
if (!this.tail) {
// No tail to delete.
return null;
}
if (this.head === this.tail) {
// There is only one node in linked list.
const deletedTail = this.tail;
this.head = null;
this.tail = null;
return deletedTail;
}
// If there are many nodes in linked list...
const deletedTail = this.tail;
this.tail = this.tail.previous;
this.tail.next = null;
return deletedTail;
}
/**
* @return {DoublyLinkedListNode}
*/
deleteHead() {
if (!this.head) {
return null;
}
const deletedHead = this.head;
if (this.head.next) {
this.head = this.head.next;
this.head.previous = null;
} else {
this.head = null;
this.tail = null;
}
return deletedHead;
}
/**
* @return {DoublyLinkedListNode[]}
*/
toArray() {
const nodes = [];
let currentNode = this.head;
while (currentNode) {
nodes.push(currentNode);
currentNode = currentNode.next;
}
return nodes;
}
/**
* @param {*[]} values - Array of values that need to be converted to linked list.
* @return {DoublyLinkedList}
*/
fromArray(values) {
values.forEach((value) => this.append(value));
return this;
}
/**
* @param {function} [callback]
* @return {string}
*/
toString(callback) {
return this.toArray().map((node) => node.toString(callback)).toString();
}
/**
* Reverse a linked list.
* @returns {DoublyLinkedList}
*/
reverse() {
let currNode = this.head;
let prevNode = null;
let nextNode = null;
while (currNode) {
// Store next node.
nextNode = currNode.next;
prevNode = currNode.previous;
// Change next node of the current node so it would link to previous node.
currNode.next = prevNode;
currNode.previous = nextNode;
// Move prevNode and currNode nodes one step forward.
prevNode = currNode;
currNode = nextNode;
}
// Reset head and tail.
this.tail = this.head;
this.head = prevNode;
return this;
}
}export default class DoublyLinkedListNode {
constructor(value, next = null, previous = null) {
this.value = value;
this.next = next;
this.previous = previous;
}
toString(callback) {
return callback ? callback(this.value) : `${this.value}`;
}
}References
Practice
Fast & Slow Pointers
- Linked List CycleEasy
- Middle of the Linked ListEasy
- Palindrome Linked ListEasy
- Happy NumberEasy
- Linked List Cycle IIMedium
- Find the Duplicate NumberMedium
- Remove Nth Node From End of ListMedium
- Reorder ListMedium
- Sort ListMedium
- Circular Array LoopMedium
- Delete the Middle Node of a Linked ListMedium
- Rotate ListMedium
- Intersection of Two Linked ListsMedium
Spotted a mistake or something unclear? Report it on GitHub.