All topics
General · Learning hub

Algorithms notes for developers

Master Algorithms with a curated set of 3 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore General notes
Algorithms

Sorting & Searching Algorithms

Sorting & Searching Algorithms Complexity Cheatsheet Algorithm Best Average Worst Space ───────────────────────────────────────────────────── Bubble Sort O(n) O

Sorting & Searching Algorithms

Complexity Cheatsheet

Algorithm        Best      Average   Worst     Space
─────────────────────────────────────────────────────
Bubble Sort      O(n)      O(n²)     O(n²)     O(1)
Insertion Sort   O(n)      O(n²)     O(n²)     O(1)
Selection Sort   O(n²)     O(n²)     O(n²)     O(1)
Merge Sort       O(n log n) O(n log n) O(n log n) O(n)
Quick Sort       O(n log n) O(n log n) O(n²)   O(log n)
Heap Sort        O(n log n) O(n log n) O(n log n) O(1)
Counting Sort    O(n+k)    O(n+k)    O(n+k)    O(k)
Radix Sort       O(nk)     O(nk)     O(nk)     O(n+k)
─────────────────────────────────────────────────────
Binary Search    O(1)      O(log n)  O(log n)  O(1)
Linear Search    O(1)      O(n)      O(n)      O(1)

Pick:
- General sort       → Quick Sort (in-place) or Merge Sort (stable)
- Nearly sorted      → Insertion Sort
- External sort      → Merge Sort
- Integer range      → Counting Sort / Radix Sort
- Priority queue     → Heap Sort

Key Implementations

// Binary Search — O(log n)
function binarySearch(arr: number[], target: number): number {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = (lo + hi) >> 1;
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return -1;
}

// Merge Sort — O(n log n), stable
function mergeSort(arr: number[]): number[] {
  if (arr.length <= 1) return arr;
  const mid = arr.length >> 1;
  const left = mergeSort(arr.slice(0, mid));
  const right = mergeSort(arr.slice(mid));
  return merge(left, right);
}
function merge(a: number[], b: number[]): number[] {
  const result: number[] = [];
  let i = 0, j = 0;
  while (i < a.length && j < b.length) {
    result.push(a[i] <= b[j] ? a[i++] : b[j++]);
  }
  return [...result, ...a.slice(i), ...b.slice(j)];
}

// Quick Sort — O(n log n) avg, in-place
function quickSort(arr: number[], lo = 0, hi = arr.length - 1): void {
  if (lo >= hi) return;
  const p = partition(arr, lo, hi);
  quickSort(arr, lo, p - 1);
  quickSort(arr, p + 1, hi);
}
function partition(arr: number[], lo: number, hi: number): number {
  const pivot = arr[hi];
  let i = lo - 1;
  for (let j = lo; j < hi; j++) {
    if (arr[j] <= pivot) { i++; [arr[i], arr[j]] = [arr[j], arr[i]]; }
  }
  [arr[i + 1], arr[hi]] = [arr[hi], arr[i + 1]];
  return i + 1;
}

Dynamic Programming Patterns

// DP template: define state, recurrence, base case, order

// 1D DP — Fibonacci / Climbing Stairs
function climbStairs(n: number): number {
  if (n <= 2) return n;
  let prev = 1, curr = 2;
  for (let i = 3; i <= n; i++) [prev, curr] = [curr, prev + curr];
  return curr;
}

