All topics
General · Learning hub

Data Structures notes for developers

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

Save this stack to your DevRecallMore General notes
Data Structures

Arrays, Strings & Hash Maps

Arrays, Strings & Hash Maps Complexity Quick Reference Data Structure | Access | Search | Insert | Delete ------------------|--------|--------|--------|-------

Arrays, Strings & Hash Maps

Complexity Quick Reference

Data Structure    | Access | Search | Insert | Delete
------------------|--------|--------|--------|-------
Array             |  O(1)  |  O(n)  |  O(n)  |  O(n)
Dynamic Array     |  O(1)  |  O(n)  |  O(1)* |  O(n)
Linked List       |  O(n)  |  O(n)  |  O(1)  |  O(1)
Hash Map/Set      |   -    |  O(1)* |  O(1)* |  O(1)*
Binary Search Tree|  O(h)  |  O(h)  |  O(h)  |  O(h)
Balanced BST      | O(log) | O(log) | O(log) | O(log)
Heap (max/min)    |   -    |  O(n)  | O(log) | O(log)
Stack             |   -    |  O(n)  |  O(1)  |  O(1)
Queue             |   -    |  O(n)  |  O(1)  |  O(1)

* amortized or average case

Arrays — Common Patterns

// Two Pointers — O(n)
function twoSum(nums: number[], target: number): [number, number] | null {
  let left = 0, right = nums.length - 1;
  while (left < right) {
    const sum = nums[left] + nums[right];
    if (sum === target) return [left, right];
    else if (sum < target) left++;
    else right--;
  }
  return null;
}

// Sliding Window — O(n)
function maxSumSubarray(nums: number[], k: number): number {
  let windowSum = nums.slice(0, k).reduce((a, b) => a + b, 0);
  let maxSum = windowSum;
  for (let i = k; i < nums.length; i++) {
    windowSum += nums[i] - nums[i - k];
    maxSum = Math.max(maxSum, windowSum);
  }
  return maxSum;
}

// Prefix Sum — O(n) precompute, O(1) range query
function buildPrefix(nums: number[]): number[] {
  const prefix = [0];
  for (const n of nums) prefix.push(prefix[prefix.length - 1] + n);
  return prefix;
}
function rangeSum(prefix: number[], left: number, right: number): number {
  return prefix[right + 1] - prefix[left];
}

// Kadane's Algorithm — max subarray sum O(n)
function maxSubarraySum(nums: number[]): number {
  let maxSum = nums[0], currentSum = nums[0];
  for (let i = 1; i < nums.length; i++) {
    currentSum = Math.max(nums[i], currentSum + nums[i]);
    maxSum = Math.max(maxSum, currentSum);
  }
  return maxSum;
}

Hash Map Patterns

// Frequency counter
function isAnagram(s: string, t: string): boolean {
  if (s.length !== t.length) return false;
  const freq = new Map<string, number>();
  for (const c of s) freq.set(c, (freq.get(c) ?? 0) + 1);
  for (const c of t) {
    const count = freq.get(c);
    if (!count) return false;
    freq.set(c, count - 1);
  }
  return true;
}

// Two-sum using hash map — O(n) time, O(n) space
function twoSumUnsorted(nums: number[], target: number): [number, number] | null {
  const seen = new Map<number, number>();  // value -> index
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (seen.has(complement)) return [seen.get(complement)!, i];
    seen.set(nums[i], i);
  }
  return null;
}

// Group anagrams
function groupAnagrams(strs: string[]): string[][] {
  const groups = new Map<string, string[]>();
  for (const s of strs) {
    const key = s.split('').sort().join('');
    const group = groups.get(key) ?? [];
    group.push(s);
    groups.set(key, group);
  }
  return [...groups.values()];
}

Linked List

class ListNode {
  constructor(public val: number, public next: ListNode | null = null) {}
}

// Reverse linked list — O(n) time, O(1) space
function reverseList(head: ListNode | null): ListNode | null {
  let prev: ListNode | null = null;
  let curr = head;
  while (curr) {
    const next = curr.next;
    curr.next = prev;
    prev = curr;
    curr = next;
  }
  return prev;
}

// Detect cycle — Floyd's algorithm
function hasCycle(head: ListNode | null): boolean {
  let slow = head, fast = head;
  while (fast && fast.next) {
    slow = slow!.next;
    fast = fast.next.next;
    if (slow === fast) return true;
  }
  return false;
}

// Find middle node (fast/slow pointers)
function findMiddle(head: ListNode): ListNode {
  let slow = head, fast = head;
  while (fast.next && fast.next.next) {
    slow = slow.next!;
    fast = fast.next.next;
  }
  return slow;  // slow is at middle
}
Data Structures

Trees, Graphs & Sorting

