Linked List 🔗 is a linear data structure that stores data as a list of nodes, where each node has a pointer to the next node and so on until the tail points to null. It has a head, tail, and length properties. We have arrays, but insertion/deletion or modification can be expensive while linked lists are efficient and dynamic.
|1|* --> |2| *--> |3| *--> null
Methods of a Linked List
- isEmpty – Checks whether the linked list is empty.
- peek – Returns the value of the head node without removing it.
- push – Adds a new node with the specified value to the end of the list and returns the new length.
- pop – Removes and returns the last node from the list.
- shift – Removes and returns the first node from the list.
- unshift – Adds a new node with the specified value to the beginning of the list and returns the new length.
- get – Retrieves the node at a specified index.
- set – Sets a new value for a node at a specified index and returns true if successful.
- insert – Inserts a new node with a given value at a specified index in the list and returns true if successful.
- remove – Removes the node at a specified index and returns it.
- reverse – Reverses the order of nodes in the linked list.
JavaScript Code for a Singly Linked List
class Node {
constructor(value) {
this.value = value; // Data stored in the node
this.next = null; // Pointer to the next node
}
}
class SinglyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// Checks if the list is empty
isEmpty() {
return this.length === 0;
}
// Returns the value of the head node
peek() {
return this.head?.value;
}
// Adds a new node with the given value to the end of the list
push(value) {
const newNode = new Node(value);
if (this.isEmpty()) {
// If the linkedList is empty, the new node becomes both the head and tail
this.head = newNode;
this.tail = newNode;
} else {
// Otherwise, add the new node to the end and update the 'tail' pointer
this.tail.next = newNode;
this.tail = newNode;
}
return ++this.length;
}
// Removes the last node from the list and returns it
pop() {
if (this.isEmpty()) return undefined;
const oldTailNode = this.tail;
if (this.length === 1) {
// If the list has only one node
this.head = null;
this.tail = null;
} else {
// current keeps moving from head to tail to find last but one node
let current = this.head;
while (current.next !== this.tail) {
current = current.next;
}
this.tail = current;
this.tail.next = null;
}
this.length--;
return oldTailNode;
}
// Removes the first node from the list and returns it
shift() {
if (this.isEmpty()) return undefined;
const oldHeadNode = this.head
this.head = oldHeadNode.next
this.length--
if (this.isEmpty()) {
this.tail = null
}
return oldHeadNode
}
// Adds a new node with the given value to the beginning of the list
unshift(value) {
const newHeadNode = new Node(value);
if (this.isEmpty()) {
this.tail = newHeadNode
} else {
newHeadNode.next = this.head
}
this.head = newHeadNode
return ++this.length;
}
// Retrieves the node at the specified index
get(index) {
if (index < 0 || index >= this.length) return undefined;
let counter = 0
let currentPointer = this.head
while (counter !== index) {
currentPointer = currentPointer.next
counter++
}
return currentPointer
}
// Sets the value of the node at the specified index
set(index, value) {
const findNode = this.get(index)
if (findNode) {
findNode.value = value
return true
}
return false
}
// Inserts a new node with the given value at the specified index
insert(index, value) {
if (index < 0 || index > this.length) return false;
if (index === 0) {
this.unshift(value)
return true
}
if (index === this.length) {
this.push(value)
return true
}
const newNode = new Node(value)
const beforeNode = this.get(index - 1)
const afterNode = beforeNode.next
beforeNode.next = newNode
newNode.next = afterNode
this.length++
return true
}
// Removes the node at the specified index and returns it
remove(index) {
if (index < 0 || index >= this.length) return undefined;
if (index === 0) {
return this.shift()
}
if (index === this.length - 1) {
return this.pop()
}
const beforeNode = this.get(index - 1)
const nodeToBeRemoved = beforeNode.next
beforeNode.next = nodeToBeRemoved.next
this.length--
return nodeToBeRemoved
}
// Reverses the order of nodes in the list
reverse() {
// Handle empty or single-node lists
if (this.length <= 1) return this;
/*
In-place list reversal, we start from the head and move towards tail
Order: Previous - Current - Next
Next keeps moving ahead one by one
Current sets the current node's next to Previous
then Previous becomes Current and Current becomes Next
and so on until Current is null i.e end of the list
*/
let current = this.head
let next = null
let previous = null;
while (current) {
next = current.next;
current.next = previous;
previous = current;
current = next;
}
this.head = this.tail
// In the end current and next become null and previous is the last node
this.tail = previous
return this;
}
}
Output
const myList = new SinglyLinkedList();
console.log(myList.isEmpty()); // true
myList.push(100);
myList.push(200);
myList.push(300);
console.log(myList.head);
/* {
"value": 100,
"next": {
"value": 200,
"next": {
"value": 300,
"next": null
}
}
} */
console.log(myList.length); // 3 - list length
console.log(myList.peek()); // 100 - value at head
console.log(myList.pop().value); // 300 - removed tail value
console.log(myList.shift().value); // 100 - removed head value
console.log(myList.peek()); // 200 - value at head
console.log(myList.length); // 1 - list length
console.log(myList.unshift(100)); // 2 (updated length)
console.log(myList.peek()); // 100 - value at head
console.log(myList.length); // 2 - list length
console.log(myList.get(0).value); // 100 - value at 0 index
console.log(myList.set(0, 1000)); // true - set value to 1000 at 0 index
console.log(myList.peek()); // 1000 - value at head
console.log(myList.insert(1, 150)); // true - inserted value at 1 index to 150
console.log(myList.head);
/* {
"value": 1000,
"next": {
"value": 150,
"next": {
"value": 200,
"next": null
}
}
} */
console.log(myList.remove(1).value); // 150 - Node removed
myList.push(300);
myList.push(400);
console.log(myList.head);
/* {
"value": 1000,
"next": {
"value": 200,
"next": {
"value": 300,
"next": {
"value": 400,
"next": null
}
}
}
} */
console.log(myList.reverse().head);
/* {
"value": 400,
"next": {
"value": 300,
"next": {
"value": 200,
"next": {
"value": 1000,
"next": null
}
}
}
} */
Time Complexity – O(n)
isEmpty()
– O(1)peek()
– O(1)push(value)
– O(1)pop()
– O(n)shift()
– O(1)unshift(value)
– O(1)get(index)
– O(n)set(index, value)
– O(n)insert(index, value)
– O(n)remove(index)
– O(n)reverse()
– O(n)
Real World Applications
- Web browsers use linked lists to track browsing history and navigation
- Music players and various apps use linked lists for flexible playlist/order management.
Data Structures in JavaScript Series
- Implementing Stack in JavaScript
- Implementing Queue in JavaScript
- Singly Linked List in JavaScript
- Doubly Linked List in JavaScript