// Coin Change — min coins to make amount
function coinChange(coins: number[], amount: number): number {
  const dp = Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (let i = 1; i <= amount; i++) {
    for (const c of coins) {
      if (c <= i) dp[i] = Math.min(dp[i], dp[i - c] + 1);
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

// Longest Common Subsequence (2D DP)
function lcs(s1: string, s2: string): number {
  const m = s1.length, n = s2.length;
  const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      dp[i][j] = s1[i-1] === s2[j-1]
        ? dp[i-1][j-1] + 1
        : Math.max(dp[i-1][j], dp[i][j-1]);
    }
  }
  return dp[m][n];
}

// 0/1 Knapsack
function knapsack(weights: number[], values: number[], cap: number): number {
  const n = weights.length;
  const dp = Array(cap + 1).fill(0);
  for (let i = 0; i < n; i++) {
    for (let w = cap; w >= weights[i]; w--) {  // iterate backwards
      dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
    }
  }
  return dp[cap];
}
Algorithms

Sorting Algorithms

Sorting Algorithms Core sorting algorithms with JavaScript implementations, time/space complexity, and when to use each one. Complexity Cheatsheet Algorithm Bes

Sorting Algorithms

Core sorting algorithms with JavaScript implementations, time/space complexity, and when to use each one.

Complexity Cheatsheet

Algorithm        Best        Average     Worst       Space   Stable
────────────────────────────────────────────────────────────────────
Bubble Sort      O(n)        O(n²)       O(n²)       O(1)    Yes
Insertion Sort   O(n)        O(n²)       O(n²)       O(1)    Yes
Selection Sort   O(n²)       O(n²)       O(n²)       O(1)    No
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
Heap Sort        O(n log n)  O(n log n)  O(n log n)  O(1)    No
Counting Sort    O(n+k)      O(n+k)      O(n+k)      O(k)    Yes
Radix Sort       O(nk)       O(nk)       O(nk)       O(n+k)  Yes

Pick:
- General purpose      -> Quick Sort (fast average case, in-place)
- Need stable sort     -> Merge Sort
- Nearly sorted input  -> Insertion Sort
- Small integer range  -> Counting Sort or Radix Sort
- Memory constrained   -> Heap Sort (O(1) space)

Bubble Sort & Insertion Sort

// Bubble Sort — O(n²) avg, O(n) best (already sorted)
// Repeatedly swap adjacent elements if out of order
function bubbleSort(arr: number[]): number[] {
  const a = [...arr];
  const n = a.length;
  for (let i = 0; i < n - 1; i++) {
    let swapped = false;
    for (let j = 0; j < n - i - 1; j++) {
      if (a[j] > a[j + 1]) {
        [a[j], a[j + 1]] = [a[j + 1], a[j]];
        swapped = true;
      }
    }
    if (!swapped) break; // already sorted — early exit
  }
  return a;
}

// Insertion Sort — O(n²) avg, O(n) best
// Great for small arrays or nearly-sorted data
function insertionSort(arr: number[]): number[] {
  const a = [...arr];
  for (let i = 1; i < a.length; i++) {
    const key = a[i];
    let j = i - 1;
    while (j >= 0 && a[j] > key) {
      a[j + 1] = a[j];
      j--;
    }
    a[j + 1] = key;
  }
  return a;
}

console.log(bubbleSort([5, 3, 1, 4, 2]));    // [1, 2, 3, 4, 5]
console.log(insertionSort([5, 3, 1, 4, 2])); // [1, 2, 3, 4, 5]

Merge Sort

// Merge Sort — O(n log n) all cases, O(n) space, stable
// Divide-and-conquer: split in half, sort each half, merge
function mergeSort(arr: number[]): number[] {
  if (arr.length <= 1) return arr;
  const mid = Math.floor(arr.length / 2);
  const left = mergeSort(arr.slice(0, mid));
  const right = mergeSort(arr.slice(mid));
  return merge(left, right);
}

function merge(left: number[], right: number[]): number[] {
  const result: number[] = [];
  let i = 0, j = 0;
  while (i < left.length && j < right.length) {
    if (left[i] <= right[j]) {
      result.push(left[i++]);
    } else {
      result.push(right[j++]);
    }
  }
  // Append remaining elements
  return [...result, ...left.slice(i), ...right.slice(j)];
}

// Generic merge sort (works with any comparable type)
function mergeSortGeneric<T>(arr: T[], compareFn: (a: T, b: T) => number): T[] {
  if (arr.length <= 1) return arr;
  const mid = Math.floor(arr.length / 2);
  const left = mergeSortGeneric(arr.slice(0, mid), compareFn);
  const right = mergeSortGeneric(arr.slice(mid), compareFn);
  const result: T[] = [];
  let i = 0, j = 0;
  while (i < left.length && j < right.length) {
    result.push(compareFn(left[i], right[j]) <= 0 ? left[i++] : right[j++]);
  }
  return [...result, ...left.slice(i), ...right.slice(j)];
}

// Sort objects by a property
const users = [{name: 'Bob', age: 30}, {name: 'Alice', age: 25}];
mergeSortGeneric(users, (a, b) => a.age - b.age);
// [{name: 'Alice', age: 25}, {name: 'Bob', age: 30}]

Quick Sort

// Quick Sort — O(n log n) avg, O(n²) worst (bad pivot), O(log n) space
// In-place: choose pivot, partition around it, recurse on both sides
function quickSort(arr: number[], lo = 0, hi = arr.length - 1): number[] {
  if (lo < hi) {
    const pivotIdx = partition(arr, lo, hi);
    quickSort(arr, lo, pivotIdx - 1);
    quickSort(arr, pivotIdx + 1, hi);
  }
  return arr;
}

// Lomuto partition scheme — pivot is last element
function partition(arr: number[], lo: number, hi: number): number {
  const pivot = arr[hi];
  let i = lo - 1;  // index of smaller element
  for (let j = lo; j < hi; j++) {
    if (arr[j] <= pivot) {
      i++;
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
  }
  [arr[i + 1], arr[hi]] = [arr[hi], arr[i + 1]];
  return i + 1;
}

// Median-of-three pivot selection (avoids worst case on sorted input)
function medianOfThree(arr: number[], lo: number, hi: number): number {
  const mid = Math.floor((lo + hi) / 2);
  if (arr[lo] > arr[mid]) [arr[lo], arr[mid]] = [arr[mid], arr[lo]];
  if (arr[lo] > arr[hi]) [arr[lo], arr[hi]] = [arr[hi], arr[lo]];
  if (arr[mid] > arr[hi]) [arr[mid], arr[hi]] = [arr[hi], arr[mid]];
  return mid; // arr[mid] is now the median
}

const arr = [3, 6, 8, 10, 1, 2, 1];
quickSort(arr);  // modifies in-place: [1, 1, 2, 3, 6, 8, 10]
Algorithms

Searching & Graph Algorithms

Searching & Graph Algorithms Binary search, BFS, DFS, and dynamic programming — the core algorithms that appear in technical interviews and real-world systems.

Searching & Graph Algorithms

Binary search, BFS, DFS, and dynamic programming — the core algorithms that appear in technical interviews and real-world systems.

Binary Search

// Binary Search — O(log n) time, O(1) space
// Requires sorted array. Halve the search space each iteration.
function binarySearch(arr: number[], target: number): number {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = lo + Math.floor((hi - lo) / 2); // avoids integer overflow
    if (arr[mid] === target) return mid;
    if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return -1; // not found
}

// Find first occurrence (leftmost) of target
function binarySearchFirst(arr: number[], target: number): number {
  let lo = 0, hi = arr.length - 1, result = -1;
  while (lo <= hi) {
    const mid = lo + Math.floor((hi - lo) / 2);
    if (arr[mid] === target) { result = mid; hi = mid - 1; } // keep searching left
    else if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return result;
}

// Search in rotated sorted array (e.g. [4,5,6,7,0,1,2])
function searchRotated(arr: number[], target: number): number {
  let lo = 0, hi = arr.length - 1;
  while (lo <= hi) {
    const mid = lo + Math.floor((hi - lo) / 2);
    if (arr[mid] === target) return mid;
    // left half is sorted
    if (arr[lo] <= arr[mid]) {
      if (arr[lo] <= target && target < arr[mid]) hi = mid - 1;
      else lo = mid + 1;
    } else { // right half is sorted
      if (arr[mid] < target && target <= arr[hi]) lo = mid + 1;
      else hi = mid - 1;
    }
  }
  return -1;
}

binarySearch([1, 3, 5, 7, 9, 11], 7);  // 3

BFS & DFS

type Graph = Record<string, string[]>;

const graph: Graph = {
  A: ['B', 'C'],
  B: ['D', 'E'],
  C: ['F'],
  D: [], E: [], F: [],
};

// BFS — Breadth-First Search (level by level, uses queue)
// Use for: shortest path in unweighted graph, level-order traversal
function bfs(graph: Graph, start: string): string[] {
  const visited = new Set<string>();
  const queue: string[] = [start];
  const order: string[] = [];
  visited.add(start);
  while (queue.length > 0) {
    const node = queue.shift()!;
    order.push(node);
    for (const neighbor of graph[node] ?? []) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push(neighbor);
      }
    }
  }
  return order;
}

// DFS — Depth-First Search (go deep first, uses stack or recursion)
// Use for: cycle detection, topological sort, connected components
function dfs(graph: Graph, start: string, visited = new Set<string>()): string[] {
  visited.add(start);
  const order: string[] = [start];
  for (const neighbor of graph[start] ?? []) {
    if (!visited.has(neighbor)) {
      order.push(...dfs(graph, neighbor, visited));
    }
  }
  return order;
}

// Shortest path with BFS
function shortestPath(graph: Graph, start: string, end: string): string[] | null {
  const queue: string[][] = [[start]];
  const visited = new Set<string>([start]);
  while (queue.length > 0) {
    const path = queue.shift()!;
    const node = path[path.length - 1];
    if (node === end) return path;
    for (const neighbor of graph[node] ?? []) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push([...path, neighbor]);
      }
    }
  }
  return null;
}

