Implementing LinkedList data structure in JavaScript
Nov 25, 2023
5 min read
Lets start from the most obvious question in this context;
What are data structures in a programming?
In computer science, a data structure is a format to organize, manage and store data in a way that allows efficient access and modification.
Data structures are the most basic concept of any programming language that help us play with data. We store data in different data types based on the data and what we want to do with it.
For instance, if you want to store user form input, you would like to store it as an object as key value pairs, with keys being input name and value being the user input. If you want to store list of choices by user, i don't know about you, but I would store it as an array.
Different datastructures store data in a different way, work on that data in its own way, and expose data in a unique manner too. Lets have a look at Linked List data structure.
Linked list data structure:
LinkedList data structure can store list of different nodes, each node containing data and a reference to next node, previous node, or both. First node in the LinkedList is referred to as head
and last node is called tail
. List starts from the head, and moves towards tail, with each node storing data and pointing out to the next node in the list. Ofcourse, for the last node, there is not next node so it points to null. Infographically, LinkedList can be represented as;
You can see, each node contains data and has a reference to the next node.
Pros:
The main properties of a linked list data structure are mentioned below:
- Effective element insertion and deletion: By simply updating the pointers, linked lists can insert or delete elements from anywhere in the list. There is no constant-time random access to elements in linked lists. While in arrays, values need to be re-indexed.
- Sequential access: Linked lists can be used for various activities since they can be sequentially accessed in both the forward and backward directions.
Cons:
- Since lists don't have indexes, we can't access values randomly. When we want to access a value, we always have to look for it by iterating through the list starting from its head or tail.
- There is no built it linked list in JavaScript but there are plenty of examples out there. But its counter-part arrays are native to Javascript with plenty of support.
- It is easier to read data in arrays compared to linked lists as we have to itterate through all the elements to reach at destined element.
To choose between array and linked list really comes torequirements of the algorithem actually.
Types of linked lists:
There are three kinds of linked lists, singly linked lists, doubly linked lists and circular linked list.
Singly-linked list:
In singly linked lists each node has a single pointer that indicates the next node on the list.
Doubly-linked list:
In doubly linked lists each node has a double pointer, one indicates the next node on the list and the other referencing to the previous node.
Circular-linked list:
Circular linked lists are similar as singly linked lists, with one difference that tail of the list points to the head, making it a circle (an infinite loop).
In this guide we will have a look at singly linked list implementation.
Singly linked list implementation:
Singly linked list looks like this;
Lets start by creating a JavaScript ES6 Classes for ListNode and LinkedList.
For ListNode class, constructor function will intialize value and next properties of the node object. value
property will store node data, and next
property will reference to the next node.
For LinkedList class, we will define a constructor function to initialize head
, tail
, and length
of the linked list.
1class ListNode {
2 constructor(value) {
3 this.value = value;
4 this.next = null;
5 }
6}
7
8class SinglyLinkedList {
9 constructor(head = null) {
10 // if head not specified, defaults to null
11 // tail also defaults to null
12 this.head = head;
13 this.tail = this.head;
14 // we increment/decrement it on nodes add/remove
15 this.length = 0;
16 }
17}
Linked list methods:
We are gonna need to add some methods to our class like add, remove, addToHead, addAtPosition, getAtPosition, etc. to make our linked list useful.
Setters (add, addToHead, addAtPosition, set):
add:
Adds new node to the tail of the linkedlist.
1class SinglyLinkedList {
2 // ...
3 add(value){
4 let newNode = new ListNode(value)
5
6 if(!this.head){
7 // no head: no items in list
8 this.head = newNode
9 this.tail = newNode
10 }else{
11 // node(s) are present already
12 // appends node to the end
13 this.tail.next = newNode
14 this.tail = newNode
15 }
16
17 // return newly added node, and increment nodes count
18 this.length++
19 return newNode
20 }
21}
addToHead:
Adds new node to the head of the list.
1class SinglyLinkedList {
2 // ...
3 addToHead(value){
4 let newNode = new ListNode(value)
5
6 if(!this.head){
7 // no head: no items in list
8 this.head = newNode
9 this.tail = newNode
10 }else{
11 // node(s) are present already
12 // set head of list to next prop. of new node
13 // set list head equal to new node.
14 // this appends previous nodes to new node
15 // and replaces head of list with new node
16 newNode.next = this.head;
17 this.head = newNode;
18 }
19
20 // return newly added node, and increment nodes count
21 this.length++
22 return newNode
23 }
24}
addAtPosition:
As linked lists are not-indexed, therefore we use positions starting from 1 instead of indexs. This method adds node at particular position.
1class SinglyLinkedList {
2 // ...
3 addAtPosition(position, value){
4 let newNode = new ListNode(value)
5
6 // add some checks
7 if(position === 0){
8 return "UnderFlow"
9 }
10
11 if(position === 1){
12 // node needs to be added to head
13 return this.addToHead(value)
14 }
15
16 if(position === this.length + 1){
17 // this.length = current no. of nodes
18 // this.length + 1 = tail + 1
19 // means node needs to be added after tail
20 return this.add(value)
21 }
22
23 if(position > this.length + 1){
24 // invalid position
25 return "OverFlow"
26 }
27
28 let i = 1;
29 // set variable temp equal to current head
30 let temp = this.head;
31
32 while (i < position - 1) {
33 // keep iterating unless we have reached
34 // previous node to desired position
35 temp = temp.next;
36 i++;
37 }
38
39 if (temp) {
40 // set new nodes next to previous nodes next
41 // as these nodes plan to switch places
42 newNode.next = temp.next;
43 // set previous nodes next to new node
44 temp.next = newNode;
45 // we have succesfully added node at position p
46
47 this.length++;
48 return newNode;
49 }
50 }
51}
set:
Sets new value of the node.
1class SinglyLinkedList {
2 // ...
3 set(position, value) {
4 // use getAtPosition (explained later) method to find node
5 const foundNode = this.getNodeAtPosition(position);
6 if (foundNode) {
7 // if found set new value
8 foundNode.value = value;
9 // return true
10 return true;
11 }
12 // if not found return false
13 return false;
14 }
15}
Delete data(shift, pop, remove):
shift:
Shift method deletes head of the list.
1class SinglyLinkedList {
2 // ...
3 shift() {
4 if (!this.head) return undefined;
5
6 // set currentHead variable = current head
7 var currentHead = this.head;
8
9 // set new head = next node of the previous head
10 this.head = currentHead.next;
11
12 // decrement nodes count
13 this.length--;
14
15 // after this operation, if list is empty
16 // set tail to null
17 if (this.length === 0) {
18 this.tail = null;
19 }
20
21 return currentHead;
22 }
23}
pop:
Removes last node (tail) of the list and returns it.
1class SinglyLinkedList {
2 // ...
3 pop() {
4 if (!this.head) return undefined;
5 // set current = current head
6
7 if (this.length === 1) {
8 let tail = this.head;
9 this.head = 0;
10 this.tail = 0;
11
12 return tail;
13 }
14
15 let i = 1;
16 // set variable temp equal to current head
17 let nextTail = this.head;
18
19 while (i < this.length - 1) {
20 // keep iterating unless we have reached
21 // previous node to desired position
22 nextTail = nextTail.next;
23 i++;
24 }
25
26 const currentTail = nextTail.next;
27 nextTail.next = null;
28 this.tail = nextTail;
29 this.length--;
30
31 return currentTail;
32 }
33}
remove:
Remove method removes a node at specific position in a linked list.
1class SinglyLinkedList {
2 // ...
3 remove(position) {
4 // invalid position
5 if (position < 1 || position > this.length) return undefined;
6 // position of head: remove head with shift method
7 if (position === 1) return this.shift();
8 // position of tail: remove tail with pop method
9 if (position === this.length) return this.pop();
10
11 // get previous node to target node
12 // getNodeAtPosition is explained below
13 let previousNode = this.getNodeAtPosition(position - 1);
14 // bypass target node from referencing
15 let removed = previousNode.next;
16 previousNode.next = removed.next;
17
18 this.length--;
19 return removed;
20 }
21}
Getters (getHead, getTail, getAtPosition, getNodeAtPosition,print):
1class SinglyLinkedList {
2 // ...
3 getHead(){
4 return this.head.value
5 }
6
7 getTail(){
8 return this.tail.value
9 }
10
11 getAtPosition(position) {
12 // invalid position value
13 if (position < 1 || position > this.length) return undefined;
14
15 // set variable temp = current head
16 let temp = this.head;
17
18 for (let a = 1; a < position; a++) {
19 // keep iterating over nodes, until position is reached
20 temp = temp.next;
21 }
22
23 // return value of found node
24 return temp.value;
25 }
26
27 // same as above mothod, only difference being
28 // returns raw node instead of value
29 getNodeAtPosition(position) {
30 // invalid position value
31 if (position < 1 || position > this.length) return undefined;
32
33 // set variable temp = current head
34 let temp = this.head;
35
36 for (let a = 1; a < position; a++) {
37 // keep iterating over nodes, until position is reached
38 temp = temp.next;
39 }
40
41 // return value of found node
42 return temp;
43 }
44
45 print() {
46 // set variable temp = current head
47 let temp = this.head;
48 while (temp) {
49 // keep iterating over nodes, until it is tail (detect null)
50 // use any method to print: i use log.
51 console.log(temp.value);
52 temp = temp.next;
53 }
54 }
55}
Utility methods (clear, reverse):
1class SinglyLinkedList {
2 // ...
3
4 clear() {
5 // clears list by setting head to null
6 // clears out all nested nodes
7 this.head = null;
8 this.tail = null;
9 this.length = 0
10 }
11
12 reverse() {
13 if (this.length === 0) return false;
14
15 // set temp variable = current head
16 let temp = this.head;
17 // change list head with list tail
18 this.head = this.tail;
19 // change list tail with list head
20 this.tail = temp;
21
22 let next;
23 let prev = null;
24 for (let i = 0; i < this.length; i++) {
25 // iterate over all nodes
26 // keep replacing next node with the prev
27 // and prev nodes with next one
28 next = temp.next;
29 temp.next = prev;
30 prev = temp;
31 temp = next;
32 }
33 return this;
34 }
35}
Additional methods:
You can always add other linked list methods as it suits your case. Maybe you would want to add replace method that replaces particular node at a position. Give it a try,
Complete implenetation will look like this;
1class ListNode {
2 constructor(value) {
3 this.value = value;
4 this.next = null;
5 }
6}
7
8class SinglyLinkedList {
9 constructor(head = null) {
10 this.head = head;
11 this.tail = this.head;
12 this.length = 0;
13 }
14
15 add(value) {
16 let newNode = new ListNode(value);
17
18 if (!this.head) {
19 // no head: no items in list
20 this.head = newNode;
21 this.tail = newNode;
22 } else {
23 // node(s) are present already
24 // appends node to the end
25 this.tail.next = newNode;
26 this.tail = newNode;
27 }
28
29 // return newly added node, and increment nodes count
30 this.length++;
31 return newNode;
32 }
33
34 addToHead(value) {
35 let newNode = new ListNode(value);
36
37 if (!this.head) {
38 // no head: no items in list
39 this.head = newNode;
40 this.tail = newNode;
41 } else {
42 // node(s) are present already
43 // set head of list to next prop. of new node
44 // set list head equal to new node.
45 // this appends previous nodes to new node
46 // and replaces head of list with new node
47 newNode.next = this.head;
48 this.head = newNode;
49 }
50
51 // return newly added node, and increment nodes count
52 this.length++;
53 return newNode;
54 }
55
56 addAtPosition(position, value) {
57 let newNode = new ListNode(value);
58
59 // add some checks
60 if (position === 0) {
61 return "UnderFlow";
62 }
63
64 if (position === 1) {
65 // node needs to be added to head
66 return this.addToHead(value);
67 }
68
69 if (position === this.length + 1) {
70 // this.length = current no. of nodes
71 // this.length + 1 = tail + 1
72 // means node needs to be added after tail
73 return this.add(value);
74 }
75
76 if (position > this.length + 1) {
77 // invalid position
78 return "OverFlow";
79 }
80
81 let i = 1;
82 // set variable temp equal to current head
83 let temp = this.head;
84
85 while (i < position - 1) {
86 // keep iterating unless we have reached
87 // previous node to desired position
88 temp = temp.next;
89 i++;
90 }
91
92 if (temp) {
93 // set new nodes next to previous nodes next
94 // as these nodes plan to switch places
95 newNode.next = temp.next;
96 // set previous nodes next to new node
97 temp.next = newNode;
98 // we have succesfully added node at position p
99
100 this.length++;
101 return newNode;
102 }
103 }
104
105 set(position, value) {
106 // use getAtPosition (explained later) method to find node
107 const foundNode = this.getNodeAtPosition(position);
108 if (foundNode) {
109 // if found set new value
110 foundNode.value = value;
111 // return true
112 return true;
113 }
114 // if not found return false
115 return false;
116 }
117
118 shift() {
119 if (!this.head) return undefined;
120
121 // set currentHead variable = current head
122 var currentHead = this.head;
123
124 // set new head = next node of the previous head
125 this.head = currentHead.next;
126
127 // decrement nodes count
128 this.length--;
129
130 // after this operation, if list is empty
131 // set tail to null
132 if (this.length === 0) {
133 this.tail = null;
134 }
135
136 return currentHead;
137 }
138
139 pop() {
140 if (!this.head) return undefined;
141 // set current = current head
142
143 if (this.length === 1) {
144 let tail = this.head;
145 this.head = 0;
146 this.tail = 0;
147
148 return tail;
149 }
150
151 let i = 1;
152 // set variable temp equal to current head
153 let nextTail = this.head;
154
155 while (i < this.length - 1) {
156 // keep iterating unless we have reached
157 // previous node to desired position
158 nextTail = nextTail.next;
159 i++;
160 }
161
162 const currentTail = nextTail.next;
163 nextTail.next = null;
164 this.tail = nextTail;
165 this.length--;
166
167 return currentTail;
168 }
169
170 remove(position) {
171 // invalid position
172 if (position < 1 || position > this.length) return undefined;
173 // position of head: remove head with shift method
174 if (position === 1) return this.shift();
175 // position of tail: remove tail with pop method
176 if (position === this.length) return this.pop();
177
178 // get previous node to target node
179 // getNodeAtPosition is explained below
180 let previousNode = this.getNodeAtPosition(position - 1);
181 // bypass target node from referencing
182 let removed = previousNode.next;
183 previousNode.next = removed.next;
184
185 this.length--;
186 return removed;
187 }
188
189 getHead() {
190 return this.head.value;
191 }
192
193 getTail() {
194 return this.tail.value;
195 }
196
197 getAtPosition(position) {
198 // invalid position value
199 if (position < 1 || position > this.length) return undefined;
200
201 // set variable temp = current head
202 let temp = this.head;
203
204 for (let a = 1; a < position; a++) {
205 // keep iterating over nodes, until position is reached
206 temp = temp.next;
207 }
208
209 // return value of found node
210 return temp.value;
211 }
212
213 // same as above mothod, only difference being
214 // returns raw node instead of value
215 getNodeAtPosition(position) {
216 // invalid position value
217 if (position < 1 || position > this.length) return undefined;
218
219 // set variable temp = current head
220 let temp = this.head;
221
222 for (let a = 1; a < position; a++) {
223 // keep iterating over nodes, until position is reached
224 temp = temp.next;
225 }
226
227 // return value of found node
228 return temp;
229 }
230
231 print() {
232 // set variable temp = current head
233 let temp = this.head;
234 while (temp) {
235 // keep iterating over nodes, until it is tail (detect null)
236 // use any method to print: i use log.
237 console.log(temp.value);
238 temp = temp.next;
239 }
240 }
241
242 clear() {
243 // clears list by setting head to null
244 // clears out all nested nodes
245 this.head = null;
246 this.tail = null;
247 this.length = 0;
248 }
249
250 reverse() {
251 if (this.length === 0) return false;
252
253 // set temp variable = current head
254 let temp = this.head;
255 // change list head with list tail
256 this.head = this.tail;
257 // change list tail with list head
258 this.tail = temp;
259
260 let next;
261 let prev = null;
262 for (let i = 0; i < this.length; i++) {
263 // iterate over all nodes
264 // keep replacing next node with the prev
265 // and prev nodes with next one
266 next = temp.next;
267 temp.next = prev;
268 prev = temp;
269 temp = next;
270 }
271 return this;
272 }
273}
Testing implementation
Next most important step is to see if any of this works or you just spent 10 minutes for nothing 😁.
1let linkedList = new SinglyLinkedList();
2
3const l = console.log;
4
5l(linkedList.print()); //
6l(linkedList.length); // 0
7linkedList.add(10);
8linkedList.add(20);
9linkedList.add(30);
10l(linkedList.print()); // 10 20 30
11l(linkedList.length); // 3
12
13// delete head
14linkedList.shift();
15l(linkedList.print()); // 20 30
16
17// add head
18linkedList.addToHead(10);
19l(linkedList.print()); // 10 20 30
20
21// add at position
22linkedList.addAtPosition(2, 15);
23l(linkedList.print()); // 10 15 20 30
24
25// set value
26linkedList.set(2, 20);
27linkedList.set(3, 30);
28linkedList.set(4, 40);
29l(linkedList.print()); // 10 20 30 40
30
31// remove tail
32linkedList.pop();
33l(linkedList.print()); // 10 20 30
34
35// remove at position
36linkedList.remove(2);
37l(linkedList.print()); // 10 30
38
39// getters
40l(linkedList.getHead()); // 10
41l(linkedList.getTail()); // 30
42linkedList.addAtPosition(2, 20);
43l(linkedList.print()); // 10 20 30
44l(linkedList.getAtPosition(2)); // 20
45l(linkedList.getNodeAtPosition(2)); // {value: 20, next: next_node}
46
47linkedList.reverse();
48l(linkedList.print()); // 30 20 10
49linkedList.clear();
50l(linkedList.print()); //
51l(linkedList.length); // 0
Have a good day. See you in next one.