Doubly Linked List — Full Javascript Implementation

Lawson Hung
10 min readApr 10, 2020

A week or two ago, I started implementing a doubly linked list from scratch. As good as it was, it was only half implemented. I fully implemented it now. Credits go to this tutorial that I coded along to.

https://dev.to/miku86/javascript-data-structures-doubly-linked-list-intro-and-setup-275b

This is my node class.

// A Node has a value, a pointer to the previous node (= prev), a pointer to the next node (= next)export class Node{// Syntax: new Node(value);constructor(value){// this refers to the current instance of Node// Used like: node1.value, node1.prev, node1.nextthis.value = value;// Initialize this.prev and this.next as nullthis.prev = null;this.next = null;}}

As I mentioned last week, I wanted to use modules in ES6 syntax, since I am a React developer. Thus, my closest parent package.json needs to have this:

{  "type": "module"}

And to run it in the command prompt or terminal, I need to add this flag to node:

$ node --experimental-modules DoublyLinkedList.js

And get ready for it, the code!

// Source: https://dev.to/miku86/javascript-data-structures-doubly-linked-list-intro-and-setup-275bimport { Node } from './Node.js';// In terminal, run with// $ node --experimental-modules DoublyLinkedList.js// This allows you to import modules in ES6 format/syntax// Looks for the closest parent `package.json` file// Make sure the closest package.json file contains the key-value pair:/*** {*   "type": "module"* }*/// Can be anywhere in the package.json file// A Node has a value, a pointer to the previous node (= prev), a pointer to the next node (= next)// class Node{//   constructor(value){//     // this refers to the current instance of Node//     // Used like: node1.value, node1.prev, node1.next//     this.value = value;//     this.prev = null;//     this.next = null;//   }// }// A Doubly Linked List has a length, a beginning (= head), an end (= tail)class DoublyLinkedList{constructor(){this.length = 0;this.head = null;this.tail = null;}/*** Add node to end* Return newly added node* @return {Node}*/push(value){// Create a new nodeconst newNode = new Node(value);// If the list is empty, the new node should become the head and tailif(!this.length){this.head = newNode;this.tail = newNode;} else {// The current tail should point forward (= next) to the new nodethis.tail.next = newNode;// The new node should point back (= prev) to the current tailnewNode.prev = this.tail;// The new node should become the new tailthis.tail = newNode;}// Increment length by 1this.length++;// Return new nodereturn newNode;}/*** Remove node from end* Return removed node* @return {Node}*/pop(){// No node in the list so it's empty, therefore return null// this.length = false if empty, so !this.length returns trueif(!this.length){return null;} else {// Save current tail to return to laterconst nodeToRemove = this.tail;if(this.length === 1){// After removing the only node, there will be no head and tailthis.head = null;this.tail = null;} else {// Set the node before the current tail as the new tailthis.tail = this.tail.prev;// Remove the connection from the new tail to the old tail, after reassigning the new tailthis.tail.next = null;// Remove the connection from the old tail to the next tailnodeToRemove.prev = null;}// Decrement length by 1this.length--;// Return old tailreturn nodeToRemove;}}/*** Add node to beginning* Return newly added Node* @returns {Node}*/unshift(value){// Create a new nodeconst newNode = new Node(value);// If list is empty, set head and tail to new node// this.length = 0 = false when empty, so !this.length would return true when emptyif (!this.length) {this.head = newNode;this.tail = newNode;} else {// Set new node's next to current headnewNode.next = this.head;// Set the current head's prev to new nodethis.head.prev = newNode;// Set list's head to new nodethis.head = newNode;}// Increment length by 1this.length++;// return new nodereturn newNode;}/*** Remove Node from beginning* Return removed node* @return {Node}*/shift(){// If the list is empty, return null. We can't remove data from an empty list// this.length = 0 = false if empty, so !this.length returns trueif (!this.length) {return null;}// Set head as nodeToRemoveconst nodeToRemove = this.head;// After removing the only element, the list will be empty, so head and tail will be nullif (this.length === 1) {this.head = null;this.tail = null;} else {// The node after nodeToRemove becomes the new headthis.head = nodeToRemove.next;// Remove both connections from the new head and the old head (nodeToRemove)this.head.prev = null;nodeToRemove.next = null;}// Decrement length by 1this.length--;// Return nodeToRemovereturn nodeToRemove;}/*** Get node by index* @return {Node}*/get(index) {// If list is empty, or index is less than 0, or index is greater than or equal to list length, return nullif (!this.length || index < 0 || index >= this.length) {return null;} else {let currentNode;// If the desired node is in the bottom of the listif (index < this.length / 2) {// Add counter, starting from 0 and count upwards in the looplet counter = 0;// Start from head nodecurrentNode = this.head;// Go to the next node until we find the desired nodewhile (counter < index) {currentNode = currentNode.next;counter++;}} else {// Add counter, starting from the top and counting downwards in the looplet counter = this.length - 1;// Start from the tail nodecurrentNode = this.tail;// Go the the previous node until we find the desired nodewhile (counter > index) {currentNode = currentNode.prev;counter--;}}// Return nodereturn currentNode;}}set(index, value) {// Find the node to changeconst nodeToChange = this.get(index);// If the node existsif (nodeToChange) {// Update the value of the nodenodeToChange.value = value;// Return the updated nodereturn nodeToChange;} else {// If we can't find the node: return nullreturn null;}}/*** Implementing splice, inserting a node at a given index* @return {Node}*/insert(index, value){// If index is less than 0 or if index is greater than the list's length, return nullif (index < 0 || index > this.length){return null;// If index equals 0, use this.unshift(value)} else if (index === 0){return this.unshift(value);// If index equals length, use this.push(value)} else if (index === this.length){return this.push(value);} else {// Create a new nodeconst nodeToInsert = new Node(value);// Find the node that is currently before the desired place and connect it to the new nodeconst prev = this.get(index - 1);nodeToInsert.prev = prev;prev.next = nodeToInsert;// Find the node that is currently at the desired place and connect it to he new nodeconst next = this.get(index);next.prev = nodeToInsert;nodeToInsert.next = next;// Increment the list's length by 1this.length++;// Return the new nodereturn nodeToInsert;}}/*** Removes a node at index* @return {Node}*/remove(index){// If list is empty, index is less than 0, greater than or equal to the list's length, return nullif (!this.length || index < 0 || index >= this.length){return null;// If index equals 0, we want to remove the first node with shift()} else if (index === 0){return this.shift();// If we want to remove the last node, use pop()} else if (index === this.length - 1){return this.pop();} else {// Get the node we want to remove, the node before it and the node after itconst nodeToRemove = this.get(index);const prevNode = this.get(index - 1);const nextNode = this.get(index + 1);// Remove the connections from the node to other nodesnodeToRemove.next = null;nodeToRemove.prev = null;// Update the connections from the node before the node to removeprevNode.next = nextNode;// Update the connections from the node after the node to removenextNode.prev = prevNode;// Decrement length by 1this.length--;// Return nodereturn nodeToRemove;}}}/////////////////////// Testing initializing new Nodeconsole.log("Testing creating new Node");const newNode = new Node(1);console.log("newNode: ", newNode);// Node { value: 1, prev: null, next: null }console.log("newNode.value: ", newNode.value);// 1console.log("newNode.prev: ", newNode.prev);// nullconsole.log("newNode.next: ", newNode.next);// null////////////////// Testing DoublyLinkedList.push()console.log("-------------------------------------------");console.log("Testing push");// Empty listconst pushDLL = new DoublyLinkedList();console.log("pushDLL: ", pushDLL);// pushDLL:  DoublyLinkedList { length: 0, head: null, tail: null }// Push first new nodeconst newNode1 = pushDLL.push("new node 1");console.log("Pushing new node and returning newly pushed node: ", newNode1);// Node { value: 'new node 1', prev: null, next: null }console.log("pushDLL after pushing newNode1: ", pushDLL);// DoublyLinkedList {//   length: 1,//   head: Node { value: 'new node 1', prev: null, next: null },//   tail: Node { value: 'new node 1', prev: null, next: null }// }// Push second new nodeconst newNode2 = pushDLL.push("new node 2");console.log("Pushing new node and returning newly pushed node: ", newNode2);// Node {//   value: 'new node 2',//   prev: Node { value: 'new node 1', prev: null, next: [Circular] },//   next: null// }console.log("pushDLL after pushing newNode2: ", pushDLL);// DoublyLinkedList {//   length: 2,//   head: Node {//     value: 'new node 1',//     prev: null,//     next: Node { value: 'new node 2', prev: [Circular], next: null }//   },//   tail: Node {//     value: 'new node 2',//     prev: Node { value: 'new node 1', prev: null, next: [Circular] },//     next: null//   }// }///////////////////// Testing DoublyLinkedList.pop()console.log("---------------------------------------");console.log("Testing pop()");const popDLL = new DoublyLinkedList();popDLL.push("A");popDLL.push("B");popDLL.push("C");console.log("popDLL after pushing: ", popDLL);// DoublyLinkedList {//   length: 3,//   head: Node {//     value: 'A',//     prev: null,//     next: Node { value: 'B', prev: [Circular], next: [Node] }//   },//   tail: Node {//     value: 'C',//     prev: Node { value: 'B', prev: [Node], next: [Circular] },//     next: null//   }// }console.log("This node is being popped out: ", popDLL.pop());// Node { value: 'C', prev: null, next: null }console.log("popDLL after popping: ", popDLL);// DoublyLinkedList {//   length: 2,//   head: Node {//     value: 'A',//     prev: null,//     next: Node { value: 'B', prev: [Circular], next: null }//   },//   tail: Node {//     value: 'B',//     prev: Node { value: 'A', prev: null, next: [Circular] },//     next: null//   }// }//////////////////////// Testing DoublyLinkedList.unshift()console.log("------------------------------------------------");console.log("Testing unshift()");const unshiftDLL = new DoublyLinkedList();unshiftDLL.push("A");console.log("unshiftDLL after pushing: ", unshiftDLL);// DoublyLinkedList {//   length: 1,//   head: Node { value: 'A', prev: null, next: null },//   tail: Node { value: 'A', prev: null, next: null }// }console.log("Unshifting unshiftDLL: ", unshiftDLL.unshift("0"));// Node {//   value: '0',//   prev: null,//   next: Node { value: 'A', prev: [Circular], next: null }// }///////////////////////////// Testing DoublyLinkedList.shift()console.log("------------------------------------------------");console.log("Testing shift()");const shiftDLL = new DoublyLinkedList();shiftDLL.push("A");shiftDLL.push("B");console.log("shiftDLL after pushing: ", shiftDLL);// DoublyLinkedList {//   length: 2,//   head: Node {//     value: 'A',//     prev: null,//     next: Node { value: 'B', prev: [Circular], next: null }//   },//   tail: Node {//     value: 'B',//     prev: Node { value: 'A', prev: null, next: [Circular] },//     next: null//   }// }console.log("Removed node from shifting shiftDLL: ", shiftDLL.shift());// Node { value: 'A', prev: null, next: null }console.log("shiftDLL after shifting: ", shiftDLL);// DoublyLinkedList {//   length: 1,//   head: Node { value: 'B', prev: null, next: null },//   tail: Node { value: 'B', prev: null, next: null }// }/////////////////////////// Testing DoublyLinkedList.get()console.log("-----------------------------------------------");console.log("Testing get(index)");const getDLL = new DoublyLinkedList();getDLL.push("A");getDLL.push("B");getDLL.push("C");console.log("getDLL.get(-1): ", getDLL.get(-1));// nullconsole.log("getDLL.get(0): ", getDLL.get(0));// Node {//   value: 'A',//   prev: null,//   next: Node {//     value: 'B',//     prev: [Circular],//     next: Node { value: 'C', prev: [Circular], next: null }//   }// }console.log("getDLL.get(1): ", getDLL.get(1));// Node {//   value: 'B',//   prev: Node { value: 'A', prev: null, next: [Circular] },//   next: Node { value: 'C', prev: [Circular], next: null }// }console.log("getDLL.get(2): ", getDLL.get(2));// Node {//   value: 'C',//   prev: Node {//     value: 'B',//     prev: Node { value: 'A', prev: null, next: [Circular] },//     next: [Circular]//   },//   next: null// }console.log("getDLL.get(3): ", getDLL.get(3));// null//////////////////////// Testing DoublyLinkedList.set(index, value)console.log("--------------------------------------");console.log("Testing set(index, value)");const setDLL = new DoublyLinkedList();setDLL.push("A");console.log("setDLL after pushing: ", setDLL);console.log("setDLL.set(-1, 'too low'): ", setDLL.set(-1, "too low"));// nullconsole.log("setDLL.set(0, 'Updated A')", setDLL.set(0, "Updated A"));// Node { value: 'Updated A', prev: null, next: null }console.log("setDLL.set(1, 'too high'): ", setDLL.set(1, 'too high'));// null///////////////////////// Testing DoublyLinkedList.insert(index, value)console.log("---------------------------------------------");console.log("Testing insert(index, value)");const insertDLL = new DoublyLinkedList();console.log("insertDLL.insert(-1, 'too low'): ", insertDLL.insert(-1, "too low"));// nullconsole.log("insertDLL.insert(0, 'at 0'): ", insertDLL.insert(0, 'at 0'));// Node { value: 'at 0', prev: null, next: null }console.log("insertDLL.insert(1, 'at 1'): ", insertDLL.insert(1, 'at 1'));// Node {//   value: 'at 1',//   prev: Node { value: 'at 0', prev: null, next: [Circular] },//   next: null// }console.log("insertDLL.insert(1, 'new at 1'): ", insertDLL.insert(1, 'new at 1'));// insertDLL.insert(1, 'new at 1'):  Node {//   value: 'new at 1',//   prev: Node { value: 'at 0', prev: null, next: [Circular] },//   next: Node { value: 'at 1', prev: [Circular], next: null }// }console.log("insertDLL after testing insertDLL.insert(index, value)", insertDLL);//////////////////////////////// Testing DoublyLinkedList.remove(index)console.log("-----------------------------------");console.log("Testing DoublyLinkedList.remove(index)");const removeDLL = new DoublyLinkedList();removeDLL.push("A");removeDLL.push("B");removeDLL.push("C");console.log("removeDLL.remove(-1): ", removeDLL.remove(-1));// nullconsole.log("removeDLL.remove(5): ", removeDLL.remove(5));// nullconsole.log("removeDLL.remove(0): ", removeDLL.remove(0));// Node { value: 'A', prev: null, next: null }console.log("removeDLL.remove(1): ", removeDLL.remove(1));// Node { value: 'C', prev: null, next: null }console.log("removeDLL after removing: ", removeDLL);// DoublyLinkedList {//   length: 1,//   head: Node { value: 'B', prev: null, next: null },//   tail: Node { value: 'B', prev: null, next: null }// }

I know it may be a lot, but here is the Github link where I uploaded it as well.

Implementing a full doubly linked list from scratch allowed me to understand how arrays work on a deeper level. Before, I always forgot the arguments for the splice function, and was even afraid to use it. But now that I know how to insert elements into the doubly linked list, I am more comfortable with the splice function. That probably was the hardest to wrap my head around for me.

Good luck, and stay safe during these times!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Lawson Hung
Lawson Hung

No responses yet

Write a response