A web app that visualizes sorting algorithms in real time using animated bar charts. Watch a single algorithm step through its work with synced pseudocode, or race all nine side-by-side on the same input. Built with React 19, TypeScript, React Router 7, and Bootstrap 5.
| Route | View | What it does |
|---|---|---|
/single-algorithm-visualizer |
Single Algorithm Visualizer | Run one algorithm at a time with full playback controls (pause / resume / scrub / restart), a pseudocode panel synced to the current step, and swap / comparison tallies. |
/algorithm-comparison |
Algorithm Comparison | Run all nine algorithms on the same generated array in side-by-side mini-charts, with a master scrub + pause/resume/restart that drives every panel at once. |
The two screens cross-link to each other; / redirects to the single-algorithm view.
| Algorithm | Time complexity | Notes |
|---|---|---|
| Bubble Sort | O(n²) | Repeatedly swaps adjacent out-of-order elements |
| Selection Sort | O(n²) | Finds the minimum and places it at the front each pass |
| Insertion Sort | O(n²) | Builds a sorted prefix one element at a time |
| Merge Sort | O(n log n) | Divide-and-conquer; stable; merges in a buffer |
| Quick Sort | O(n log n) avg, O(n²) worst | Lomuto partition, recursive |
| Heap Sort | O(n log n) | Builds a max heap and repeatedly extracts the maximum |
| Shell Sort | ~O(n^1.5) | Insertion sort over halving gap sequence (n/2) |
| Tim Sort | O(n log n) | Hybrid: insertion-sort runs of 32, then merge |
| Intro Sort | O(n log n) | Hybrid: quicksort, falling back to heapsort (depth limit) and insertion sort (small ranges) |
The starting array can be generated as Random, Already sorted, Reversed, Nearly sorted (sorted with ~10% of values randomized), or Many duplicates (drawn from four distinct values). Values range from 10 to 1000.
- Adjustable size — 10–100 bars on the single view, 10–35 on the comparison view (nine panels share the area).
- Adjustable speed — 5–100 ms per step.
- Frame-accurate playback — pause, resume, restart, and scrub to any step; the single view also has Prev/Next step buttons.
- Animated swaps — bars slide to their new positions via CSS
transformtransitions (timed to the chosen speed) instead of jumping; honoursprefers-reduced-motion. - Synced pseudocode — the panel highlights the line each snapshot is executing (toggleable).
- Operation counters — live swap and comparison tallies.
- Keyboard shortcuts — see below.
- Bar value labels — optional numeric labels on each bar.
- Persisted preferences — form inputs are saved to
localStorageper view (sav:*for single,sac:*for comparison). - Inputs are disabled mid-run to prevent conflicts.
| Key | Single view | Comparison view |
|---|---|---|
Space |
Pause / Resume | Pause All / Resume All |
← / → |
Scrub by 1 step | Adjust master progress by 1% |
Shift + ← / → |
Scrub by 10 steps | Adjust master progress by 10% |
Esc |
Close the shortcuts modal | Close the shortcuts modal |
The info icon at the bottom of each sidebar opens a modal listing them.
npm install
npm start # dev server at http://localhost:3000
npm run build # production build
CI=true npm test -- --watchAll=false # run the test suite onceSorting and animation are fully decoupled.
-
Algorithms produce snapshots, not animations. Each algorithm is a pure function
(bars: Bar[]) => Step[]. It runs on a copy of the input and, after every observable mutation, pushes aStep— a deep copy of the array tagged with astate(comparing/swapping/sorted) and a 1-indexed pseudocodeline. Bars carry a stableidso React keys them to the same DOM node across snapshots (so swaps reconcile rather than remount) — which is what lets bars slide to their new positions with CSStransformtransitions rather than teleport. Algorithms live one-per-folder undersrc/algorithms/and each default-exports anAlgorithmEntry({ sort, label, description, pseudocode });src/algorithms/index.tsis the registry. -
The playback engine replays them.
usePlayback(src/hooks/use-playback.ts) walks theStep[]using one self-schedulingsetTimeoutat a time. Because only a single tick is ever in flight, pause is aclearTimeout, resume re-schedules from the current index, and scrub cancels and jumps to step N — all cheap. Each frame is emitted through anonFrame(bars)callback the view wires to its bar state.
Input patterns follow the same registry shape (PatternEntry per file in src/patterns/, resolved via resolvePattern).
src/
App.tsx Router shell (mounts <RouterProvider/>)
routes.tsx Route table (createBrowserRouter)
types.ts Shared types: Bar, Step, SortFunction, AlgorithmEntry, PatternEntry
utils.ts Shared helpers (clamp)
views/
single-algorithm-visualizer/ Single-algorithm screen (owns its form/playback state)
algorithms-comparison/ Side-by-side comparison screen
algorithms/
index.ts Registry (key → AlgorithmEntry) + resolveAlgorithm()
algorithm-utils.ts Snapshot helpers (snapshot, snapshotRange, finalSortedSnapshot)
sort-contract.ts Shared correctness test suite
<algo>/ One folder per algorithm: <algo>.ts (entry) + <algo>.test.ts
patterns/
index.ts Registry (key → PatternEntry) + resolvePattern()
<pattern>.ts One file per input pattern
patterns-util.ts randomValue / randomChoice
components/
view-layout/ Shared 15/85 shell (dark sidebar + visualization area)
*-sidebar/ Per-view sidebar contents (fully controlled)
bars-row/ Bar chart (pure)
mini-bars-box/ Captioned mini chart for a comparison panel
algorithm-panel/ One comparison panel (own usePlayback + imperative handle)
pseudocode-panel/ Pseudocode display synced to the current step
keyboard-shortcuts-modal/
number-of-bars-control/, delay-control/, input-pattern-control/, sidebar-divider/
hooks/
use-playback.ts Tick-based playback engine
use-bars-generator.ts bars / maxBarValue state + bar generation
use-local-storage.ts Persisted state
use-keyboard-shortcuts.ts Global key bindings
Each algorithm has a colocated *.test.ts that runs runSortCorrectnessSuite from src/algorithms/sort-contract.ts, exercising it against all five input patterns at sizes 10/25/100. Per case it checks that every snapshot is a length-preserving id-permutation of the input, the final snapshot matches the sorted multiset and is fully tagged sorted, and the input array is never mutated.
- React 19 + TypeScript
- React Router 7 (data-router mode)
- Bootstrap 5 + react-icons
- Create React App (react-scripts)

