DSA with JavaScript
Complete Course Notes
Code Step By Step • 58 Videos • Hindi
Table of Contents
1. Introduction to Data Structures
2. Types of Data Structures
3. JavaScript Fundamentals for DSA
4. Algorithm Complexity — Time & Space
5. Recursion
6. Arrays
7. Searching Algorithms
8. Sorting Algorithms
9. Stack
10. Queue
11. Linked List
12. Trees & Binary Search Tree
13. Graph
14. Hash Table
1. Introduction to Data Structures
What is a Data Structure?
A Data Structure is a way of organizing, storing, and managing data in a computer so it can be accessed
and modified efficiently — like a container that holds data in a specific format.
What is an Algorithm?
An Algorithm is a step-by-step set of instructions to solve a specific problem. It takes input, processes it,
and produces output. A recipe is a real-world algorithm for cooking.
Why Learn DSA?
• Foundation of every software system (GPS, search engines, games, databases)
• Essential for cracking coding interviews (FAANG, top startups)
• Makes code efficient in both time and memory usage
• Helps solve complex real-world problems elegantly
2. Types of Data Structures
Linear Data Structures
Elements arranged sequentially, one after another.
Structure Key Feature
Array Fixed size, O(1) index access
Stack LIFO — Last In, First Out
Queue FIFO — First In, First Out
Linked List Dynamic size, node-based pointers
Non-Linear Data Structures
Elements arranged hierarchically or networked.
Structure Key Feature
Tree Hierarchical, parent-child relationships
Graph Nodes connected by edges (network model)
Hash Table Key-value pairs, average O(1) lookup
3. JavaScript Fundamentals for DSA
Variables, Functions & Loops
// Variables
let num = 10; let str = "hello"; let arr = [1, 2, 3];
// Arrow function
const multiply = (a, b) => a * b;
// For loop
for (let i = 0; i < 5; i++) { [Link](i); }
// While loop
let i = 0;
while (i < 5) { [Link](i++); }
Arrays & Objects
let arr = [10, 20, 30];
[Link](40); // add to end
[Link](); // remove from end
[Link](5); // add to beginning
[Link](); // remove from beginning
let person = { name: "Ali", age: 25 };
[Link] = function() { [Link]("Hello, " + [Link]); };
Classes (Used to implement Data Structures)
class Node {
constructor(data) {
[Link] = data;
[Link] = null;
}
}
4. Algorithm Complexity — Time & Space
Big-O Notation
Big-O describes the worst-case performance of an algorithm as input size n grows.
Big-O Name Example
O(1) Constant Array index access
O(log n) Logarithmic Binary Search
O(n) Linear Linear Search
O(n log n) Linearithmic Merge Sort
O(n²) Quadratic Bubble Sort (nested loops)
O(2■) Exponential Recursive Fibonacci
Time Complexity Examples
// O(1) — constant: always 1 step
function getFirst(arr) { return arr[0]; }
// O(n) — linear: n steps
function printAll(arr) {
for (let i = 0; i < [Link]; i++) [Link](arr[i]);
}
// O(n^2) — quadratic: n x n steps
function printPairs(arr) {
for (let i = 0; i < [Link]; i++)
for (let j = 0; j < [Link]; j++)
[Link](arr[i], arr[j]);
}
Space Complexity
// O(1) space — fixed extra memory regardless of input
function sum(arr) {
let total = 0;
for (let x of arr) total += x;
return total;
}
// O(n) space — creates new array proportional to input
function double(arr) {
let result = [];
for (let x of arr) [Link](x * 2);
return result;
}
Rules for Calculating Big-O
• Drop constants: O(2n) becomes O(n)
• Drop non-dominant terms: O(n² + n) becomes O(n²)
• Different inputs = different variables: O(a + b) when processing two separate arrays
5. Recursion
A function that calls itself until a base condition is met. Each call is pushed onto the call stack; when the
base case is hit, calls unwind.
Factorial
function factorial(n) {
if (n === 0) return 1; // base case
return n * factorial(n - 1); // recursive case
}
// factorial(5) = 5 x 4 x 3 x 2 x 1 = 120
Fibonacci
function fibonacci(n) {
if (n <= 1) return n; // base cases: fib(0)=0, fib(1)=1
return fibonacci(n-1) + fibonacci(n-2);
}
// fibonacci(6) = 8
■ Note: Naive Fibonacci is O(2^n) — very slow. Use memoization for large inputs.
Recursion vs Iteration
Feature Recursion Iteration
Code style Cleaner, shorter Longer but explicit
Memory Uses call stack (more) Usually less memory
Speed Can be slower Generally faster
Best for Trees, graphs, divide & conquer Simple sequential loops
6. Arrays
An ordered collection of elements stored at contiguous memory locations, accessible via index starting
from 0.
Operations & Complexity
Operation Method Time Complexity
Access arr[i] O(1)
Search Loop O(n)
Insert at end push() O(1)
Remove from end pop() O(1)
Insert at start unshift() O(n)
Remove from start shift() O(n)
Insert at middle splice() O(n)
Common Array Methods
let arr = [3, 1, 4, 1, 5, 9, 2];
[Link](4); // 2 — index of value
[Link](9); // true
[Link](); // reverses in place
[Link]((a, b) => a-b); // sort ascending
[Link](1, 3); // [1, 4] — non-mutating
[Link](2, 1, 99); // remove 1 at index 2, insert 99
[Link](x => x * 2); // new array, each element doubled
[Link](x => x > 3); // new array, elements > 3
[Link]((acc,x) => acc+x, 0); // sum all elements
7. Searching Algorithms
7.1 Linear Search
Check each element one by one from start to end.
function linearSearch(arr, target) {
for (let i = 0; i < [Link]; i++) {
if (arr[i] === target) return i; // found -> return index
}
return -1; // not found
}
linearSearch([10, 25, 3, 7, 40], 7); // returns 3
• Time: O(n) | Space: O(1)
• Works on unsorted AND sorted arrays
• Best for small datasets
7.2 Binary Search
Repeatedly halve the search space by comparing with the middle element.
■ Note: Requires a SORTED array!
function binarySearch(arr, target) {
let left = 0, right = [Link] - 1;
while (left <= right) {
let mid = [Link]((left + right) / 2);
if (arr[mid] === target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
binarySearch([2,5,8,12,16,23,38], 23); // returns 5
Comparison
Feature Linear Search Binary Search
Sorted array needed? No Yes
Time Complexity O(n) O(log n)
For n = 1,000,000 ~1M steps ~20 steps
8. Sorting Algorithms
8.1 Bubble Sort
Repeatedly swap adjacent elements if in wrong order. Largest elements bubble up.
function bubbleSort(arr) {
let n = [Link];
for (let i = 0; i < n - 1; i++) {
for (let j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j+1])
[arr[j], arr[j+1]] = [arr[j+1], arr[j]];
}
}
return arr;
}
8.2 Selection Sort
Find the minimum element in unsorted portion, place it at the front.
function selectionSort(arr) {
let n = [Link];
for (let i = 0; i < n - 1; i++) {
let minIdx = i;
for (let j = i + 1; j < n; j++)
if (arr[j] < arr[minIdx]) minIdx = j;
[arr[i], arr[minIdx]] = [arr[minIdx], arr[i]];
}
return arr;
}
8.3 Insertion Sort
Build sorted array one element at a time by inserting each into its correct position.
function insertionSort(arr) {
for (let i = 1; i < [Link]; i++) {
let key = arr[i], j = i - 1;
while (j >= 0 && arr[j] > key) { arr[j+1] = arr[j]; j--; }
arr[j+1] = key;
}
return arr;
}
8.4 Merge Sort
Divide into halves, sort each recursively, then merge.
function mergeSort(arr) {
if ([Link] <= 1) return arr;
let mid = [Link]([Link] / 2);
let left = mergeSort([Link](0, mid));
let right = mergeSort([Link](mid));
return merge(left, right);
}
function merge(left, right) {
let result = []; let i = 0, j = 0;
while (i < [Link] && j < [Link])
[Link](left[i] <= right[j] ? left[i++] : right[j++]);
return [Link]([Link](i)).concat([Link](j));
}
8.5 Quick Sort
Choose a pivot, partition smaller left / larger right, sort both sides recursively.
function quickSort(arr, low=0, high=[Link]-1) {
if (low < high) {
let p = partition(arr, low, high);
quickSort(arr, low, p - 1);
quickSort(arr, p + 1, high);
}
return arr;
}
function partition(arr, low, high) {
let pivot = arr[high], i = low - 1;
for (let j = low; j < high; j++)
if (arr[j] <= pivot) { i++; [arr[i],arr[j]]=[arr[j],arr[i]]; }
[arr[i+1],arr[high]]=[arr[high],arr[i+1]];
return i + 1;
}
Sorting Summary Table
Algorithm Best Average Worst Space Stable
Bubble Sort O(n) O(n²) O(n²) O(1) Yes
Selection Sort O(n²) O(n²) O(n²) O(1) No
Insertion Sort O(n) O(n²) O(n²) O(1) Yes
Merge Sort O(n log n) O(n log n) O(n log n) O(n) Yes
Quick Sort O(n log n) O(n log n) O(n²) O(log n) No
9. Stack
A linear data structure following LIFO — Last In, First Out. Like a stack of plates: add on top, remove from
top.
Operations
Operation Description Time
push(x) Add element to top O(1)
pop() Remove & return top O(1)
peek() View top (no removal) O(1)
isEmpty() Check if empty O(1)
Implementation
class Stack {
constructor() { [Link] = []; }
push(el) { [Link](el); }
pop() { return [Link]() ? "Underflow" : [Link](); }
peek() { return [Link][[Link] - 1]; }
isEmpty() { return [Link] === 0; }
size() { return [Link]; }
}
Real-World Applications
• Undo/Redo in text editors (Ctrl+Z)
• Browser Back button (history stack)
• Function Call Stack in JavaScript
• Balanced parentheses checking
• Expression evaluation (infix → postfix)
Example: Balanced Parentheses
function isBalanced(str) {
let stack = [], map = { ')':'(', '}':'{', ']':'[' };
for (let c of str) {
if ('({['.includes(c)) [Link](c);
else if ([Link]() !== map[c]) return false;
}
return [Link] === 0;
}
isBalanced("{[()]}"); // true
isBalanced("{[(])}"); // false
10. Queue
A linear data structure following FIFO — First In, First Out. Like a line at a ticket counter: first person in,
first served.
Operations
Operation Description Time
enqueue(x) Add element to rear O(1)
dequeue() Remove & return front O(1)
front() View front element O(1)
isEmpty() Check if empty O(1)
Implementation
class Queue {
constructor() { [Link] = []; }
enqueue(el) { [Link](el); }
dequeue() { return [Link]() ? "Underflow" : [Link](); }
front() { return [Link][0]; }
isEmpty() { return [Link] === 0; }
size() { return [Link]; }
}
Real-World Applications
• CPU process scheduling
• Printer job queue
• BFS graph traversal
• Call center systems
• Keyboard input buffer
11. Linked List
A dynamic linear data structure where elements (nodes) are stored at non-contiguous memory
locations, connected via pointers. Each node holds data and a reference to the next node.
Array vs Linked List
Feature Array Linked List
Size Fixed (static) Dynamic
Memory Contiguous Non-contiguous
Access O(1) — by index O(n) — traverse
Insert/Delete start O(n) O(1)
Insert/Delete end O(1) O(n) / O(1) with tail
Full Singly Linked List Implementation
class Node {
constructor(data) { [Link] = data; [Link] = null; }
}
class LinkedList {
constructor() { [Link] = null; [Link] = 0; }
append(data) { // Add to end — O(n)
let node = new Node(data);
if (![Link]) { [Link] = node; }
else {
let cur = [Link];
while ([Link]) cur = [Link];
[Link] = node;
}
[Link]++;
}
prepend(data) { // Add to beginning — O(1)
let node = new Node(data);
[Link] = [Link]; [Link] = node; [Link]++;
}
delete(data) { // Delete by value — O(n)
if (![Link]) return;
if ([Link] === data) { [Link] = [Link]; return; }
let cur = [Link];
while ([Link]) {
if ([Link] === data) { [Link] = [Link]; return; }
cur = [Link];
}
}
search(data) { // Search — O(n)
let cur = [Link], i = 0;
while (cur) { if ([Link] === data) return i; cur = [Link]; i++; }
return -1;
}
traverse() { // Print all nodes
let cur = [Link], out = [];
while (cur) { [Link]([Link]); cur = [Link]; }
[Link]([Link](" -> "));
}
}
Types of Linked Lists
• Singly: each node points to next only → [10|•] → [20|•] → [null]
• Doubly: each node has prev AND next pointers → bidirectional traversal
• Circular: last node points back to head → forms a loop
12. Trees & Binary Search Tree
A non-linear hierarchical data structure with nodes connected by edges. A Binary Tree has at most 2
children per node. A BST adds the ordering property: left values < root < right values.
Key Terminology
Term Meaning
Root Top node — no parent
Leaf Node with no children
Height Longest path from root to leaf
Depth Distance from root to a node
Inorder Left → Root → Right (gives sorted output for BST)
Preorder Root → Left → Right (good for copying)
Postorder Left → Right → Root (good for deleting)
BST Implementation
class TreeNode { constructor(d) { [Link]=d; [Link]=[Link]=null; } }
class BST {
constructor() { [Link] = null; }
insert(data) {
let node = new TreeNode(data);
if (![Link]) { [Link] = node; return; }
let cur = [Link];
while (true) {
if (data < [Link]) {
if (![Link]) { [Link] = node; return; } else cur = [Link];
} else {
if (![Link]) { [Link] = node; return; } else cur = [Link];
}
}
}
search(data) {
let cur = [Link];
while (cur) {
if (data === [Link]) return true;
cur = data < [Link] ? [Link] : [Link];
}
return false;
}
}
All 4 Traversals
// Inorder — Left, Root, Right
function inorder(node) { if(!node)return; inorder([Link]); [Link]([Link]); inorder([Link]
// Preorder — Root, Left, Right
function preorder(node) { if(!node)return; [Link]([Link]); preorder([Link]); preorder(node.
// Postorder — Left, Right, Root
function postorder(node) { if(!node)return; postorder([Link]); postorder([Link]); [Link](no
// Level Order (BFS) — uses Queue
function levelOrder(root) {
let q = [root];
while ([Link]) {
let node = [Link]();
[Link]([Link]);
if ([Link]) [Link]([Link]);
if ([Link]) [Link]([Link]);
}
}
BST Complexity
Operation Average (Balanced) Worst (Skewed)
Search O(log n) O(n)
Insert O(log n) O(n)
Delete O(log n) O(n)
13. Graph
A non-linear data structure of vertices (nodes) and edges (connections). Models networks: social
graphs, road maps, web pages, etc.
Types of Graphs
• Undirected: A—B means both directions
• Directed (Digraph): A→B does NOT imply B→A
• Weighted: edges carry a cost/distance value
• Connected: every vertex reachable from every other
Graph Representation — Adjacency List
class Graph {
constructor() { [Link] = {}; }
addVertex(v) { if (![Link][v]) [Link][v] = []; }
addEdge(v1, v2) { // undirected
[Link][v1].push(v2);
[Link][v2].push(v1);
}
removeEdge(v1, v2) {
[Link][v1] = [Link][v1].filter(v => v !== v2);
[Link][v2] = [Link][v2].filter(v => v !== v1);
}
}
DFS — Depth First Search
dfs(start) {
let visited = {}, result = [];
const explore = (vertex) => {
visited[vertex] = true; [Link](vertex);
[Link][vertex].forEach(nb => { if (!visited[nb]) explore(nb); });
};
explore(start); return result;
}
BFS — Breadth First Search
bfs(start) {
let queue = [start], visited = { [start]: true }, result = [];
while ([Link]) {
let vertex = [Link](); [Link](vertex);
[Link][vertex].forEach(nb => {
if (!visited[nb]) { visited[nb] = true; [Link](nb); }
});
}
return result;
}
DFS vs BFS
Feature DFS BFS
Data Structure Stack / Recursion Queue
Shortest Path Not guaranteed Yes (unweighted graphs)
Space O(h) — tree height O(w) — max width
Use Case Cycle detection, topological sort Shortest path, level traversal
14. Hash Table
Stores key-value pairs and uses a hash function to map keys to array indices, enabling average O(1)
insert, search, and delete.
Hash Table Implementation
class HashTable {
constructor(size = 53) { [Link] = new Array(size); }
_hash(key) {
let total = 0, PRIME = 31;
for (let i = 0; i < [Link]([Link], 100); i++)
total = (total * PRIME + [Link](i) - 96) % [Link];
return total;
}
set(key, value) {
let idx = this._hash(key);
if (![Link][idx]) [Link][idx] = [];
for (let pair of [Link][idx])
if (pair[0] === key) { pair[1] = value; return; }
[Link][idx].push([key, value]);
}
get(key) {
let idx = this._hash(key);
if (![Link][idx]) return undefined;
for (let pair of [Link][idx]) if (pair[0] === key) return pair[1];
}
delete(key) {
let idx = this._hash(key);
if (![Link][idx]) return false;
[Link][idx] = [Link][idx].filter(p => p[0] !== key);
return true;
}
}
JavaScript Built-in Hash Structures
// Map — best for frequent add/delete, any key type
let map = new Map();
[Link]("name", "Ali"); [Link]("name"); // "Ali"
[Link]("name"); // true
[Link]("name"); [Link]; // 0
// Set — stores only unique values
let set = new Set([1, 2, 3, 2, 1]);
[Link]([...set]); // [1, 2, 3]
[Link](4); [Link](2); // true
Collision Handling
• Separate Chaining: each index holds an array/linked list of key-value pairs
• Open Addressing (Linear Probing): if slot is occupied, try the next slot
Complexity
Operation Average Worst (many collisions)
Insert O(1) O(n)
Search O(1) O(n)
Delete O(1) O(n)
Master Complexity Reference
Structure Access Search Insert Delete Space
Array O(1) O(n) O(n) O(n) O(n)
Stack O(n) O(n) O(1) O(1) O(n)
Queue O(n) O(n) O(1) O(1) O(n)
Linked List O(n) O(n) O(1) O(1) O(n)
BST (avg) O(log n) O(log n) O(log n) O(log n) O(n)
Hash Table N/A O(1) O(1) O(1) O(n)
Graph O(V+E) O(V+E) O(1) O(E) O(V+E)
Key Takeaways
• Choose the right data structure — it makes all the difference in performance
• Always analyze complexity: time AND space before committing to an approach
• Recursion is powerful — always define a clear base case to avoid infinite loops
• Arrays for random access; Linked Lists for dynamic inserts/deletes
• Stacks for LIFO problems (undo, call stack); Queues for FIFO (scheduling, BFS)
• BSTs give O(log n) average operations when balanced
• Graphs model real-world networks; choose DFS or BFS based on the problem
• Hash Tables give O(1) average key-value lookups — incredibly powerful
Notes compiled from: DSA with JavaScript in Hindi — Code Step By Step (58 videos)