Sorting Algorithm Visualizer — Free HTML CSS JS Snippet

Sorting Algorithm Visualizer · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Five algorithms: Bubble, Selection, Insertion, Quick (Lomuto partition), and Merge Sort
Steps pre-computed as a flat array of {type, i, j} records before any animation plays
setTimeout-driven playback loop fully decoupled from sorting logic — speed changes apply mid-sort instantly
Live comparison and swap counters incremented inside a single applyStep() function for accuracy
Color-coded bar states: amber compare, red swap, violet pivot, green sorted
Merge sort uses overwrite steps instead of swaps, visually distinguishing it from in-place algorithms
Speed slider maps 1-100 to a clamped millisecond delay with a 4ms floor to prevent UI lockup
Play/Pause toggle and Shuffle button both fully reset or resume animation state cleanly

About this UI Snippet

Sorting Algorithm Visualizer — Animated Bar Comparison for Bubble, Selection, Insertion, Quick & Merge Sort in Vanilla JS

Screenshot of the Sorting Algorithm Visualizer snippet rendered live

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:

text
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

  1. 1
    Pick 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.
  2. 2
    Click 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.
  3. 3
    Drag 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.
  4. 4
    Watch 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.
  5. 5
    Click 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.
  6. 6
    Compare 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

Teaching algorithm complexity in a classroom or bootcamp
Run the same shuffled array through Bubble Sort and Quick Sort back to back so students see O(n²) versus O(n log n) as a real comparison-count difference, not just Big-O notation on a whiteboard. Pair with the binary search visualizer to build a full "searching and sorting" lesson module.
Technical interview prep and algorithm self-study
Step through Quick Sort at low speed to trace exactly how the Lomuto partition scheme moves the pivot, which is one of the most commonly whiteboarded interview questions. Watching the pivot (violet) and partition boundary evolve step-by-step builds intuition that reading pseudocode alone rarely gives.
Portfolio and CS-education website interactive demo
Drop this into a computer-science blog post or portfolio piece as a live, embedded demonstration rather than a static GIF. The self-contained HTML/CSS/JS means it runs in any iframe or UI snippets gallery without a build step.
Documentation for a sorting library or coding course platform
Course platforms like the ones teaching data structures benefit from a visual companion to code samples. Reuse the step-array architecture to add your own algorithms (heap sort, shell sort) by writing one more *Steps function that pushes compare/swap records.
Reference implementation for step-based animation architecture
Because the algorithm logic and the playback timer are fully separate, this is a good reference for any UI that needs "compute everything, then replay it at a controllable pace" — the same pattern used by the recursion tree visualizer for call-stack events and the pathfinding grid visualizer for search frontiers.

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.