JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
169: Implementing a Doubly Linked List
I've seen a lot of developers move from Singly Linked Lists to Doubly Linked Lists and think, "Oh, I just add a prev property to the node, easy." On paper, that's true. In practice, it's where the most frustrating bugs creep in because you now have twice as many pointers to keep in sync. Every time you change a connection, you have to perform a "handshake" between two nodes, not just one.
Take a look at this append method. It looks correct at a glance, but it contains a bug that will haunt you the moment you try to traverse the list backward.
class Node {
constructor(value) {
this.value = value;
this.next = null;
this.prev = null;
}
}
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
}
append(value) {
const newNode = new Node(value);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return;
}
this.tail.next = newNode; // Link current tail to new node
this.tail = newNode; // Move tail to the new node
}
}
The One-Way Street Bug
If you run this and iterate from the head to the tail, everything seems fine. But if you try to write a reverseTraverse method, you'll find that this.tail.prev is null. Even worse, every single node you appended after the first one has a prev pointer that points to nowhere.
The problem is that we updated the "forward" link (this.tail.next), but we completely ignored the "backward" link. We've essentially built a Singly Linked List that just happens to have an unused prev property on every node. In a Doubly Linked List, the connection must be mutual.
Completing the Two-Way Handshake
To fix this, we need to ensure that when the current tail points forward to the new node, the new node points back to that tail. I like to think of it as a handshake: both parties must agree to the connection.
append(value) {
const newNode = new Node(value);
if (!this.head) {
this.head = newNode;
this.tail = newNode;
return;
}
// The Handshake:
this.tail.next = newNode; // Tail points forward to new node
newNode.prev = this.tail; // New node points back to old tail
this.tail = newNode; // Now move the tail pointer
}
Managing Deletions Without Breaking the Chain
Removing a node is where things get even more precarious. In a Singly Linked List, you just skip over the node. In a Doubly Linked List, you have to "stitch" the surrounding nodes together. If you're removing the middle node, the node before it needs to point to the node after it, and vice versa.
Here is how I usually implement a remove method to handle all the edge cases (head, tail, and middle) without leaving dangling pointers:
remove(value) {
let current = this.head;
while (current) {
if (current.value === value) {
// Case 1: Removing the head
if (current === this.head) {
this.head = current.next;
if (this.head) this.head.prev = null;
else this.tail = null; // List became empty
}
// Case 2: Removing the tail
else if (current === this.tail) {
this.tail = current.prev;
this.tail.next = null;
}
// Case 3: Removing a middle node
else {
current.prev.next = current.next;
current.next.prev = current.prev;
}
return true; // Successfully removed
}
current = current.next;
}
return false; // Value not found
}
Notice the order of operations. We don't just set the next pointer; we immediately update the prev pointer of the following node. If you forget that current.next.prev = current.prev line, your list will be broken for anyone trying to navigate backward through the data.
📋 Practical Task
Implementing a Music Playlist Navigation System
Build a Playlist class using a Doubly Linked List to simulate a music player's queue. Your implementation should allow a user to move forward and backward through songs without losing their place.
Requirements:
- Create a
Songnode class that stores the song title. - Implement an
addSong(title)method to append songs to the end of the playlist. - Implement a
nextSong()method that moves acurrentSongpointer forward and returns the title. If it's at the end, it should return "End of playlist". - Implement a
prevSong()method that moves thecurrentSongpointer backward and returns the title. If it's at the start, it should return "Start of playlist". - Implement a
removeCurrentSong()method that removes the song currently pointed to bycurrentSongand moves the pointer to the next available song (or the previous one if no next exists).
Test Case:
const myPlaylist = new Playlist();
myPlaylist.addSong("Bohemian Rhapsody");
myPlaylist.addSong("Stairway to Heaven");
myPlaylist.addSong("Hotel California");
console.log(myPlaylist.nextSong()); // "Bohemian Rhapsody"
console.log(myPlaylist.nextSong()); // "Stairway to Heaven"
console.log(myPlaylist.prevSong()); // "Bohemian Rhapsody"
myPlaylist.removeCurrentSong(); // Removes Bohemian Rhapsody
console.log(myPlaylist.prevSong()); // "Start of playlist"
console.log(myPlaylist.nextSong()); // "Stairway to Heaven"
There are no comments for now.