bfs(graph, 'A');  // ['A', 'B', 'C', 'D', 'E', 'F']
dfs(graph, 'A');  // ['A', 'B', 'D', 'E', 'C', 'F']

Dynamic Programming

// DP pattern: define state, recurrence, base case, fill order

// 1. Fibonacci (space-optimized)
function fib(n: number): number {
  if (n <= 1) return n;
  let prev = 0, curr = 1;
  for (let i = 2; i <= n; i++) [prev, curr] = [curr, prev + curr];
  return curr;
}

// 2. Coin Change — minimum coins to reach amount — O(n * coins)
function coinChange(coins: number[], amount: number): number {
  const dp = Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (let i = 1; i <= amount; i++) {
    for (const coin of coins) {
      if (coin <= i) dp[i] = Math.min(dp[i], dp[i - coin] + 1);
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}
coinChange([1, 5, 10, 25], 36); // 3 (25 + 10 + 1)

// 3. Longest Common Subsequence (2D DP) — O(m*n)
function lcs(s1: string, s2: string): number {
  const m = s1.length, n = s2.length;
  const dp = Array.from({length: m + 1}, () => Array(n + 1).fill(0));
  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      dp[i][j] = s1[i - 1] === s2[j - 1]
        ? dp[i - 1][j - 1] + 1
        : Math.max(dp[i - 1][j], dp[i][j - 1]);
    }
  }
  return dp[m][n];
}
lcs('ABCBDAB', 'BDCAB'); // 4 (BCAB)

// 4. 0/1 Knapsack — O(n * capacity)
function knapsack(weights: number[], values: number[], capacity: number): number {
  const dp = Array(capacity + 1).fill(0);
  for (let i = 0; i < weights.length; i++) {
    for (let w = capacity; w >= weights[i]; w--) { // iterate backwards!
      dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
    }
  }
  return dp[capacity];
}
knapsack([2, 3, 4, 5], [3, 4, 5, 6], 8); // 10

Keep your Algorithms knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever