Data Structures in JavaScript and TypeScript - Simple & Code Examples
Table of contents
In this blog post, I will delve into the world of data structures and explore some of the most commonly used data structures in JavaScript and TypeScript.
Data Structures
Data structures are essential components of any computer program, and they play a critical role in managing, organizing, and storing data. Simply put, a data structure is a way of organizing and storing data in a particular format so that it can be accessed and used efficiently.
In programming, data structures are used to solve complex problems and optimize algorithms, making them indispensable tools for developers across different programming languages, including JavaScript and TypeScript.
Arrays
Arrays are one of the most commonly used data structures in JavaScript and TypeScript. They are used to store collections of data of the same type and can be accessed using an index number. Here are two examples of how arrays can be implemented
JavaScript Example:
// Creating an array in JavaScript
let fruits = ["apple", "banana", "orange"];
// Accessing array elements using their index
console.log(fruits[0]); // Output: "apple"
console.log(fruits[1]); // Output: "banana"
console.log(fruits[2]); // Output: "orange"
In this example, we create an array named fruits
that contains three string elements. We then access each element of the array using its index number, starting from 0.
TypeScript Example:
// Creating an array in TypeScript
let numbers: number[] = [1, 2, 3];
// Accessing array elements using their index
console.log(numbers[0]); // Output: 1
console.log(numbers[1]); // Output: 2
console.log(numbers[2]); // Output: 3
In this example, we create an array named numbers
that contains three numeric elements. The type of the elements is specified using the number
keyword, which is followed by square brackets []
. We then access each element of the array using its index number, starting from 0.
Both of these examples illustrate how arrays can be used to store collections of data in JavaScript and TypeScript, and how we can access their elements using their index number.
Objects
Objects are another commonly used data structure in JavaScript and TypeScript. Unlike arrays, which are used to store collections of data of the same type, objects are used to store collections of key-value pairs. In other words, an object is a collection of properties, where each property has a name (or key) and a value. Here is an example of how objects can be implemented in JavaScript and TypeScript:
JavaScript Example:
// Creating an object in JavaScript
let person = {
name: "Esther",
age: 30,
address: "123 Main St"
};
// Accessing object properties using dot notation
console.log(person.name); // Output: "Esther"
console.log(person.age); // Output: 30
console.log(person.address); // Output: "123 Main St"
In this example, we create an object named person
that contains three properties: name
, age
, and address
. We can access each property of the object using dot notation, where we specify the name of the object followed by a dot and the name of the property.
TypeScript Example:
// Creating an object in TypeScript
interface Person {
name: string;
age: number;
address: string;
}
let person: Person = {
name: "Esther",
age: 30,
address: "123 Main St"
};
// Accessing object properties using dot notation
console.log(person.name); // Output: "Esther"
console.log(person.age); // Output: 30
console.log(person.address); // Output: "123 Main St"
In this example, we create an object named person
that contains three properties: name
, age
, and address
. The Person
interface defines the type of the object, specifying that each property is of a particular type. We can access each property of the object using dot notation, just like in the JavaScript example.
Both of these examples illustrate how objects can be used to store collections of key-value pairs in JavaScript and TypeScript, and how we can access their properties using dot notation. Objects are a powerful and flexible data structure that can be used in many different ways in programming.
Sets
Sets are a relatively new data structure in JavaScript and TypeScript that were introduced in ECMAScript 6. A set is a collection of unique values, where each value can occur only once in the set. Sets are similar to arrays, but unlike arrays, they do not have indexes to access their elements. Instead, sets use methods to add, remove, and check if a value is present in the set. Here is an example of how sets can be implemented in JavaScript and TypeScript:
JavaScript Example:
// Creating a set in JavaScript
let colors = new Set();
// Adding values to the set
colors.add("red");
colors.add("green");
colors.add("blue");
// Checking if a value is in the set
console.log(colors.has("red")); // Output: true
console.log(colors.has("yellow")); // Output: false
// Removing a value from the set
colors.delete("green");
// Iterating over the set using forEach
colors.forEach(function(value) {
console.log(value);
});
In this example, we create a new set named colors
and add three string values to it. We can check if a value is in the set using the has()
method, remove a value using the delete()
method, and iterate over the set using the forEach()
method.
TypeScript Example:
// Creating a set in TypeScript
let numbers: Set<number> = new Set();
// Adding values to the set
numbers.add(1);
numbers.add(2);
numbers.add(3);
// Checking if a value is in the set
console.log(numbers.has(1)); // Output: true
console.log(numbers.has(4)); // Output: false
// Removing a value from the set
numbers.delete(2);
// Iterating over the set using forEach
numbers.forEach(function(value) {
console.log(value);
});
In this example, we create a new set named numbers
that contains three numeric values. We can add, remove, and check if a value is in the set using the same methods as in the JavaScript example. We can also use the forEach()
method to iterate over the set.
Sets are a useful data structure in JavaScript and TypeScript for storing collections of unique values. They can be used in many different contexts, such as filtering duplicates or creating unique lists of items.
Maps
Maps are another data structure in JavaScript and TypeScript that were introduced in ECMAScript 6. Maps are similar to objects in that they are collections of key-value pairs, but they provide additional functionality and flexibility. Maps allow keys of any type, not just strings, and they preserve the order of their elements. Maps also have built-in methods for adding, removing, and iterating over their elements. Here is an example of how maps can be implemented in JavaScript and TypeScript:
JavaScript Example:
// Creating a map in JavaScript
let ages = new Map();
// Adding values to the map
ages.set("Esther", 30);
ages.set("Jane", 25);
ages.set("Bob", 40);
// Checking if a key is in the map
console.log(ages.has("Esther")); // Output: true
console.log(ages.has("Mike")); // Output: false
// Removing a value from the map
ages.delete("Jane");
// Iterating over the map using for...of loop
for (let [key, value] of ages) {
console.log(key + " is " + value + " years old");
}
In this example, we create a new map named ages
and add three key-value pairs to it. We can check if a key is in the map using the has()
method, remove a key-value pair using the delete()
method, and iterate over the map using a for...of
loop.
TypeScript Example:
// Creating a map in TypeScript
let scores: Map<string, number> = new Map();
// Adding values to the map
scores.set("Esther", 80);
scores.set("Jane", 90);
scores.set("Bob", 70);
// Checking if a key is in the map
console.log(scores.has("Esther")); // Output: true
console.log(scores.has("Mike")); // Output: false
// Removing a value from the map
scores.delete("Jane");
// Iterating over the map using for...of loop
for (let [key, value] of scores) {
console.log(key + " scored " + value + " points");
}
In this example, we create a new map named scores
that contains three key-value pairs. We can add, remove, and check if a key is in the map using the same methods as in the JavaScript example. We can also use a for...of
loop to iterate over the map.
Maps are a powerful data structure in JavaScript and TypeScript that provide flexibility and built-in functionality for working with key-value pairs. They can be used in many different contexts, such as storing user preferences or caching data.
Stacks
Stacks are a linear data structure in JavaScript and TypeScript that operate on a "last in, first out" (LIFO) principle. This means that the last item added to the stack will be the first item removed. Stacks have two main operations: push
, which adds an item to the top of the stack, and pop
, which removes the top item from the stack. Other common operations include peek
, which allows you to look at the top item without removing it, and isEmpty
, which checks if the stack is empty. Here is an example of how stacks can be implemented in JavaScript and TypeScript:
JavaScript Example:
// Creating a stack in JavaScript
let myStack = [];
// Adding values to the stack
myStack.push("apple");
myStack.push("banana");
myStack.push("cherry");
// Removing the top item from the stack
let topItem = myStack.pop();
console.log(topItem); // Output: "cherry"
// Checking if the stack is empty
console.log(myStack.length === 0); // Output: false
// Looking at the top item without removing it
let nextItem = myStack[myStack.length - 1];
console.log(nextItem); // Output: "banana"
In this example, we create a new stack using an array and add three items to it using the push()
method. We then remove the top item from the stack using the pop()
method and check if the stack is empty using the length
property. Finally, we look at the top item without removing it using array indexing.
TypeScript Example:
// Creating a stack in TypeScript
let myStack: string[] = [];
// Adding values to the stack
myStack.push("apple");
myStack.push("banana");
myStack.push("cherry");
// Removing the top item from the stack
let topItem = myStack.pop();
console.log(topItem); // Output: "cherry"
// Checking if the stack is empty
console.log(myStack.length === 0); // Output: false
// Looking at the top item without removing it
let nextItem = myStack[myStack.length - 1];
console.log(nextItem); // Output: "banana"
In this example, we create a new stack using an array and add three items to it using the push()
method. We then remove the top item from the stack using the pop()
method and check if the stack is empty using the length
property. Finally, we look at the top item without removing it using array indexing.
Stacks are commonly used in programming to keep track of function calls, undo/redo functionality, and browser history. They are a simple and effective data structure for managing data in a LIFO manner.
Queues
Queues are a linear data structure in JavaScript and TypeScript that operate on a "first in, first out" (FIFO) principle. This means that the first item added to the queue will be the first item removed. Queues have two main operations: enqueue
, which adds an item to the back of the queue, and dequeue
, which removes the item from the front of the queue. Other common operations include peek
, which allows you to look at the front item without removing it, and isEmpty
, which checks if the queue is empty. Here is an example of how queues can be implemented in JavaScript and TypeScript:
JavaScript Example:
// Creating a queue in JavaScript
let myQueue = [];
// Adding values to the queue
myQueue.push("apple");
myQueue.push("banana");
myQueue.push("cherry");
// Removing the front item from the queue
let frontItem = myQueue.shift();
console.log(frontItem); // Output: "apple"
// Checking if the queue is empty
console.log(myQueue.length === 0); // Output: false
// Looking at the front item without removing it
let nextItem = myQueue[0];
console.log(nextItem); // Output: "banana"
In this example, we create a new queue using an array and add three items to it using the push()
method. We then remove the front item from the queue using the shift()
method and check if the queue is empty using the length
property. Finally, we look at the front item without removing it using array indexing.
TypeScript Example:
// Creating a queue in TypeScript
let myQueue: string[] = [];
// Adding values to the queue
myQueue.push("apple");
myQueue.push("banana");
myQueue.push("cherry");
// Removing the front item from the queue
let frontItem = myQueue.shift();
console.log(frontItem); // Output: "apple"
// Checking if the queue is empty
console.log(myQueue.length === 0); // Output: false
// Looking at the front item without removing it
let nextItem = myQueue[0];
console.log(nextItem); // Output: "banana"
In this example, we create a new queue using an array and add three items to it using the push()
method. We then remove the front item from the queue using the shift()
method and check if the queue is empty using the length
property. Finally, we look at the front item without removing it using array indexing.
Queues are commonly used in programming to manage processes or events that occur in sequential order. They can be used to implement algorithms like breadth-first search and to manage tasks in a message processing system. Queues are a simple and effective data structure for managing data in a FIFO manner.
Linked Lists
Linked lists are a fundamental data structure in computer science used to store a sequence of elements. Unlike arrays, which store data in contiguous blocks of memory, linked lists store data in individual nodes that point to the next node in the list. Each node contains a value and a pointer to the next node, forming a chain-like structure.
Linked lists are dynamic data structures that can be easily resized, and new elements can be added or removed from the list with minimal overhead. However, accessing elements in a linked list can be slower compared to arrays since you have to traverse the list from the beginning to access an element at a specific index.
Here is an example of a simple singly linked list implementation
JavaScript Example:
// Creating a linked list in JavaScript
class ListNode {
constructor(value, next = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
constructor() {
this.head = null;
this.size = 0;
}
// Adding a new node to the end of the linked list
addNode(value) {
const newNode = new ListNode(value);
if (!this.head) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.size++;
}
// Removing a node from the linked list
removeNode(value) {
let current = this.head;
let previous = null;
while (current) {
if (current.value === value) {
if (!previous) {
this.head = current.next;
} else {
previous.next = current.next;
}
this.size--;
return current.value;
}
previous = current;
current = current.next;
}
return null;
}
}
TypeScript Example:
// Creating a linked list in TypeScript
class ListNode {
value: any;
next: ListNode | null;
constructor(value: any, next: ListNode | null = null) {
this.value = value;
this.next = next;
}
}
class LinkedList {
head: ListNode | null;
size: number;
constructor() {
this.head = null;
this.size = 0;
}
// Adding a new node to the end of the linked list
addNode(value: any) {
const newNode = new ListNode(value);
if (!this.head) {
this.head = newNode;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = newNode;
}
this.size++;
}
// Removing a node from the linked list
removeNode(value: any): any {
let current = this.head;
let previous: ListNode | null = null;
while (current) {
if (current.value === value) {
if (!previous) {
this.head = current.next;
} else {
previous.next = current.next;
}
this.size--;
return current.value;
}
previous = current;
current = current.next;
}
return null;
}
}
In this example, we create a ListNode
class that contains a value and a reference to the next node in the list. We also create a LinkedList
class that contains a reference to the head node and the size of the list. The addNode
method adds a new node to the end of the list, while the removeNode
method removes a node with a given value from the list.
Linked lists are commonly used in various applications, such as implementing hash tables, stacks, and queues, and as an underlying data structure for graph algorithms. For instance, graph nodes can be represented as a linked list, where each node is connected to other nodes through its next
pointer.
Linked lists can also be used to implement other data structures. For example, a doubly linked list has a previous
pointer in addition to the next
pointer, allowing for easy traversal in both directions. Moreover, circular linked lists have a pointer from the last node to the first node, creating a circular structure.
Trees
Trees are a widely-used data structure in computer science that is used to represent hierarchical relationships between data. They consist of nodes connected by edges or links and have a single root node at the top that serves as the starting point for traversing the tree. Each node in a tree can have zero or more child nodes and is connected to its parent node by a single edge. Trees are used in a wide range of applications, including search algorithms, decision-making algorithms, and computer network routing algorithms.
JavaScript Example:
// Define the TreeNode class
class TreeNode {
constructor(value) {
this.value = value;
this.children = [];
}
// Add a child node to the current node
addChild(node) {
this.children.push(node);
}
}
// Create the root node with a value of 1
const root = new TreeNode(1);
// Create two child nodes with values of 2 and 3
const child1 = new TreeNode(2);
const child2 = new TreeNode(3);
// Add the child nodes to the root node
root.addChild(child1);
root.addChild(child2);
TypeScript Example:
// Define the TreeNode class
class TreeNode {
public value: number;
public children: TreeNode[];
constructor(value: number) {
this.value = value;
this.children = [];
}
// Add a child node to the current node
addChild(node: TreeNode) {
this.children.push(node);
}
}
// Create the root node with a value of 1
const root: TreeNode = new TreeNode(1);
// Create two child nodes with values of 2 and 3
const child1: TreeNode = new TreeNode(2);
const child2: TreeNode = new TreeNode(3);
// Add the child nodes to the root node
root.addChild(child1);
root.addChild(child2);
In these examples, we create a simple tree with three nodes, represented by the TreeNode
class. Each node has a value
property to store data and a children
property to store an array of child nodes. We start with the root node, which is created first, and add its child nodes by creating new TreeNode
objects and using the addChild
method to add them to the children
array of the parent node. This creates a hierarchical relationship between nodes that forms the tree structure.
Graphs
Graphs are a powerful data structure for modeling and analyzing relationships between objects. There are various ways to represent graphs in code, including using adjacency matrices and adjacency lists. An adjacency matrix represents the edges in a graph as a two-dimensional array, where the value at index [i][j] represents the weight of the edge between nodes i and j. An adjacency list, on the other hand, represents the edges in a graph as an array of linked lists, where each linked list corresponds to a node in the graph and contains the nodes that it is connected to.
Adjacency Matrix in JavaScript Example:
// Example adjacency matrix for a graph with 4 nodes
const graph = [
[0, 1, 0, 1],
[1, 0, 1, 1],
[0, 1, 0, 0],
[1, 1, 0, 0],
];
// The value at index [i][j] represents the weight of the edge between nodes i and j
// In this example, the graph is undirected and the weights are all 1
Adjacency Matrix in TypeScript Example:
// Example adjacency matrix for a graph with 4 nodes
const graph: number[][] = [
[0, 1, 0, 1],
[1, 0, 1, 1],
[0, 1, 0, 0],
[1, 1, 0, 0],
];
// The value at index [i][j] represents the weight of the edge between nodes i and j
// In this example, the graph is undirected and the weights are all 1
In this example, we're creating an undirected graph with 4 nodes and representing it as a 2D array. The value at index [i][j] represents the weight of the edge between nodes i and j. In this case, the graph is undirected, so the matrix is symmetric along the diagonal, and the weights are all 1.
Adjacency List in JavaScript Example:
// Example adjacency list for a graph with 4 nodes
const graph = [
[1, 3],
[0, 2, 3],
[1],
[0, 1],
];
// The array at index i contains the nodes that i is connected to
// In this example, the graph is undirected
Adjacency List in TypeScript Example:
typescriptCopy code// Example adjacency list for a graph with 4 nodes
const graph: number[][] = [
[1, 3],
[0, 2, 3],
[1],
[0, 1],
];
// The array at index i contains the nodes that i is connected to
// In this example, the graph is undirected
In this example, we're creating the same undirected graph with 4 nodes and representing it as an array of linked lists. The array at index i
contains the nodes that i is connected to. In this case, we're using zero-based indexing, so node 0 is connected to nodes 1 and 3, and so on.
In the code example above, we created a Graph class that implements an adjacency list representation of a graph using a Map object in TypeScript and a plain JavaScript object in JavaScript. We then defined several methods for adding vertices and edges to the graph, as well as for retrieving the neighbors of a given vertex.
Hash Tables
Hash tables are commonly implemented in JavaScript and TypeScript using objects. They are designed to quickly store and retrieve values based on a key. In hash tables, a key is hashed to a numerical index, which is used to store and retrieve the corresponding value.
Option 1
JavaScript example:
const myHashTable = {}; // creating an empty object to act as the hash table
// adding values to the hash table
myHashTable['apple'] = 1;
myHashTable['banana'] = 2;
myHashTable['cherry'] = 3;
// accessing values in the hash table
console.log(myHashTable['apple']); // output: 1
console.log(myHashTable['banana']); // output: 2
console.log(myHashTable['cherry']); // output: 3
TypeScript example:
In TypeScript, you can define a hash table interface using an index signature to specify the key and value types:
interface HashTable {
[key: string]: number;
}
const myHashTable: HashTable = {}; // creating an empty hash table
// adding values to the hash table
myHashTable['apple'] = 1;
myHashTable['banana'] = 2;
myHashTable['cherry'] = 3;
// accessing values in the hash table
console.log(myHashTable['apple']); // output: 1
console.log(myHashTable['banana']); // output: 2
console.log(myHashTable['cherry']); // output: 3
Note that in both JavaScript and TypeScript, keys can be of any type, not just strings, and values can be of any type as well. Also, in JavaScript, you can use the Map
class to implement hash tables, while in TypeScript, you can use the built-in Record
type.
Option 2
JavaScript Example:
// JavaScript example
function HashTable() {
this.data = {};
}
// Set a value to the hash table
HashTable.prototype.set = function (key, value) {
this.data[key] = value;
};
// Get a value from the hash table
HashTable.prototype.get = function (key) {
return this.data[key];
};
// Delete a value from the hash table
HashTable.prototype.delete = function (key) {
delete this.data[key];
};
// Check if the hash table has a specific key
HashTable.prototype.has = function (key) {
return this.data.hasOwnProperty(key);
};
// Clear the hash table
HashTable.prototype.clear = function () {
this.data = {};
};
// Get the size of the hash table
HashTable.prototype.size = function () {
return Object.keys(this.data).length;
};
TypeScript Example:
// TypeScript example
interface HashTable<T> {
[key: string]: T;
}
const hashTable: HashTable<number> = {};
// Set a value to the hash table
hashTable['key1'] = 1;
// Get a value from the hash table
const value = hashTable['key1'];
// Delete a value from the hash table
delete hashTable['key1'];
// Check if the hash table has a specific key
const hasKey = 'key1' in hashTable;
// Clear the hash table
for (const key in hashTable) {
delete hashTable[key];
}
// Get the size of the hash table
const size = Object.keys(hashTable).length;
In these examples, we define a hash table using either a function constructor in JavaScript or a class in TypeScript. The hash table has several methods, including set
, get
, delete
, has
, clear
, and size
, which allows us to add, retrieve, delete, check for the existence of, clear, and get the size of key-value pairs in the hash table, respectively.
Which option is better
Option 2 is a more structured way of working with hash tables than Option 1. In Option 2, a constructor function HashTable
is defined, which creates a hash table object with its own methods set
, get
, delete
, has
, clear
, and size
. This allows for a more organized and consistent approach to working with hash tables.
Option 1 is using the built-in object notation to define a hash table, but it lacks the structure and methods of a proper hash table. While it may be easier to understand at first glance, it does not have the functionality that Option 2 provides.
When deciding which approach to use, it depends on the needs of the project. If a simple key-value structure is needed and the values do not need to be accessed or manipulated in any special way, Option 1 may suffice. However, if more functionality is needed, such as the ability to delete or clear keys or get the size of the hash table, Option 2 is the better choice.
In general, it is best to use a more structured approach like Option 2, especially when working with larger and more complex data sets, as it allows for more organized and maintainable code.
Data structures are a critical component of any programming language, including JavaScript and TypeScript. They allow us to efficiently store, manipulate, and access data, making our code more readable, maintainable, and performant. By understanding the strengths and limitations of different data structures, we can choose the right one for the job and optimize our programs for speed, memory usage, and data integrity.
Whether you're building a simple web app or a complex enterprise system, having a solid grasp of data structures is essential for writing high-quality code that meets your requirements and exceeds your users' expectations. So keep exploring, experimenting, and learning new ways to organize your data, and see where your programming journey takes you!
Subscribe to my newsletter
Read articles from {{ MonaCodeLisa }} directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
{{ MonaCodeLisa }}
{{ MonaCodeLisa }}
Hello, I'm Esther White, I am an experienced FullStack Web Developer with a focus on Angular & NodeJS.