Trees, Graphs & Sorting Binary Trees — BFS & DFS class TreeNode { constructor( public val: number, public left: TreeNode | null = null, public right: TreeNode |

Trees, Graphs & Sorting

Binary Trees — BFS & DFS

class TreeNode {
  constructor(
    public val: number,
    public left: TreeNode | null = null,
    public right: TreeNode | null = null
  ) {}
}

// DFS — Inorder (left, root, right) → sorted for BST
function inorder(root: TreeNode | null): number[] {
  if (!root) return [];
  return [...inorder(root.left), root.val, ...inorder(root.right)];
}

// Iterative inorder
function inorderIterative(root: TreeNode | null): number[] {
  const result: number[] = [];
  const stack: TreeNode[] = [];
  let curr: TreeNode | null = root;
  while (curr || stack.length) {
    while (curr) { stack.push(curr); curr = curr.left; }
    curr = stack.pop()!;
    result.push(curr.val);
    curr = curr.right;
  }
  return result;
}

// BFS — Level order traversal
function levelOrder(root: TreeNode | null): number[][] {
  if (!root) return [];
  const result: number[][] = [];
  const queue: TreeNode[] = [root];
  while (queue.length) {
    const level: number[] = [];
    const size = queue.length;
    for (let i = 0; i < size; i++) {
      const node = queue.shift()!;
      level.push(node.val);
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    result.push(level);
  }
  return result;
}

// Max depth
function maxDepth(root: TreeNode | null): number {
  if (!root) return 0;
  return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

Graph Traversal

type Graph = Map<number, number[]>;

// BFS — shortest path in unweighted graph
function bfs(graph: Graph, start: number, target: number): number {
  const visited = new Set([start]);
  const queue: [number, number][] = [[start, 0]];  // [node, distance]
  while (queue.length) {
    const [node, dist] = queue.shift()!;
    if (node === target) return dist;
    for (const neighbor of graph.get(node) ?? []) {
      if (!visited.has(neighbor)) {
        visited.add(neighbor);
        queue.push([neighbor, dist + 1]);
      }
    }
  }
  return -1;
}

// DFS — detect cycle, connected components
function dfs(graph: Graph, node: number, visited: Set<number>): void {
  visited.add(node);
  for (const neighbor of graph.get(node) ?? []) {
    if (!visited.has(neighbor)) dfs(graph, neighbor, visited);
  }
}

function countComponents(n: number, edges: [number, number][]): number {
  const graph: Graph = new Map();
  for (let i = 0; i < n; i++) graph.set(i, []);
  for (const [u, v] of edges) {
    graph.get(u)!.push(v);
    graph.get(v)!.push(u);
  }
  const visited = new Set<number>();
  let count = 0;
  for (let i = 0; i < n; i++) {
    if (!visited.has(i)) { dfs(graph, i, visited); count++; }
  }
  return count;
}

Sorting Algorithms

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

// Quick Sort — O(n log n) average, O(n²) worst, in-place
function quickSort(arr: number[], lo = 0, hi = arr.length - 1): void {
  if (lo < hi) {
    const pivot = partition(arr, lo, hi);
    quickSort(arr, lo, pivot - 1);
    quickSort(arr, pivot + 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;
}

// 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 + Math.floor((hi - lo) / 2);
    if (arr[mid] === target) return mid;
    else if (arr[mid] < target) lo = mid + 1;
    else hi = mid - 1;
  }
  return -1;
}

Stack & Queue

// Valid parentheses — stack pattern
function isValid(s: string): boolean {
  const stack: string[] = [];
  const pairs: Record<string, string> = { ')': '(', ']': '[', '}': '{' };
  for (const c of s) {
    if ('([{'.includes(c)) stack.push(c);
    else if (stack.pop() !== pairs[c]) return false;
  }
  return stack.length === 0;
}

// Monotonic stack — next greater element
function nextGreaterElement(nums: number[]): number[] {
  const result = new Array(nums.length).fill(-1);
  const stack: number[] = [];  // indices
  for (let i = 0; i < nums.length; i++) {
    while (stack.length && nums[stack[stack.length - 1]] < nums[i]) {
      result[stack.pop()!] = nums[i];
    }
    stack.push(i);
  }
  return result;
}

// Min Stack (O(1) getMin)
class MinStack {
  private stack: number[] = [];
  private minStack: number[] = [];
  push(val: number) {
    this.stack.push(val);
    this.minStack.push(Math.min(val, this.minStack[this.minStack.length - 1] ?? val));
  }
  pop() { this.stack.pop(); this.minStack.pop(); }
  top() { return this.stack[this.stack.length - 1]; }
  getMin() { return this.minStack[this.minStack.length - 1]; }
}

Keep your Data Structures 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