You Might Also Like
Sorting Algorithm Visualizer — Free HTML CSS JS Snippet
Sorting Algorithm Visualizer · Animations · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Sorting Algorithm Visualizer — Animated Bar Comparison for Bubble, Selection, Insertion, Quick & Merge Sort in Vanilla JS

Most sorting visualizations you find online hardcode the animation directly into the sorting function — a setTimeout or await sleep() call sits right inside the comparison loop, which means the algorithm's actual logic and its on-screen playback speed are permanently welded together. This snippet takes a different, more reusable approach: every algorithm is compiled into a flat array of step objects before a single frame is drawn. Each step is a small descriptive record like {type:'compare', i, j}, {type:'swap', i, j}, or {type:'mark-sorted', i}. The sorting functions themselves — bubbleSteps, selectionSteps, insertionSteps, quickSteps, mergeSteps — run to completion synchronously against a plain copy of the array, pushing a step for every comparison and mutation, with zero knowledge of timing, DOM elements, or animation at all.
Why decoupling steps from playback matters
Once the full step list exists, a single tick() function walks it on a setTimeout loop, applying one step per call and scheduling the next with a delay computed from the speed slider (Math.max(4, 220 - speed * 2)). This separation is the single most important idea in the file, and it is the same pattern reused across the pathfinding grid visualizer (BFS/A* frontier expansion) and the recursion tree visualizer (call-stack push/pop events) in this library — any traversal or search algorithm can be pre-computed into steps, then replayed at any speed, paused, or even scrubbed backward, because the algorithm never has to be re-run to change the pacing. It also makes the counters trivially accurate: compares and swaps increment inside applyStep() exactly once per comparison or swap step, so the displayed numbers always match what's rendered, not an approximation.
How each algorithm is translated into steps
Bubble, selection, and insertion sort push one compare step per inner-loop comparison and one swap step whenever two elements exchange positions — the visual choreography follows directly from the textbook pseudocode, which is why bubble sort visibly "bubbles" the largest remaining value to the right on every outer pass. Quick sort adds a pivot step (colored violet) before partitioning around a[hi], using the Lomuto partition scheme, then recurses on the two sub-ranges exactly like the real algorithm — so the animation shows partitioning happening independently within shrinking sub-arrays, which is the detail that makes quicksort's average O(n log n) behavior visually distinct from bubble sort's O(n²) crawl. Merge sort is the odd one out: it doesn't swap in place, it overwrites. The mergeSteps function recursively splits the array, merges two sorted halves into temporary left/right slices, and pushes an overwrite step ({type:'overwrite', i, value}) for every value written back — so on screen you see bars snapping to new heights rather than trading places, which is an honest visual representation of merge sort actually needing auxiliary space.
Bar rendering and color-coded state
Each bar is a plain <div> with its height set as a CSS percentage and a transition: height 0.18s ease — so even though JavaScript sets the height instantly, the browser's compositor animates the visual change smoothly without any requestAnimationFrame bookkeeping. Four classes carry meaning: .compare (amber, applied to the two indices currently being examined), .swap (red, applied briefly to bars that just exchanged values), .pivot (violet, quicksort's anchor element), and .sorted (green, permanently applied once an index's final position is confirmed). clearHighlights() strips the transient amber/red classes before applying a new compare step so only the current comparison is ever highlighted, keeping the visualization readable even at high speed.
Speed slider and the setTimeout scheduling formula
The speed slider ranges from 1 to 100 and feeds directly into the delay formula 220 - speed * 2, clamped to a 4ms floor so that even at maximum speed there's still one animation frame's worth of breathing room and the browser doesn't lock up mid-sort on a 500-element style array. Because tick() re-reads the slider's value on every single scheduled call rather than caching it once, dragging the slider mid-sort changes the pace immediately — there's no need to restart the sort or rebuild the step list, since the steps and the timing are fully independent by design.
Why array snapshots avoid a subtle bug
Every *Steps function operates on arr.slice(), a shallow copy, rather than the live array variable. This avoids a bug that's easy to introduce: if the step-generation functions mutated the same array object that renderBars() reads from, the array would already be fully sorted before the first frame ever plays, because step generation runs synchronously and instantly, while playback is deliberately slow. Working on a copy guarantees the visualization always starts from the original shuffled state and only reaches the sorted state after all steps have actually played out on screen.
Build with AI
Build, Understand, Optimize, and Extend It With AI
Paste this snippet's JavaScript into an AI assistant like Claude and ask it to trace exactly how quickSteps() implements the Lomuto partition scheme, or how mergeSteps() reconstructs the sorted range from two temporary slices — both are classic points of confusion. It's also worth asking the assistant to add heap sort or shell sort following the existing step-object contract, add a "step backward" button using the same pre-computed array, or add ARIA live-region announcements for each comparison so the visualizer is usable with a screen reader.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build an animated sorting algorithm visualizer in plain HTML, CSS, and JavaScript, no libraries or frameworks.
Requirements:
- Render an array of 30+ values as a row of divs whose height represents the value, with smooth CSS-transitioned height changes.
- Support at least Bubble Sort, Selection Sort, Insertion Sort, and Quick Sort, selectable via tab buttons that reset the array and rebuild the animation.
- Architect each algorithm to run to completion up front against a plain copy of the array, pushing a flat array of discrete step objects (e.g. {type:'compare', i, j} and {type:'swap', i, j}) — the algorithm itself must never call setTimeout or know about timing.
- Play back the pre-computed step array using a single timer-driven loop that applies one step per tick, so animation speed is fully decoupled from the sorting logic and can change mid-sort without restarting.
- Color-code bar state during playback: one color for the two elements currently being compared, another for elements that just swapped, and a third for elements confirmed in their final sorted position.
- Include a speed slider that changes the delay between steps in real time, a shuffle button that generates a new random array, and live counters for total comparisons and total swaps.
- Ensure Play/Pause can stop and resume cleanly at the exact step index, and that switching algorithms or shuffling always fully resets step index, counters, and bar colors.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Pick an algorithm from the tab rowChoose Bubble, Selection, Insertion, Quick, or Merge Sort. The bar array instantly resets to its unsorted shuffled state and the comparison/swap counters return to zero.
- 2Click Play to start the animationBars begin comparing in amber pairs and swapping with a red flash. Watch how differently each algorithm approaches the same starting array — bubble sort crawls, quick sort jumps around a pivot.
- 3Drag the speed slider at any timeThe delay between animation steps recalculates on every tick, so speed changes apply immediately mid-sort with no restart, letting you slow down to study a tricky partition or speed up to see the full pass.
- 4Watch bars lock in green as they finalizeOnce an index reaches its permanent sorted position it turns green and never changes color again. By the end every bar is green, confirming the sort is complete.
- 5Click Shuffle for a new random arrayGenerates a fresh set of 32 random bar heights and rebuilds the step list for whichever algorithm tab is currently active, resetting all counters to zero.
- 6Compare counters across algorithmsRun the same array size through Bubble Sort and then Quick Sort back to back and compare the final Comparisons and Swaps counts — the gap between O(n²) and O(n log n) becomes a concrete number instead of an abstract notation.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Yes. In React, move buildSteps/bubbleSteps/etc. into a utils file, generate the step array in a useMemo keyed on the array and algorithm, and drive playback with useEffect that sets a setInterval or recursive setTimeout — clear it in the cleanup function to avoid ticking after unmount. In Vue, call buildSteps() in a method and drive tick() from onMounted, clearing the timer in onUnmounted. In Angular, generate steps in ngOnInit and manage the timer in a service, clearing it in ngOnDestroy. In every framework, the critical rule is the same: whatever schedules tick() (setTimeout or setInterval) must be cleared on unmount/pause, or the timer keeps firing against DOM nodes that no longer exist.
Merge sort is not an in-place algorithm — it needs auxiliary arrays to merge two sorted halves, so this snippet models it with overwrite steps (bars snapping to a new height) rather than swap steps (two bars trading heights). This is intentional and accurate: showing merge sort as a series of swaps would misrepresent how the algorithm actually works and hide the O(n) auxiliary space it requires.
Write a new function following the same contract as the existing ones: accept a plain array copy and a shared list array, push {type:"compare", i, j} before every comparison and {type:"swap", i, j} (or {type:"overwrite", i, value} if not in-place) after every mutation, then push {type:"mark-sorted", i} for finalized indices. Add it to the algo dispatch in buildSteps() and a matching tab button with a data-algo attribute — the playback engine, counters, and color states all work automatically with no other changes.
requestAnimationFrame is ideal for continuous per-frame motion like dragging or physics, but this visualizer needs discrete, variable-length pauses between logically meaningful steps (one comparison, one swap) rather than a fixed 60fps cadence. setTimeout with a delay computed from the speed slider gives precise control over how long each step is visible, which matters more here than frame-perfect smoothness — and it makes the pause/resume logic trivial since clearTimeout instantly halts playback at an exact step boundary.
Yes — change the SIZE constant and randomArray() will generate that many bars. For arrays larger than roughly 80, reduce the CSS gap between .bar elements and consider raising the speed slider default, since O(n²) algorithms like bubble and insertion sort generate proportionally many more steps and can take a long time to finish at slow speeds with a larger n.