Javascript Algorithms Sample Chapter Your First Algorithms
Javascript Algorithms Sample Chapter Your First Algorithms
© 2019 Fullstack.io
All rights reserved. No portion of the book manuscript may be reproduced, stored in a retrieval
system, or transmitted in any form or by any means beyond the number of purchased copies,
except for a single backup or archival copy. The code may be used freely in your projects,
commercial or otherwise.
The authors and publisher have taken care in preparation of this book, but make no expressed
or implied warranty of any kind and assume no responsibility for errors or omissions. No
liability is assumed for incidental or consequential damagers in connection with or arising out
of the use of the information or programs container herein.
Published by Fullstack.io.
FULLSTACK .io
Contents
Book Revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
EARLY RELEASE VERSION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Join Our Discord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Bug Reports . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Be notified of updates via Twitter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
We’d love to hear from you! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
PRE-RELEASE VERSION . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Join Our Discord . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
How To Read This Book . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Linked list and its common operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Complexities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Queue and its common operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
When to use a Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Usage Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Complexities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
CONTENTS
Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Stack and its common operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Usage Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
Complexities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Hash Table . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Hash Function . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
Collision Resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
Operations Time Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Binary Heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Basic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Usage Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Complexities . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
Graphs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Application . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Graph Representation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Basic Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Usage Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Operations Time Complexity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Factorial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Intro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Applications . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148
Recursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Problems Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
Quiz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
References . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 273
Changelog . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
Revision 2 (11-25-2019) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
Revision 1 (10-29-2019) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
CONTENTS 1
Book Revision
Revision 1 - EARLY RELEASE - 2019-11-25
Bug Reports
If you’d like to report any bugs, typos, or suggestions just email us at: us@fullstack.io.
¹https://newline.co/discord/algorithms
²https://twitter.com/fullstackio
³mailto:us@fullstack.io
CONTENTS 1
PRE-RELEASE VERSION
This is a pre-release version of the book.
Most, but not all, of the chapters have been edited.
Introduction
As a JavaScript developer you might think you don’t need to learn about data structures or
algorithms.
Did you know JavaScript itself (runtime + browser) uses things like a stack, a heap and a queue? The
more you understand the JavaScript you use on a daily basis – really understand it, the better you
can wield it’s power and make less mistakes. If you thought algorithms and data structures were
just a computer science course that wasn’t necessary on a day to day basis as JavaScript developer,
think again!
This is an algorithm. We’ve defined and used the set of steps that solve our task of getting home.
Algorithm Complexity
Let’s take the example of “getting home” issue from the previous section. Is there another way of
solving it? Yes, we implement different steps (read “algorithm”) to achieve that goal:
Now, let’s think about which of these two algorithms we should choose:
First, we need to clarify our restrictions (how much money do we have, how urgently we need to
get home, how healthy we are) and evaluate algorithms from the perspective of these restrictions.
As you can see each of these two algorithms requires a different amount of time and resources. The
first one will require more time and energy but less money. The second one will require less time
and energy but more money. And if we don’t have money and are pretty healthy we need to choose
the first algorithm and enjoy the walk. Otherwise, if we have enough money and need to get home
urgently the second option is better.
What we’ve just done here is we’ve evaluated the complexity of each algorithm.
Algorithms and Their Complexities 3
Big O Notation
Let’s imagine we have two men: one is a young and fast Olympic champion and the other one is
your typical middle-aged male. We’ve just agreed that we want the complexity of “stairs climbing”
algorithm to be the same for both men so it should not depend on a man’s speed but rather depends
on how high the two men climb. In terms of a computer, our algorithm analysis will not depend on
the hardware, but on the software problem itself. How many steps would these two men need to
take to get to the 20ʰ floor? Well, let’s assume that we have 20 steps between each floor. That means
the two men need to take 20 * 20 = 400 steps to get to the 20ʰ floor. Let’s generalize it for n's floor:
Do you see the pattern here? The time complexity of climbing the stairs has a linear dependency on
the number of floors. In Big O syntax, this is written as follows:
O(20 * n)
We must always get rid of non-dominant parts when describing complexity using Big O
notation: O(n + 1) becomes O(n), O(n + n^2) becomes O(n^2), O(n + log(n)) becomes
O(n) and so on.
Why is that? Because Big O must describe the order of the function and not an exact number of
milliseconds or megabytes it consumes. It describes the trend and not absolute values. Therefore
non-dominant constants do not matter. Non-dominant constants are the ones we may discard for
huge values of n. For example in a situation when n = 10000 which of these two parts will influence
the final number more: 20 or n = 10000? It is obvious that the influence of n to the final number is
500 times bigger than the constant 20. Therefore, we may discard it and the time complexity of the
“stairs climbing” algorithm is as follows:
O(n)
This was an example of linear dependency, but there are other types of dependency exists as well:
O(log(n)), O(n * log(n)), O(n^2), O(2^n), O(n!) etc.
Take a look at the graphs of functions commonly used in the analysis of algorithms, showing the
number of operations N versus input size n for each function.
Algorithms and Their Complexities 5
All these graphs show us how fast our function’s memory and time consumption will grow
depending on the input. Two different array sorting algorithms may accomplish the same task of
sorting but one will do it in O(n * log(n)) and another one in O(n^2) time. If we have a choice, we
would prefer the first one over the second one.
With Big O notation we may compare algorithms (functions, programs) based on their Big O value
independently of the hardware they are running on.
Take a look at the list of some of the most common Big O notations and their performance
comparisons against different sizes of the input data. This will give you the feeling of how different
algorithms complexities (time and memory consumptions) may be.
Algorithms and Their Complexities 6
Now let’s roughly analyze how much memory it will require to evaluate this function. In this case,
the number of variables we operate with doesn’t depend on the input. So the space complexity is:
Algorithms and Their Complexities 7
O(n) example
And now let’s implement the same power function but in an iterative way:
01-algorithms-and-their-complexities/iterativePower.js
1 /**
2 * Raise number to the power.
3 *
4 * Example:
5 * number = 3
6 * power = 2
7 * output = 3^2 = 9
8 *
9 * @param {number} number
10 * @param {number} power
11 * @return {number}
12 */
13 export function iterativePower(number, power) {
14 let result = 1;
15
16 for (let i = 0; i < power; i += 1) {
17 result *= number;
18 }
19
20 return result;
21 }
The function still does the same. But let’s see how many operations it will perform depending on
the input.
All operations outside and inside the for() loop require constant time and space. There are just
assignment, multiplication and return operations. But for() loop itself acts as a multiplier for time
complexity of the code inside of it because it will make the body of the loop to be executed power
times. So the time complexity of this function may be expressed as a sum of time complexities of
its code parts like O(1) + power * O(1) + O(1). Where the first and last O(1) in the equation are
for assignment and return operations. After we drop non-dominant parts like O(1) and move power
into O() brackets we will get our time complexity as:
Notice that we’ve written O(power) and not O(number). This is important since we’re showing that
the execution time of our function directly depends on the power input. A big number input won’t
slow the function down but big power input will.
Algorithms and Their Complexities 8
From the perspective of space complexity, we may conclude that our function won’t create additional
variables or stack layers with bigger input. Amount of memory it consumes does not change.
5! = 1 * 2 * 3 * 4 * 5 = 120
1 /**
2 * Calculate factorial.
3 *
4 * Example:
5 * number = 5
6 * output = 120
7 *
8 * @param {number} number
9 * @return {number}
10 */
11 export function factorial(number) {
12 if (number === 0) {
13 return 1;
14 }
15
16 return factorial(number - 1) * number;
17 }
If we were not dealing with recursion we would say that all operations inside the function have O(1)
time complexity and thus overall time complexity is also O(1) (the sum of all O(1)s with discarded
non-dominant parts). But we do have recursion call here at the very last line of the function. Let’s
analyze it and see the tree of recursion calls for 4!:
Algorithms and Their Complexities 9
factorial(4)
factorial(3) * 4
factorial(2) * 3
factorial(1) * 2
You see that from time perspective our recursion acts like a for() loop from the previous example
multiplying time complexity of function body by number. Thus time complexity is calculated as
follows:
You may also notice that recursion makes the stack of function calls grow proportionally to the
number. This happens because to calculate factorial(4) computer needs to calculate factorial(3)
first and so on. Thus all intermediate calculations and variables must be stored in memory before the
next one in the stack will be calculated. This will lead us to the conclusion that space complexity is
in linear proportion to the number. The space complexity of one function call must be multiplied by
the number of function calls. Since one call of factorial() function would cost us constant memory
(because the number of variables and their sizes is constant) then we may calculate overall recursive
space complexity as follows:
O(n²) example
Let’s write a function that generates all possible pairs out of provided letters:
01-algorithms-and-their-complexities/pairs.js
1 /**
2 * Get all possible pairs out of provided letters.
3 *
4 * Example:
5 * letter = ['a', 'b']
6 * output = ['aa', 'ab', 'ba', 'bb']
7 *
8 * @param {string[]} letters
9 * @return {string[]}
10 */
11 export function pairs(letters) {
12 const result = [];
13
14 for (let i = 0; i < letters.length; i += 1) {
15 for (let j = 0; j < letters.length; j += 1) {
16 result.push(`${letters[i]}${letters[j]}`);
Algorithms and Their Complexities 10
17 }
18 }
19
20 return result;
21 }
Now we have two nested for() loops that acts like multipliers for the code inside the loops. Inside
the loops we have a code with constant execution time O(1) - it is just a pushing to the array.
So let’s calculate our time complexity as a sum of our function parts time complexities: O(1) +
letters.length * letters.length * O(1) + O(1). After dropping non-dominant parts we’ll get
our final time complexity:
This would mean that our function will slow down proportionally to the square of letters number
we will provide for it as an input.
You may also notice that the more letters we will provide for the function as an input the more pairs
it will generate. All those pairs are stored in pairs array. Thus this array will consume the memory
that is proportional to the square of letters number.
Let’s see it from the following example. Here is a function that multiplies all array elements by
specific value:
Algorithms and Their Complexities 11
01-algorithms-and-their-complexities/multiplyArrayInPlace.js
1 /**
2 * Multiply all values of the array by certain value in-place.
3 *
4 * Example:
5 * array = [1, 2, 3]
6 * multiplier = 2
7 * output = [2, 4, 6]
8 *
9 * @param {number[]} array
10 * @param {number} multiplier
11 * @return {number[]}
12 */
13 export function multiplyArrayInPlace(array, multiplier) {
14 for (let i = 0; i < array.length; i += 1) {
15 array[i] *= multiplier;
16 }
17
18 return array;
19 }
The space complexity of this function is in linear proportion to input array size. So the bigger the
input array size is the more memory this function will consume.
Not let’s analyze additional space. We may see that function does all of its operations in-place
without allocating any additional memory.
Sometimes it may be unacceptable to modify function arguments. And instead of modifying the
input array parameter in-place we might want to clone it first. Let’s see how we change the array
multiplication function to prevent input parameters modification.
Algorithms and Their Complexities 12
01-algorithms-and-their-complexities/multiplyArray.js
1 /**
2 * Multiply all values of the array by certain value with allocation
3 * of additional memory to prevent input array modification.
4 *
5 * Example:
6 * array = [1, 2, 3]
7 * multiplier = 2
8 * output = [2, 4, 6]
9 *
10 * @param {number[]} array
11 * @param {number} multiplier
12 * @return {number[]}
13 */
14 export function multiplyArray(array, multiplier) {
15 const multipliedArray = [...array];
16
17 for (let i = 0; i < multipliedArray.length; i += 1) {
18 multipliedArray[i] *= multiplier;
19 }
20
21 return multipliedArray;
22 }
In this version of the function, we’re allocating additional memory for cloning the input array. Thus
our space complexity becomes equal to O(2 * array.length). After discarding non-dominant parts
we get the following:
Because our algorithm now allocates additional memory the auxiliary space complexity has also
changed:
Understanding the difference is important so as not to get confused by space complexity evaluations.
Auxiliary space complexity might also become handy for example if we want to compare standard
sorting algorithms on the basis of space. It is better to use auxiliary space than space complexity
because it will make the difference between algorithms clearer. Merge Sort uses O(n) auxiliary space,
Insertion Sort and Heap Sort use O(1) auxiliary space. But at the same time, the space complexity
of all these sorting algorithms is O(n).
Other examples
You will find other big O examples like O(log(n)), O(n * log(n)) in the next chapters of this book.
Algorithms and Their Complexities 13
Quiz
Q1: Does time complexity answer the question of how many milliseconds an algorithm would take
to finish?
Q2: Which time complexity means faster execution time: O(n) or O(log(n))?
Q3: Is it true that algorithms with better time complexity also have better space complexity?
Linked List
• Difficulty: easy
This structure allows for efficient insertion or removal of nodes from any position in the sequence
during iteration. More complex implementations of linked lists add additional links, allowing
efficient insertion or removal from arbitrary node references. A drawback of linked lists is that
access time is linear. Faster access, such as random access, is not possible.
The main operations on linked lists are:
Applications
A linked list is used as a good foundation for many other data structures like queues, stacks and
hash tables. The linked list implementation we will create in JavaScript will be used for many of the
other data structures we will learn about in this book.
Linked List 15
Implementation
Our LinkedList implementation will consist of two parts:
• A LinkedListNode class
• A LinkedList class
We will explore the details of each of these classes in the following sections.
LinkedListNode
Our LinkedListNode class is a class that will represent one item in our collection of items. The
LinkedListNode has two important properties:
1. this.value
2. this.next
this.value contains the actual value we want to store in our node (a number, another object, a
string, etc). this.next is a pointer to the next node our list of nodes.
What is a pointer?
Objects in JavaScript are dictionaries with key-value pairs. Keys are always strings and values can be
a boolean or strings, numbers and objects. When we say our next property in our LinkedListNode
class is a “pointer”, what we really mean is that the value of this.next is a reference to another
JavaScript object – another LinkedListNode object.
constructor
Our constructor method for our LinkedListNode class sets the initial values for this.value and
this.next – both set to null.
02-linked-list/LinkedListNode.js
2 constructor(value, next = null) {
3 this.value = value;
4 this.next = next;
5 }
toString
Our toString method is a convenience method when we want to see what is contained in each node
of our linked list.
Linked List 16
02-linked-list/LinkedListNode.js
7 toString(callback) {
8 return callback ? callback(this.value) : `${this.value}`;
9 }
LinkedList
The LinkedList class contains some important methods to be able to manipulate our collection of
LinkedListNode objects. Let’s take a look at what each method does:
constructor
Our LinkedList class contains two properties: this.head and this.tail. Each of these properties
points to a LinkedListNode object. They are the beginning and the end of our collection of
LinkedListNodes.
02-linked-list/LinkedList.js
4 constructor() {
5 /** @var LinkedListNode */
6 this.head = null;
7
8 /** @var LinkedListNode */
9 this.tail = null;
10 }
prepend
The prepend method adds a LinkedListNode object to the beginning of our LinkedList. First, we
create a new LinkedListNode object.
02-linked-list/LinkedList.js
16 prepend(value) {
17 // Make new node to be a head.
18 const newNode = new LinkedListNode(value, this.head);
Then we set our this.head to point to our newly created LinkedListNode object. If this is the first
item in our LinkedList, we must also set this.tail to point to the new object, as it is also the last
item in our LinkedList.
Linked List 17
02-linked-list/LinkedList.js
19 this.head = newNode;
20
21 // If there is no tail yet let's make new node a tail.
22 if (!this.tail) {
23 this.tail = newNode;
24 }
25 return this;
26 }
append
The append method adds a LinkedListNode object to the end of our LinkedList. First, we create a
new LinkedListNode object.
02-linked-list/LinkedList.js
33 append(value) {
34 const newNode = new LinkedListNode(value);
If there are no other items in our LinkedList, then we need to set this.head to our new node.
02-linked-list/LinkedList.js
Then, we get the value of this.tail and set that objects next property to be our newly created
LinkedListNode object.
Linked List 18
02-linked-list/LinkedList.js
47 this.tail = newNode;
48
49 return this;
delete
Our delete method finds an item in the LinkedList and removes it. First, we check to see if our
LinkedList is empty by checking if this.head is null. If it is, we return null.
02-linked-list/LinkedList.js
56 delete(value) {
57 if (!this.head) {
58 return null;
59 }
If our LinkedList contains items in it, then we create a deletedNode variable which will serve as a
placeholder for the node that we will eventually return as the result from our delete method.
02-linked-list/LinkedList.js
Before we traverse our LinkedList, we first check to see if the value of this.head matches the value
we are trying to delete. If so, we set our deletedNode variable to this.head and then move this.head
to be the next item in our LinkedList.
Linked List 19
02-linked-list/LinkedList.js
62 // If the head must be deleted then make next node that is different
63 // from the head to be a new head.
64 while (this.head && this.head.value === value) {
65 deletedNode = this.head;
66 this.head = this.head.next;
67 }
In order to traverse our LinkedList, we need to create a placeholder variable, currentNode to keep
track of where we are in our LinkedList. We set the value of currentNode equal to this.head to
ensure we start at the beginning of our list.
02-linked-list/LinkedList.js
We then check to make sure that our currentNode is not null. If it isn’t, we kick off our while loop.
We will break out of our while loop when we reach a node where its this.next property is null,
meaning it doesn’t point to another LinkedListNode object.
02-linked-list/LinkedList.js
Next, we check to see if the value of currentNode.next object is equal to the value we want to
delete. If it is, then we set our deletedNode variable equal to currentNode.next. We also set the
currentNode.next pointer equal to the object that the currentNode.next object points to.
If the values don’t match, we just move on to the next item in our LinkedList.
02-linked-list/LinkedList.js
Finally, we must check if the value of the tail of our LinkedList matches the value we are trying to
remove. If it does match, we’ll set this.tail equal to currentNode.
See the following diagrams for a visual representation of what happens when the delete method is
called:
Linked List 20
deleteTail
For our deleteTail method, we first create a variable deletedTail and set it to this.tail so we
can return the object that is deleted when we return from this method.
02-linked-list/LinkedList.js
95 deleteTail() {
96 const deletedTail = this.tail;
For scenario 1, this.head.value and this.tail.value are the same, so we set both this.head and
this.tail to null and return deleteTail.
02-linked-list/LinkedList.js
For scenario 2, we need to traverse the entire list to get to the next to last LinkedListNode in
the LinkedList. We will set the next to last LinkedListNode to this.tail by checking if the
currentNode.next.next value is null. If currentNode.next.next is null, we know the currentNode.next
is the object we want to set to this.tail.
02-linked-list/LinkedList.js
02-linked-list/LinkedList.js
121
deleteHead
Our deleteHead method removes the first item in the list. Similar to our deleteTail method, we
have a few scenarios for which we need to write code to delete the head our LinkedList:
For scenario 1, we check if this.head is null, if it is null we return null from the method
02-linked-list/LinkedList.js
125 deleteHead() {
126 if (!this.head) {
127 return null;
128 }
For scenarios 2 and 3, we must first create a variable: deletedHead and set it to this.head so we can
return the object that is deleted when we return from this method.
02-linked-list/LinkedList.js
Also for scenarios 2 and 3, we combine the logic into an if/else statement. If this.head.next is not
null, we know there is more than 1 item in the list and we set this.head.next to equal this.head
which removes the first item in the list. If this.head.next is null, then we know there is only 1
item in the list and we set both this.head and this.tail to null as there are no more items in our
LinkedList.
Linked List 23
02-linked-list/LinkedList.js
131 if (this.head.next) {
132 this.head = this.head.next;
133 } else {
134 this.head = null;
135 this.tail = null;
136 }
find
The find method traverses the items in the LinkedList and locates the first LinkedListNode object
that matches the value we want. The method takes an object that has two key/value pairs, a value
and a callback:
02-linked-list/LinkedList.js
148 find({ value = undefined, callback = undefined }) {
As in our previous methods, we first check if this.head is null. If it is null, we return from the
method because the LinkedList is empty.
02-linked-list/LinkedList.js
149 if (!this.head) {
150 return null;
151 }
Next, we create a variable currentNode to keep track of where we are in the LinkedList and set it to
this.head to start at the beginning of the LinkedList.
02-linked-list/LinkedList.js
152
Then we create a while loop to loop over all the nodes in the LinkedList and either call the callback
function sent as a parameter or check the value of the current node to see if it matches the value we
are looking for. If there is a match, we return the node.
Linked List 24
02-linked-list/LinkedList.js
155 while (currentNode) {
156 // If callback is specified then try to find node by callback.
157 if (callback && callback(currentNode.value)) {
158 return currentNode;
159 }
160
161 // If value is specified then try to compare by value..
162 if (value !== undefined && currentNode.value === value) {
163 return currentNode;
164 }
165
166 currentNode = currentNode.next;
167 }
Finally, if we go through the entire LinkedList and do not find the value we are looking for, we
return null from our method.
02-linked-list/LinkedList.js
169 return null;
At this point, we’ve implemented the essential LinkedList methods. We’ll now implement two helper
methods that will make it more convenient to test and debug our LinkedList.
toArray
The toArray method takes all the nodes in the LinkedList and puts them in an array. To put them
in an array, we create a new array called nodes. Then we loop over all the nodes in the LinkedList
and push each one on to the array.
02-linked-list/LinkedList.js
175 toArray() {
176 const nodes = [];
177
178 let currentNode = this.head;
179 while (currentNode) {
180 nodes.push(currentNode);
181 currentNode = currentNode.next;
182 }
183
184 return nodes;
185 }
Linked List 25
toString
The toString method takes the LinkedList and prints out a string representation of the LinkedList.
The method takes the LinkedList and first converts it to an array (using the toArray method
described above). Then the map array method is called to process each LinkedListNode in the list. The
toString method for each LinkedListNode is called and then the toString array method is called
to print out the entire array of LinkedListNodes.
02-linked-list/LinkedList.js
191 toString(callback) {
192 return this.toArray().map(node => node.toString(callback)).toString();
193 }
Complexities
Problems Examples
Here are some related problems that you might encounter during the interview:
Quiz
Q1: What does this.next refer to in a linked list in JavaScript?
Q2: What is the difference between a deleteTail and deleteHead method in a linked list?
References
Linked List on Wikipedia https://en.wikipedia.org/wiki/Linked_list⁷
Linked List on YouTube https://www.youtube.com/watch?v=njTh_OwMljA⁸
⁵https://leetcode.com/problems/linked-list-cycle-ii/
⁶https://leetcode.com/problems/intersection-of-two-linked-lists/
⁷https://en.wikipedia.org/wiki/Linked_list
⁸https://www.youtube.com/watch?v=njTh_OwMljA
Linked List 26
Operations such as searching or updating elements are not common for queues. There are only two
things we’re interested in when working with queues: the beginning and the end of it.