Data Structures in JavaScript_ Arrays, HashMaps, and Lists _ Adrian Mejia Blog
Data Structures in JavaScript_ Arrays, HashMaps, and Lists _ Adrian Mejia Blog
When we are developing software, we have to store data in memory. However, many types of
data structures, such as arrays, maps, sets, lists, trees, graphs, etc., and choosing the right one
for the task can be tricky. This series of posts will help you know the trade-offs so that you can
use the right tool for the job!
This section will focus on linear data structures: Arrays, Lists, Sets, Stacks, and Queues.
You can find all these implementations and more in the Github repo:
https://github.com/amejiarosario/dsa.js
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 1/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
3. Data Structures for Beginners: Arrays, HashMaps, and Lists 👈 you are here
“ Bookmark it, pin it, or share it, so you have it at hand when you need it.
Click on the name to go to the section or click on the runtime to go to the implementation
* = Amortized runtime
Insertion to the
Array O(n) O(1) O(n) O(n) end is O(1) .
Details here.
Rehashing might
affect insertion
HashMap O(1) O(1) O(1) O(1)
time. Details
here.
Set using a
Set (using HashMap
O(1) - O(1) O(1)
HashMap) implementation.
Details here.
Implemented
Set (using list) O(n) - O(n) O(n) using Binary
Search Tree
Adding/Removing
Linked List to the start of the
O(n) - O(n) O(n)
(singly) list is O(1) .
Details here.
Adding/Deleting
from the
beginning/end is
Linked List O(1) . But,
O(n) - O(n) O(n)
(doubly) deleting/adding
from the middle is
O(n) . Details
here
Insert/delete is
Stack (array
O(1) - - O(1) last-in, first-out
implementation)
(LIFO)
Remove
Queue (naïve
O(1) - - O(n) ( Array.shift )
array impl.)
is O(n)
Using Doubly
Queue (list Linked List with
O(1) - - O(1)
implementation) reference to the
last element.
Note: Binary search trees and trees, in general, will be cover in the next post. Also, graph
data structures.
Integers. E.g., 1 , 2 , 3 , …
Characters. E.g., a , b , "1" , "*"
Booleans. E.g., true or false .
Float (floating points) or doubles. E.g., 3.14159 , 1483e-2 .
Null values. E.g. null
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 3/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
undefined
Symbol
Number
Note: Objects are not primitive since they are composed of zero or more primitives and other
objects.
Array
Arrays are collections of zero or more elements. Arrays are one of the most used data
structures because of their simplicity and fast way of retrieving information.
You can think of an array as a drawer where you can store things in the bins.
When you want to search for something, you can go directly to the bin number. That’s a
constant time operation ( O(1) ). However, if you forgot what cabinet had, you will have to open
one by one ( O(n) ) to verify its content until you find what you are looking for. That same
happens with an array.
Depending on the programming language, arrays have some differences. For some dynamic
languages like JavaScript and Ruby, an array can contain different data types: numbers,
strings, words, objects, and even functions. In typed languages like Java/C/C++, you have to
predefine the Array size and the data type. In JavaScript, it would automatically increase the
size of the Array when needed.
For instance, in JavaScript, we can accomplish append to end with push and append to the
beginning with unshift . But also, we have pop and shift to remove from an array. Let’s
describe some everyday operations that we are going to use through this post.
Based on the language specification, push just set the new value at the end of the Array. Thus,
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 5/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
What do you think is the runtime of the insertToHead function? It looks the same as the
previous one, except that we are using unshift instead of push . But there’s a catch! unshift
algorithm makes room for the new element by moving all existing ones to the next position in
the Array. So, it will iterate through all the elements.
As you can see in the code above, accessing an element on an array has a constant time:
Note: You can also change any value at a given index in constant time.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 6/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
1. You can delete from the end of the Array, which might be constant time. O(1)
2. However, you can also remove it from the beginning or middle of the collection. In that
case, you would have to move all the following elements to close the gap. O(n)
So we are using our search function to find the elements’ index O(n). Then we use the JS
built-in splice function, which has a running time of O(n). What’s the total O(2n)? Remember,
we constants don’t matter as much.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 7/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Operation Worst
HashMaps
Maps, dictionaries, and associative arrays all describe the same abstract data type. But hash
map implementations are distinct from treemap implementations in that one uses a hash table
and one uses a binary search tree.
Going back to the drawer analogy, bins have a label rather than a number.
HashMap is like a drawer that stores things on bins and labels them
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 8/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
In this example, if you are looking for the book, you don’t have to open bin 1, 2, and 3. You go
directly to the container labeled as “books”. That’s a huge gain! Search time goes from O(n) to
O(1).
In arrays, the data is referenced using a numeric index (relatively to the position). However,
HashMaps uses labels that could be a string, number, Object, or anything. Internally, the
HashMap uses an Array, and it maps the labels to array indexes using a hash function.
1. Array: Using a hash function to map a key to the array index value. Worst: O(n) ,
Average: O(1)
2. Binary Search Tree: using a self-balancing binary search tree to look up for values
(more on this later). Worst: O(log n) , Average: O(log n) .
We will cover Trees & Binary Search Trees, so don’t worry about it for now. The most common
implementation of Maps is using an array and hash function. So, that’s the one we are going
to focus on.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 9/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
As you can see in the image, each key gets translated into a hash code. Since the array size
is limited (e.g., 10), we have to loop through the available buckets using the modulus function.
In the buckets, we store the key/value pair, and if there’s more than one, we use a collection to
hold them.
Now, What do you think about covering each of the HashMap components in detail? Let’s start
with the hash function.
Let’s say you want to count how many times words are used in a text. How would you
implement that?
1. You can use two arrays (let’s call it A and B ). One for storing the word and another for
storing how many times they have seen (frequency).
2. You can use a HashMap. They key is the word, and the value is the word’s frequency.
What is the runtime of approach #1 using two arrays? If we say, the number of words in the
text is n . Then we have to search if the word in the array A and then increment the value on
array B matching that index. For every word on n , we have to test if it’s already on array A .
This double loop leave use with a runtime of O(n2) .
What is the runtime of approach #2 using a HashMap? We iterate through each word on the
text once and increment the value if there is something there or set it to 1 if that word is seen
for the first time. The runtime would be O(n) , which is much more performant than approach
#1.
Hash Function
The first step to implement a HashMap is to have a hash function. This function will map every
key to its value.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 10/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
“ The perfect hash function is the one that for every key, it assigns a unique index.
Ideal hashing algorithms allow constant time access/lookup. However, it’s hard to achieve a
perfect hashing function in practice. You might have the case where two different keys yields on
the same index, causing a collision.
Collisions in HashMaps are unavoidable when using an array-like underlying data structure. At
some point, data that can’t fit in a HashMap will reuse data slots. One way to deal with
collisions is to store multiple values in the same bucket using a linked list or another array
(more on this later). When we try to access the key’s value and found various values, we iterate
over the values O(n). However, in most implementations, the hash adjusts the size dynamically
to avoid too many collisions. We can say that the amortized lookup time is O(1). We are
going to explain what we mean by amortized runtime later in this post with an example.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 11/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
1 class NaiveHashMap {
2
3 constructor(initialCapacity = 2) {
4 this.buckets = new Array(initialCapacity);
5 }
6
7 set(key, value) {
8 const index = this.getIndex(key);
9 this.buckets[index] = value;
10 }
11
12 get(key) {
13 const index = this.getIndex(key);
14 return this.buckets[index];
15 }
16
17 hash(key) {
18 return key.toString().length;
19 }
20
21 getIndex(key) {
22 const indexHash = this.hash(key);
23 const index = indexHash % this.buckets.length;
24 return index;
25 }
26 }
We are using buckets rather than drawer/bins, but you get the idea :)
We have an initial capacity of 2 (two buckets). But, we want to store any number of elements
on them. We use modulus % to loop through the number of available buckets.
Take a look at our hash function in line 18. We are going to talk about it in a bit. First, let’s use
our new HashMap!
1 // Usage:
2 const assert = require('assert');
3 const hashMap = new NaiveHashMap();
4
5 hashMap.set('cat', 2);
6 hashMap.set('rat', 7);
7 hashMap.set('dog', 1);
8 hashMap.set('art', 8);
9
10 console.log(hashMap.buckets);
11 /*
12 bucket #0: <1 empty item>,
13 bucket #1: 8
14 */
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 12/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
15
16 assert.equal(hashMap.get('art'), 8); // this one is ok
17 assert.equal(hashMap.get('cat'), 8); // got overwritten by art 😱
18 assert.equal(hashMap.get('rat'), 8); // got overwritten by art 😱
19 assert.equal(hashMap.get('dog'), 8); // got overwritten by art 😱
This Map allows us to set a key and a value and then get the value using a key . The key
part is the hash function. Let’s see multiple implementations to see how it affects the Map’s
performance.
Can you tell what’s wrong with NaiveHashMap before expanding the answer below?
“ The primary purpose of a HashMap is to reduce the search/access time of an Array from O(n)
to O(1) .
Let’s give it another shot at our hash function. Instead of using the string’s length, let’s sum
each character ascii code.
1 hash(key) {
2 let hashValue = 0;
3 const stringKey = key.toString();
4
5 for (let index = 0; index < stringKey.length; index++) {
6 const charCode = stringKey.charCodeAt(index);
7 hashValue += charCode;
8 }
9
10 return hashValue;
11 }
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 13/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
This one is better! Because words with the same length have different codes.
Howeeeeeeeeever, there’s still an issue! Because rat and art are both 327, collision! 💥
1 hash(key) {
2 let hashValue = 0;
3 const stringKey = `${key}`;
4
5 for (let index = 0; index < stringKey.length; index++) {
6 const charCode = stringKey.charCodeAt(index);
7 hashValue += charCode << (index * 8);
8 }
9
10 return hashValue;
11 }
Now let’s try again, this time with hex numbers so we can see the offset.
1 hash(1); // 49
2 hash('1'); // 49
3
4 hash('1,2,3'); // 741485668
5 hash([1,2,3]); // 741485668
6
7 hash('undefined') // 3402815551
8 hash(undefined) // 3402815551
Houston, we still have a problem!! Different value types shouldn’t return the same hash code!
One way is taking into account the key type into the hash function.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 14/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
1 hash(key) {
2 let hashValue = 0;
3 const stringTypeKey = `${key}${typeof key}`;
4
5 for (let index = 0; index < stringTypeKey.length; index++) {
6 const charCode = stringTypeKey.charCodeAt(index);
7 hashValue += charCode << (index * 8);
8 }
9
10 return hashValue;
11 }
1 console.log(hash(1)); // 1843909523
2 console.log(hash('1')); // 1927012762
3
4 console.log(hash('1,2,3')); // 2668498381
5 console.log(hash([1,2,3])); // 2533949129
6
7 console.log(hash('undefined')); // 5329828264
8 console.log(hash(undefined)); // 6940203017
We also can change the initial capacity of the Array to minimize collisions. Let’s put all of that
together in the next section.
Hash function that checks types and character orders to minimize collisions.
Handle collisions by appending values to a list. We also added a counter to keep track
of them.
1 class DecentHashMap {
2
3 constructor(initialCapacity = 2) {
4 this.buckets = new Array(initialCapacity);
5 this.collisions = 0;
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 15/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
6 }
7
8 set(key, value) {
9 const bucketIndex = this.getIndex(key);
10 if(this.buckets[bucketIndex]) {
11 this.buckets[bucketIndex].push({key, value});
12 if(this.buckets[bucketIndex].length > 1) { this.collisions++; }
13 } else {
14 this.buckets[bucketIndex] = [{key, value}];
15 }
16 return this;
17 }
18
19 get(key) {
20 const bucketIndex = this.getIndex(key);
21 for (let arrayIndex = 0; arrayIndex < this.buckets[bucketIndex].length; ar
22 const entry = this.buckets[bucketIndex][arrayIndex];
23 if(entry.key === key) {
24 return entry.value
25 }
26 }
27 }
28
29 hash(key) {
30 let hashValue = 0;
31 const stringTypeKey = `${key}${typeof key}`;
32
33 for (let index = 0; index < stringTypeKey.length; index++) {
34 const charCode = stringTypeKey.charCodeAt(index);
35 hashValue += charCode << (index * 8);
36 }
37
38 return hashValue;
39 }
40
41 getIndex(key) {
42 const indexHash = this.hash(key);
43 const index = indexHash % this.buckets.length;
44 return index;
45 }
46 }
1 // Usage:
2 const assert = require('assert');
3 const hashMap = new DecentHashMap();
4
5 hashMap.set('cat', 2);
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 16/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
6 hashMap.set('rat', 7);
7 hashMap.set('dog', 1);
8 hashMap.set('art', 8);
9
10 console.log('collisions: ', hashMap.collisions); // 2
11 console.log(hashMap.buckets);
12 /*
13 bucket #0: [ { key: 'cat', value: 2 }, { key: 'art', value: 8 } ]
14 bucket #1: [ { key: 'rat', value: 7 }, { key: 'dog', value: 1 } ]
15 */
16
17 assert.equal(hashMap.get('art'), 8); // this one is ok
18 assert.equal(hashMap.get('cat'), 2); // Good. Didn't got overwritten by art
19 assert.equal(hashMap.get('rat'), 7); // Good. Didn't got overwritten by art
20 assert.equal(hashMap.get('dog'), 1); // Good. Didn't got overwritten by art
This DecentHashMap gets the job done, but there are still some issues. We are using a decent
hash function that doesn’t produce duplicate values, and that’s great. However, we have two
values in bucket#0 and two more in bucket#1 . How is that possible?
Since we are using a limited bucket size of 2, we use modulus % to loop through the number
of available buckets. So, even if the hash code is different, all values will fit on the Array size:
bucket#0 or bucket#1.
So naturally, we have increased the initial capacity, but by how much? Let’s see how the initial
size affects the hash map performance.
If we have an initial capacity of 1 . All the values will go into one bucket ( bucket#0 ), and it
won’t be any better than searching a deal in a simple array O(n) .
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 17/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
10 /*
11 bucket#0: [ { key: 'cat', value: 2 }, { key: 'art', value: 8 } ],
12 <4 empty items>,
13 bucket#5: [ { key: 'rat', value: 7 } ],
14 <1 empty item>,
15 bucket#7: [ { key: 'dog', value: 1 } ],
16 <2 empty items>
17 */
As you can see, we reduced the number of collisions (from 2 to 1) by increasing the hash
map’s initial capacity.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 18/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Yay! 🎊 no collision!
Having a bigger bucket size is excellent to avoid collisions, but it consumes too much
memory, and probably most of the buckets will be unused.
Wouldn’t it be great if we can have a HashMap that automatically increases its size as needed?
Well, that’s called ** rehash**, and we are going to do it next!
Having allocated massive amounts of memory is impractical. So, we can automatically have the
hash map resize itself based on a load factor. This operation is called ** rehash**.
The load factor is the measurement of how full is a hash map. We can get the load factor by
dividing the number of items by the bucket size.
Pay special attention to lines 96 to 114. That’s where the rehash magic happens. We create a
new HashMap with doubled capacity.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 19/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
17 hashMap.set('Hello', 'Adele');
18 hashMap.set('All About That Bass', 'Meghan Trainor');
19 hashMap.set('This Is What You Came For', 'Calvin Harris ');
20
21 assert.equal(hashMap.collisions, 2);
22 assert.equal(hashMap.getLoadFactor(), 0.75);
23 assert.equal(hashMap.buckets.length, 16);
24
25 hashMap.set('Wake Me Up', 'Avicii'); // <--- Trigger REHASH
26
27 assert.equal(hashMap.collisions, 0);
28 assert.equal(hashMap.getLoadFactor(), 0.40625);
29 assert.equal(hashMap.buckets.length, 32);
Take notice that after we add the 12th item, the load factor gets beyond 0.75, so a rehash is
triggered and doubles the capacity (from 16 to 32). Also, you can see how the number of
collisions improves from 2 to 0!
This implementation is good enough to help us figure out the runtime of standard operations
like insert/search/delete/edit.
1. The hash function that every key produces for different output.
2. Size of the bucket to hold data.
We nailed both 🔨. We have a decent hash function that produces different outputs for different
data. Two distinct data will never return the same code. Also, we have a rehash function that
automatically grows the capacity as needed. That’s great!
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 20/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Note: We will use the Map rather than the regular Object , since the Map’s key could be
anything while on Object’s key can only be string or number. Also, Map s keeps the order of
insertion.
Behind the scenes, the Map.set just insert elements into an array (take a look at
DecentHashMap.set ). So, similar to Array.push we have that:
“ Insert an element in HashMap runtime is O(1). If rehash is needed, then it will take O(n)
Our implementation with rehash functionality will keep collisions to the minimum. The rehash
operation takes O(n) , but it doesn’t happen all the time, only when it is needed.
1 get(key) {
2 const hashIndex = this.getIndex(key);
3 const values = this.array[hashIndex];
4 for (let index = 0; index < values.length; index++) {
5 const entry = values[index];
6 if(entry.key === key) {
7 return entry.value
8 }
9 }
10 }
If there’s no collision, then values will only have one value, and the access time would be
O(1) . But, we know there will be collisions. If the initial capacity is too small and the hash
function is terrible like NaiveHashMap.hash, then most of the elements will end up in a few
buckets O(n) .
HashMap access operation has a runtime of O(1) on average and worst-case of O(n) .
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 21/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
“
Advanced Note: Another idea to reduce the time to get elements from O(n) to O(log n) is to
use a binary search tree instead of an array. Actually, Java’s HashMap implementation switches
from an array to a tree when a bucket has more than 8 elements.
“ HashMap edits and delete operations has a runtime of O(1) on average and worst-case of
O(n) .
Sets
Sets are very similar to arrays. The difference is that they don’t allow duplicates.
How can we implement a Set (Array without duplicates)? We could use an array and check if
an element is there before inserting a new one. But the running time of checking if a value is
already there is O(n) . Can we do better than that? We develop the Map with an amortized run
time of O(1) !
Set Implementation
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 22/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
We could use the JavaScript built-in Set . However, if we implement it by ourselves, it’s more
logical to deduct the runtimes. We are going to use the optimized HashMap with rehash
functionality.
We used HashMap.set to add the set elements without duplicates. We use the key as the
value, and since the hash map’s keys are unique, we are all set.
Checking if an element is already there can be done using the hashMap.has , which has an
amortized runtime of O(1) . Most operations would be an amortized constant time except for
getting the entries , O(n) .
Note: The JS built-in Set.has has a runtime of O(n) since it uses a regular list of elements and
checks each one at a time. You can see the Set.has algorithm here
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 23/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
You should be able to use MySet and the built-in Set interchangeably for these examples.
Linked Lists
A linked list is a data structure where every element is connected to the next one.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 24/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
The linked list is the first data structure that we are going to implement without using an array.
Instead, we will use a node that holds a value and points to the next element.
node.js
1 class Node {
2 constructor(value) {
3 this.value = value;
4 this.next = null;
5 }
6 }
When we have a chain of nodes where each one points to the next one, we a Singly Linked
list.
linked-list.js
1 class LinkedList {
2 constructor() {
3 this.root = null; // first/head/root element
4 this.size = 0; // total number of elements in the list
5 }
6
7 // ...
8 }
There are four basic operations that we can do in every Linked List:
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 25/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
1. If the list first (root/head) doesn’t have any element yet, we make this node the head of the
list.
2. Contrary, if the list already has items, then we have to iterate until finding the last one and
appending our new node to the end.
LinkedList.prototype.addLast
What’s the runtime of this code? If it is the first element, then adding to the root is O(1).
However, finding the last item is O(n).
Now, removing an element from the end of the list has a similar code. We have to find the
current before last and make its next reference null .
LinkedList.prototype.removeLast
1 removeLast() {
2 let current = this.root;
3 let target;
4
5 if(current && current.next) {
6 while(current && current.next && current.next.next) {
7 current = current.next;
8 }
9 target = current.next;
10 current.next = null;
11 } else {
12 this.root = null;
13 target = current;
14 }
15
16 if(target) {
17 return target.value;
18 }
19 }
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 26/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
The runtime again is O(n) because we have to iterate until the second-last element and remove
the reference to the last (line 10).
LinkedList.addFirst
1 /**
2 * Adds an element to the beginning of the list. Similar to Array.unshift
3 * Runtime: O(1)
4 * @param {any} value
5 */
6 addFirst(value) {
7 const node = new Node(value);
8 node.next = this.root;
9 this.root = node;
10 }
Adding and removing elements from the beginning is a constant time because we hold a
reference to the first element:
LinkedList.removeFirst
1 /**
2 * Removes element from the start of the list (head/root). It's Similar `Arra
3 * Runtime: O(1)
4 */
5 removeFirst() {
6 const first = this.root;
7
8 if (first) {
9 this.root = first.next;
10 return first.value;
11 }
12 }
As expected, the runtime for removing/adding to the first element from a linked List is always
constant O(1)
Removing an element anywhere in the list leverage the removeLast and removeFirst .
However, if the removal is in the middle, then we assign the previous node to the next one.
That removes any reference from the current node, this is removed from the list:
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 27/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
LinkedList.remove
1 remove(index = 0) {
2 if(index === 0) {
3 return this.removeFirst();
4 }
5
6 for (let current = this.first, i = 0; current; i++, current = current.next)
7 if(i === index) {
8 if(!current.next) { // if it doesn't have next it means that it is the l
9 return this.removeLast();
10 }
11 current.previous.next = current.next;
12 this.size--;
13 return current.value;
14 }
15 }
16 }
Note that index is a zero-based index: 0 will be the first element, 1 second, and so on.
LinkedList.contains
1 contains(value) {
2 for (let current = this.first, index = 0; current; index++, current = curren
3 if(current.value === value) {
4 return index;
5 }
6 }
7 }
This function finds the first element with the given value.
Notice that every time we add/remove from the last position, the operation takes O(n).
“ But we could reduce the addLast / removeLast from O(n) to a flat O(1) if we keep a reference of
the last element!
Doubly linked list nodes have double references (next and previous). We are also going to keep
track of the list first and the last element.
1 class Node {
2 constructor(value) {
3 this.value = value;
4 this.next = null;
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 29/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
5 this.previous = null;
6 }
7 }
8
9 class LinkedList {
10 constructor() {
11 this.first = null; // head/root element
12 this.last = null; // last element of the list
13 this.size = 0; // total number of elements in the list
14 }
15
16 // ...
17 }
Adding and removing from the start of the list is simple since we have this.first reference:
1 addFirst(value) {
2 const node = new Node(value);
3
4 node.next = this.first;
5
6 if(this.first) {
7 this.first.previous = node;
8 } else {
9 this.last = node;
10 }
11
12 this.first = node; // update head
13 this.size++;
14
15 return node;
16 }
Notice that we have to be very careful and update the previous and last reference.
1 removeFirst() {
2 const first = this.first;
3
4 if(first) {
5 this.first = first.next;
6 if(this.first) {
7 this.first.previous = null;
8 }
9
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 30/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
10 this.size--;
11
12 return first.value;
13 } else {
14 this.last = null;
15 }
16 }
“ Adding and removing elements from a (singly/doubly) LinkedList has a constant runtime O(1)
Adding and removing from the end of the list is a little tricky. If you checked in the Singly Linked
List, both operations took O(n) since we had to loop through the list to find the last element.
Now, we have the last reference:
1 addLast(value) {
2 const node = new Node(value);
3
4 if(this.first) {
5 let currentNode = this.first;
6 node.previous = this.last;
7 this.last.next = node;
8 this.last = node;
9 } else {
10 this.first = node;
11 this.last = node;
12 }
13
14 this.size++;
15
16 return node;
17 }
Again, we have to be careful about updating the references and handling exceptional cases
such as only one element.
1 removeLast() {
2 let current = this.first;
3 let target;
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 31/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
4
5 if(current && current.next) {
6 current = this.last.previous;
7 this.last = current;
8 target = current.next;
9 current.next = null;
10 } else {
11 this.first = null;
12 this.last = null;
13 target = current;
14 }
15
16 if(target) {
17 this.size--;
18 return target.value;
19 }
20 }
Using a doubly-linked list, we no longer have to iterate through the whole list to get the 2nd last
element. We can use directly this.last.previous and is O(1) .
Did you remember that for the Queue, we had to use two arrays? We can now change that
implementation and use a doubly-linked list instead. The runtime will be O(1) for insert at the
start and deleting at the end.
Adding an element on anywhere on the list leverages our addFirst and addLast functions
as you can see below:
LinkedList.add FullCode
1 add(value, index = 0) {
2 if(index === 0) {
3 return this.addFirst(value);
4 }
5
6 for (let current = this.first, i = 0; i <= this.size; i++, current = (curre
7 if(i === index) {
8 if(i === this.size) { // if it doesn't have next it means that it is the
9 return this.addLast(value);
10 }
11 const newNode = new Node(value);
12 newNode.previous = current.previous;
13 newNode.next = current;
14
15 current.previous.next = newNode;
16 if(current.next) { current.next.previous = newNode; }
17 this.size++;
18 return newNode;
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 32/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
19 }
20 }
21 }
If we have an insertion in the middle of the Array, then we have to update the next and
previous reference of the surrounding elements.
Doubly linked lists are a significant improvement compared to the singly linked list! We
improved from O(n) to O(1) by:
Removing first/last can be done in constant time; however, eliminating in the middle of the Array
is still O(n).
Stacks
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 33/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Stacks is a data structure where the last entered data is the first to come out. Also know as
Last-in, First-out (LIFO).
1 class Stack {
2 constructor() {
3 this.input = [];
4 }
5
6 push(element) {
7 this.input.push(element);
8 return this;
9 }
10
11 pop() {
12 return this.input.pop();
13 }
14 }
As you can see, it is easy since we are using the built-in Array.push and Array.pop . Both
have a runtime of O(1) .
The first element in ( a ) is the last to get out. We can also implement Stack using a linked list
instead of an array. The runtime will be the same.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 34/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
That’s all!
Queues
Queues are a data structure where the first data to get in is also the first to go out. A.k.a First-
in, First-out (FIFO). It’s like a line of people at the movies, the first to come in is the first to come
out.
We could implement a Queue using an array, very similar to how we implemented the Stack.
1 class Queue {
2 constructor() {
3 this.input = [];
4 }
5
6 add(element) {
7 this.input.push(element);
8 }
9
10 remove() {
11 return this.input.shift();
12 }
13 }
Queue.remove uses array.shift which has a linear runtime. Can we do better than
O(n) ?
Think of how you can implement a Queue only using Array.push and Array.pop .
1 class Queue {
2 constructor() {
3 this.input = [];
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 35/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
4 this.output = [];
5 }
6
7 add(element) {
8 this.input.push(element);
9 }
10
11 remove() {
12 if(!this.output.length) {
13 while(this.input.length) {
14 this.output.push(this.input.pop());
15 }
16 }
17 return this.output.pop();
18 }
19 }
When we remove something for the first time, the output array is empty. So, we insert the
content of input backward like ['b', 'a'] . Then we pop elements from the output array.
As you can see, using this trick, we get the output in the same order of insertion (FIFO).
If the output already has some elements, then the remove operation is constant O(1) . When
the output arrays need to get refilled, it takes O(n) to do so. After the refilled, every operation
would be constant again. The amortized time is O(1) .
We can achieve a Queue with a pure constant if we use LinkedList. Let’s see what it is in the
next section!
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 36/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Using a doubly-linked list with the last element reference, we achieve an add of O(1). That’s
the importance of using the right tool for the right job. 💪
Summary
We explored most of the linear data structures. We saw that depending on how we implement
the data structures. There are different runtimes.
Here’s a summary of everything that we explored. You can click on each runtime, and it will
take you to the implementation.
Time complexity
Click on the name to go to the section or click on the runtime to go to the implementation
* = Amortized runtime
Insertion to the
Array O(n) O(1) O(n) O(n) end is O(1) .
Details here.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 37/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Rehashing might
affect insertion
HashMap O(1) O(1) O(1) O(1)
time. Details
here.
Set using a
Set (using HashMap
O(1) - O(1) O(1)
HashMap) implementation.
Details here.
Implemented
Set (using list) O(n) - O(n) O(n) using Binary
Search Tree
Adding/Removing
Linked List to the start of the
O(n) - O(n) O(n)
(singly) list is O(1) .
Details here.
Adding/Deleting
from the
beginning/end is
Linked List O(1) . But,
O(n) - O(n) O(n)
(doubly) deleting/adding
from the middle is
O(n) . Details
here
Insert/delete is
Stack (array
O(1) - - O(1) last-in, first-out
implementation)
(LIFO)
Remove
Queue (naïve
O(1) - - O(n) ( Array.shift )
array impl.)
is O(n)
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 38/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Using Doubly
Queue (list Linked List with
O(1) - - O(1)
implementation) reference to the
last element.
Note: Binary search trees and trees, in general, will be cover in the next post. Also, graph
data structures.
NEWER OLDER
Graph Data Structures in JavaScript for Beginners Analysis of Recursive Algorithms
email address
Subscribe
Follow @iAmAdrianMejia
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 39/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 40/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 41/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
8 time complexities
that every programmer
should know
Data Structures in
JavaScript: Arrays,
HashMaps, and Lists
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 42/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 43/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
Self-balanced Binary
Search Trees with AVL
in JavaScript
Analysis of Recursive
Algorithms
Contents
1. Data Structures Big-O Cheatsheet
2. Primitive Data Types
3. Array
4. HashMaps
5. Sets
6. Linked Lists
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 44/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
7. Stacks
8. Queues
9. Summary
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 45/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
21 Comments
1 Login
Name
Bombehjort − ⚑
2 years ago edited
Same lol
Even tho I wanna go to a better college outside of my country, they just won't let me..
But when my bro want it, they just let him..
Life is so unfair.. (`^´)
Tr.2632N.US/jH4952gp
62 0 Reply Share ›
Ravina Parab − ⚑
5 years ago
9 0 Reply Share ›
Shifting operations are the bits level. 1 byte = 8 bits. So, we use multiple of 8 to avoid bytes overlap
1 0 Reply Share ›
Vaibhav Baluni − ⚑
V 6 years ago
Shouldn't insert complexity for "Queue (naive array impl.)" be O(1) as it will use Array.push for insert. In the
table you mentioned the opposite "Insert (Array.shift)". Array.shift is for removing 1st element.
2 0 Reply Share ›
0 0 Reply Share ›
Chaitanya Sairam − ⚑
C 5 years ago
1 0 Reply Share ›
@Chaitanya Sairam You can use an Object and Map interchangably in most cases. But, it's
recommended to use Map because it's optimize for that. Here are some differences:
- Objects keys should be strings or number, while on Maps can be anything. You can have a key as
array or even another object.
- If you use an Object as a map and want to get the keys, you have to use `hasOwnProperty` or
`Object.keys`. However, you might also get functions if you define them in the object. If you use a
Map, you don't have to worry about your map being used for another thing other than map unlike
objects.
0 0 Reply Share ›
Csaba − ⚑
C 5 years ago edited
removeFirst() {
let current = this.root;
if (current && current.next) {
this.root = this.root.next;
} else this.root = null;
if (current) return current.value;
};
};
1 0 Reply Share ›
Good catch, thanks for pointing that out. It's fixed now
0 0 Reply Share ›
Vaibhav Baluni − ⚑
V 6 years ago
Deletion of key from keys array will not work as on Line 83 in HashMap class "keyIndex" will be undefined as
_getIndexes will never return this value.
1) Is deleting key from keys array actually required? Not deleting will have a duplicate entry for the same key.
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 47/50
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
1 0 Reply Share ›
What implementation were you using? The most up-to-date is https://github.com/amejiaro.... All the
previous implementations are simpler and builds up to this last one. Let me know if you see an issue
on that one.
0 0 Reply Share ›
Pablito Millenio − ⚑
3 years ago
I had never seen such a brilliant way of explaining the HashMap. And furthermore it is in Javascript.
Impressive !
0 0 Reply Share ›
0 0 Reply Share ›
Viktror Soroka − ⚑
4 years ago edited
1)
What is the runtime of approach #1 using two arrays? If we say, the number of words in the text is n. Then
we have to search if the word in the array A and then increment the value on array B matching that index. For
every word on n, we have to test if it’s already on array A. This double loop leave use with a runtime of O(n2).
Not clear description for me, could you please clarify. Is array A already contains all the unique words in text or
it is empty initially and we go over the text and then fill the array A based on it and array B? If the first
statement is correct then why should we check for the word existence as it is already prepopulated for us? If
the second, then it makes sense, but I think a better description should be provided.
3) remove method in Singly Linked List: IMO it is suboptimal to iterate over the list and while being on the last
item realise that we are going to remove the last item and then use removeLast() method which iterates from
the beginning again. I think this method should not be used at all and the initial iteration should count for this
case.
0 0 Reply Share ›
Justin − ⚑
J
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 48/50
J
1/10/25, 9:49 AM Data Structures in JavaScript: Arrays, HashMaps, and Lists | Adrian Mejia Blog
4 years ago
See in the buckets where the k, v objects are ... where are the values coming from? Like why is “art” 8? And
“cat” 2? And “rat” 7?
0 0 Reply Share ›
George Miller − ⚑
4 years ago
I didn't know that in the ES6 `Set` has a linear time complexity for checking if an element exists or not i.e.
`set.has` 😅
0 0 Reply Share ›
raz_al_ghul − ⚑
R 5 years ago
0 0 Reply Share ›
Csaba − ⚑
C 5 years ago edited
remove(value) {
let current = this.root;
let target;
if (!(current)) return;
if (current.value === value) return this.removeFirst();
while (current) {
if (current.next && current.next.value === value) {
target = current.next.value;
current.next = current.next.next;
break;
}
current = current.next;
}
return target;
};
0 0 Reply Share ›
5 years ago
@Csaba you are right, I missed adding the size attribute on the constructor, I added it back. You can
see my full implementations of the LinkedList on Github repo: https://github.com/amejiaro...
In there you will notice that I have a "removeByPosition", I think it's a good idea to implement a
"removeByValue" as you pointed out
0 0 Reply Share ›
https://adrianmejia.com/data-structures-time-complexity-for-beginners-arrays-hashmaps-linked-lists-stacks-queues-tutorial/ 50/50