Understanding Algorithms
A visual guide based on the book by Aditya Bhargava.
Bubble Sort
Classic comparison-based sorting algorithm
Bubble Sort compares adjacent pairs and swaps those out of order. After each pass, the largest unsorted element 'bubbles' to its correct position at the end of the array.
💡 Each pass guarantees at least one more element is in its final position.
Try: change arr[j] > arr[j+1] to < to sort in descending order.
Open in editor →Cocktail Sort
Bidirectional variant of Bubble Sort
Cocktail Sort (Shaker Sort) is a Bubble Sort that alternates direction each pass: left→right, then right→left. This moves both large and small elements toward their positions faster.
💡 Bidirectional scanning eliminates the 'turtle' problem — small elements near the end that Bubble Sort drags slowly.
Try: add a swap counter to compare against Bubble Sort on the same input.
Open in editor →Selection Sort
Grokking Algorithms — Ch. 2: Selection Sort
Selection Sort finds the minimum in the unsorted portion and moves it to the front. Repeats until sorted. Uses very few swaps — just n-1 — but many comparisons.
💡 Minimizes memory writes — useful when writes are expensive (e.g., flash storage).
Try: swap arr[j] < arr[minIndex] with > to sort in descending order.
Open in editor →Heap Sort
Based on data structure: Max-Heap (binary tree)
Heap Sort builds a Max-Heap from the array — a binary tree where each node is larger than its children. Then repeatedly extracts the maximum, building the sorted array from back to front.
💡 The binary tree lives implicitly in the array: left child of i is at 2i+1, right child at 2i+2.
Try: modify heapify to use < instead of > and observe what happens.
Open in editor →Bucket Sort
Distribution algorithm — divide and conquer
Bucket Sort distributes elements into buckets based on value, sorts each bucket individually, then concatenates. Works exceptionally well when values are uniformly distributed.
💡 Instead of comparing elements, the algorithm uses the value directly to decide where each element goes.
Try: change bucketCount to 3 or 10 and watch how bucket sizes change.
Open in editor →Radix Sort
Linear algorithm — sorts without direct comparisons
Radix Sort processes numbers digit by digit, from least significant (units) to most significant (thousands). Uses a stable internal sort (Counting Sort) on each pass.
💡 Never directly compares two elements — just counts and redistributes by digit. This breaks the O(n log n) lower bound of comparison-based algorithms.
Try: use an array with 3-digit numbers to watch all 3 passes of the algorithm.
Open in editor →Stalin Sort
Humorous internet algorithm — O(n)
Stalin Sort traverses the array once, keeping only elements greater than or equal to the running maximum. The rest are 'eliminated'. Always produces a sorted array in O(n).
💡 Technically correct, but destroys data. Useful for understanding 'running maximum' and contrasting with Selection Sort.
Try: add logic to reject negative elements instead of non-increasing ones.
Open in editor →Breadth-First Search (BFS)
Grokking Algorithms — Ch. 6: Breadth-First Search
BFS explores a graph level by level. Starting from the source node, it visits all neighbors, then their neighbors. Uses a queue (FIFO) to control exploration order.
💡 BFS guarantees the shortest path in unweighted graphs — finds the solution with the fewest edges.
Try: add edge weights and see why BFS no longer guarantees the optimal path.
Open in editor →Depth-First Search (DFS)
Based on Grokking Algorithms Ch. 6 — depth-first variant
DFS explores as deep as possible before backtracking. Uses a stack (LIFO) — explicit or via recursion. Great for cycle detection, topological sort, and connected components.
💡 DFS doesn't guarantee shortest path, but uses less memory than BFS on wide, shallow graphs.
Try: count connected components by running DFS repeatedly from unvisited nodes.
Open in editor →How to use the editor
Write your run() function, call emitStep() for each step, then click Run. The visualizer animates every event automatically.
async function run(input) {
const arr = [...input];
// Emita o estado inicial
emitStep({ t: "array", arr: [...arr] });
for (let i = 0; i < arr.length - 1; i++) {
// Emita cada comparação
emitStep({ t: "compare", arr: [...arr], i, j: i + 1 });
if (arr[i] > arr[i + 1]) {
[arr[i], arr[i + 1]] = [arr[i + 1], arr[i]];
// Emita cada troca
emitStep({ t: "swap", arr: [...arr], i, j: i + 1 });
}
}
// Emita o estado final
emitStep({ t: "done", arr: [...arr] });
return arr;
}