Partition Explorer

A step-by-step visualisation of the partition operation at the heart of quicksort. Given an initial array and a pivot strategy, every comparison and swap is precomputed; cells glide across the partition boundary via shared layoutId so the eye tracks each value. Pick lomuto for the single-pointer canonical walk or hoare for the original two-pointer version.

3
6
1
8
2
7
4
5

Pivot: 5 (index 7). Move it to the end as a starting position.

Customize
Algorithm
last
Playback
600ms

Installation

npx shadcn@latest add https://craftbits.dev/r/partition-explorer.json

Usage

import { PartitionExplorer } from "@craft-bits/core";
 
<PartitionExplorer
  values={[3, 6, 1, 8, 2, 7, 4, 5]}
  pivot="last"
  scheme="lomuto"
/>

Drive playback from outside to sync with narration or a scrubber:

const [step, setStep] = useState(0);
 
<PartitionExplorer
  values={values}
  currentStep={step}
  onCurrentStepChange={setStep}
  playing
  playSpeed={400}
/>

Understanding the component

  1. Precomputed snapshots. When values, pivot, or scheme change, the walk is recomputed end-to-end inside useMemo. Each snapshot carries the array, the pivot identity, the current i / j pointer positions, and a human-readable caption.
  2. Stable cell identities. Every value is tagged with an id on mount and the same id follows it through every swap. Cells render with a shared layoutId, so a value that crosses the partition boundary glides to its new index instead of cross-fading.
  3. Two schemes. lomuto is the canonical single-pointer walk — i tracks the partition boundary, j scans every element. hoare is the two-pointer version — i and j march inward and swap any pair on the wrong side of the pivot.
  4. Pivot semantics. "first" | "middle" | "last" selects a position; a numeric pivot selects an explicit index (clamped). The pivot cell wears the accent fill across the entire walk.
  5. Reduced motion. The spring collapses to duration: 0, autoplay is suppressed, and the component snaps to the final snapshot — so the result is reachable without animation.

Props

PropTypeDefaultDescription
valuesreadonly number[]requiredInitial array to partition.
pivot"first" | "middle" | "last" | number"last"Pivot index strategy.
scheme"lomuto" | "hoare""lomuto"Partition algorithm to visualise.
currentStepnumberControlled active step. Pair with onCurrentStepChange.
defaultCurrentStepnumber0Uncontrolled initial step.
onCurrentStepChange(step: number) => voidFires whenever the active step changes.
playingbooleanControlled play state. Pair with onPlayingChange.
defaultPlayingbooleanfalseUncontrolled initial play state.
onPlayingChange(playing: boolean) => voidFires when play/pause flips.
playSpeednumber600Milliseconds between auto-played steps.
showCaptionbooleantrueRender the per-step description beneath the array.
classNamestringMerged onto the outer <div> via cn().

Accessibility

  • The root is role="group" with an aria-label naming the scheme and total step count.
  • Each cell carries data-cell-state (pivot / less / geq / default) and an aria-label describing its index, value, and partition side — three orthogonal signals (label, ring, fill) for colorblind users.
  • The caption sits in an aria-live="polite" region so screen-reader users hear the new step on every change.
  • Motion respects prefers-reduced-motion: transitions collapse to duration: 0, autoplay is suppressed, and the component jumps to the final snapshot on mount.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/PartitionExplorer.tsx). The original is a dual-array median-of-two-sorted-arrays primitive tied to AF's track-colour system; the library extract is a focused quicksort partition walker. Identity-preserving swaps, Lomuto vs Hoare, and the reduced-motion snap-to-final behaviour are new for craft-bits.