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;

singly linked list demonstration

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.

singly linked list demonstration

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.

doubly linked list demonstration

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).

circular linked list demonstration

In this guide we will have a look at singly linked list implementation.

Singly linked list implementation:

Singly linked list looks like this;

Copy code
1const linkedListStructure = {
2  head: {
3    value: 1,
4    next: {
5      value: 2,
6      next: {
7        value: 3,
8        next: null,
9      },
10    },
11  },
12  tail: {
13    value: 3,
14    next: null,
15  },
16  length: 3,
17}

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.

Copy code
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.

Copy code
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.

Copy code
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.

Copy code
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.

Copy code
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.

Copy code
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.

Copy code
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.

Copy code
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):

Copy code
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):

Copy code
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;

Copy code
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 😁.

Copy code
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.