Overview › Code › R4
Code · archive round

R4 · Sorting race: bubble vs insertion vs quicksort

Archive round — this round predates the blind-first layout, so answers appear with model names attached. Newer rounds keep the models anonymous until you reveal them.
Scores on this page are the original editorial 0–10 ratings from the 2026-07-17 write-up (writing / art-direction / runtime quality) — not blind rubric scores against a documented gold answer, which is how R31 onward are scored. The underlying answers are unchanged from that write-up.
editorial scores · 2026-07-17 5/7 models answered
Show task
Three sorting algorithms — bubble, insertion, quick — racing side by side on the SAME shuffled 60-bar array. Animated, auto-starting, auto-restarting, live comparison counts, highlighted comparisons. NO user input.

The answers

🥇Claude Sonnet 5FRONTIEROK★ 9.2/10
RUNS ✓quick DONE @488 cmp while bubble grinds at 1404

Tells the algorithmic story perfectly: finish states, pivot highlights, glowing done-panels. Textbook.

🥈GPT-5.5 · CodexCLIOK★ 8.3/10
RUNS ✓steady progress, clean panels

Redemption after R3 — correct, brisk, minimal. Lacks finish-state drama.

🥉Claude Opus 4.8FRONTIEROK★ 8.0/10
RUNS ✓generator-based stepping

Elegant generator architecture, slightly leisurely pacing on screen.

4Claude Fable 5HOUSEOK★ 7.5/10
RUNS ✓horizontal lanes, value-hue bars

The only distinct layout of the round — but the hue-coded bars read noisy next to Sonnet's clarity.

5qwen3:14bLOCALISSUES★ 2.0/10
headers render, 0 comparisons, no bars

Valid syntax, dead runtime — the shell renders, nothing sorts.

6qwen3-coder:30bLOCALCRASH★ 1.5/10
SyntaxError — script never parses

17.8 KB of ambition, one parse error, zero bars.

7qwen3.5:9bLOCALCRASH★ 1.5/10
SyntaxError — script never parses

Same story, 22 KB heavier.

Frontier reference — outside the field

Answered the identical prompt outside the field, deliberately unscored. Collected through a context-free agent rather than the harness the other answers used, so response times are not comparable and are not shown.
Claude Opus 5frontier reference
Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sorting Race — Bubble vs Insertion vs Quick</title>
<style>
  :root{
    --bg:#0b0f17;
    --panel:#121826;
    --edge:#1f2937;
    --text:#e5e7eb;
    --muted:#94a3b8;
    --bar:#3b82f6;
    --cmp:#f59e0b;
    --swap:#ef4444;
    --done:#22c55e;
    --pivot:#a855f7;
  }
  *{box-sizing:border-box}
  html,body{height:100%}
  body{
    margin:0;
    background:radial-gradient(1200px 700px at 50% -10%,#16203a 0%,var(--bg) 60%);
    color:var(--text);
    font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    display:flex;flex-direction:column;min-height:100%;
  }
  header{
    padding:18px 20px 6px;text-align:center;
  }
  h1{
    margin:0;font-size:clamp(18px,2.4vw,26px);letter-spacing:.14em;text-transform:uppercase;font-weight:700;
  }
  .sub{margin:6px 0 0;color:var(--muted);font-size:12px;letter-spacing:.08em}
  main{
    flex:1;display:grid;gap:14px;padding:14px 16px 18px;
    grid-template-columns:repeat(3,1fr);
  }
  @media (max-width:900px){ main{grid-template-columns:1fr} }
  .lane{
    background:linear-gradient(180deg,#131a29,#0f141f);
    border:1px solid var(--edge);border-radius:14px;
    display:flex;flex-direction:column;overflow:hidden;
    box-shadow:0 10px 30px rgba(0,0,0,.35);
    min-height:260px;
  }
  .lane.win{border-color:var(--done);box-shadow:0 0 0 1px var(--done),0 10px 30px rgba(34,197,94,.18)}
  .lane-head{
    display:flex;align-items:baseline;justify-content:space-between;gap:10px;
    padding:10px 14px;border-bottom:1px solid var(--edge);background:rgba(255,255,255,.02);
  }
  .name{font-weight:700;letter-spacing:.12em;text-transform:uppercase;font-size:13px}
  .name .dot{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:8px;vertical-align:middle}
  .stats{display:flex;gap:14px;font-variant-numeric:tabular-nums;font-size:12px;color:var(--muted)}
  .stats b{color:var(--text);font-weight:600}
  .badge{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--done);opacity:0;transition:opacity .25s}
  .lane.done .badge{opacity:1}
  .bars{
    flex:1;display:flex;align-items:flex-end;gap:2px;padding:12px 12px 14px;
  }
  .bar{
    flex:1;background:var(--bar);border-radius:2px 2px 0 0;
    transition:height .06s linear,background-color .06s linear;
  }
  footer{
    padding:0 20px 16px;text-align:center;color:var(--muted);font-size:11px;letter-spacing:.06em;
  }
  #round{color:var(--text);font-variant-numeric:tabular-nums}
</style>
</head>
<body>
<header>
  <h1>Sorting Race</h1>
  <p class="sub">Same shuffled 60-bar array · bubble vs insertion vs quick</p>
</header>

<main id="track"></main>

<footer>Round <span id="round">1</span> · auto-restarting</footer>

<script>
(() => {
  const N = 60;
  const ALGOS = [
    { key:'bubble',    label:'Bubble Sort',    color:'#3b82f6' },
    { key:'insertion', label:'Insertion Sort', color:'#14b8a6' },
    { key:'quick',     label:'Quick Sort',     color:'#a855f7' }
  ];
  const CMP = '#f59e0b', SWAP = '#ef4444', DONE = '#22c55e', PIVOT = '#e879f9';
  const STEPS_PER_FRAME = 90;   // work per frame, shared by all lanes
  const RESTART_DELAY = 2200;

  const track = document.getElementById('track');
  const roundEl = document.getElementById('round');
  let round = 1, lanes = [], rafId = null, timerId = null;

  // ---- generators: each yields after every comparison / swap ----
  function* bubble(a){
    const n = a.length;
    for(let i=0;i<n-1;i++){
      let swapped = false;
      for(let j=0;j<n-1-i;j++){
        yield { cmp:[j,j+1] };
        if(a[j] > a[j+1]){
          [a[j],a[j+1]] = [a[j+1],a[j]];
          swapped = true;
          yield { swap:[j,j+1] };
        }
      }
      if(!swapped) return;
    }
  }

  function* insertion(a){
    const n = a.length;
    for(let i=1;i<n;i++){
      const v = a[i];
      let j = i-1;
      while(j >= 0){
        yield { cmp:[j,j+1] };
        if(a[j] <= v) break;
        a[j+1] = a[j];
        j--;
        yield { swap:[j+1, j+2 <= n-1 ? j+2 : j+1] };
      }
      a[j+1] = v;
    }
  }

  function* quick(a){
    function* qs(lo,hi){
      if(lo >= hi) return;
      const pivot = a[hi];
      let i = lo - 1;
      for(let j=lo;j<hi;j++){
        yield { cmp:[j,hi], pivot:hi };
        if(a[j] <= pivot){
          i++;
          if(i !== j){
            [a[i],a[j]] = [a[j],a[i]];
            yield { swap:[i,j], pivot:hi };
          }
        }
      }
      i++;
      if(i !== hi){
        [a[i],a[hi]] = [a[hi],a[i]];
        yield { swap:[i,hi], pivot:i };
      }
      yield* qs(lo, i-1);
      yield* qs(i+1, hi);
    }
    yield* qs(0, a.length-1);
  }

  const RUNNERS = { bubble, insertion, quick };

  // ---- lane construction ----
  function buildLane(algo){
    const el = document.createElement('section');
    el.className = 'lane';
    el.innerHTML = `
      <div class="lane-head">
        <span class="name"><span class="dot" style="background:${algo.color}"></span>${algo.label}</span>
        <span class="stats">
          <span>cmp <b class="c">0</b></span>
          <span>swap <b class="s">0</b></span>
          <span class="badge">done</span>
        </span>
      </div>
      <div class="bars"></div>`;
    track.appendChild(el);

    const barsEl = el.querySelector('.bars');
    const bars = [];
    for(let i=0;i<N;i++){
      const b = document.createElement('div');
      b.className = 'bar';
      barsEl.appendChild(b);
      bars.push(b);
    }
    return {
      algo, el, bars,
      cEl: el.querySelector('.c'),
      sEl: el.querySelector('.s'),
      data:[], gen:null, comps:0, swaps:0,
      finished:false, dirty:new Set(), marked:[]
    };
  }

  function shuffled(){
    const a = Array.from({length:N},(_,i)=>i+1);
    for(let i=a.length-1;i>0;i--){
      const j = Math.floor(Math.random()*(i+1));
      [a[i],a[j]] = [a[j],a[i]];
    }
    return a;
  }

  function paint(lane, i){
    const v = lane.data[i];
    lane.bars[i].style.height = (v / N * 100) + '%';
  }

  function resetLane(lane, base){
    lane.data = base.slice();
    lane.gen = RUNNERS[lane.algo.key](lane.data);
    lane.comps = 0; lane.swaps = 0;
    lane.finished = false;
    lane.marked = [];
    lane.el.classList.remove('done','win');
    lane.cEl.textContent = '0';
    lane.sEl.textContent = '0';
    for(let i=0;i<N;i++){
      paint(lane, i);
      lane.bars[i].style.backgroundColor = lane.algo.color;
    }
  }

  function clearMarks(lane){
    for(const i of lane.marked){
      if(i >= 0 && i < N) lane.bars[i].style.backgroundColor = lane.algo.color;
    }
    lane.marked = [];
  }

  function stepLane(lane, budget){
    if(lane.finished) return;
    clearMarks(lane);
    let last = null;
    for(let k=0;k<budget;k++){
      const r = lane.gen.next();
      if(r.done){
        lane.finished = true;
        for(let i=0;i<N;i++){
          paint(lane,i);
          lane.bars[i].style.backgroundColor = DONE;
        }
        lane.el.classList.add('done');
        lane.marked = [];
        return;
      }
      const st = r.value;
      if(st.cmp) lane.comps++;
      if(st.swap) lane.swaps++;
      last = st;
    }
    // repaint bars (cheap enough at N=60) and highlight the latest action
    for(let i=0;i<N;i++) paint(lane,i);
    lane.cEl.textContent = lane.comps.toLocaleString();
    lane.sEl.textContent = lane.swaps.toLocaleString();

    if(last){
      const idx = last.cmp || last.swap || [];
      const col = last.swap ? SWAP : CMP;
      for(const i of idx){
        if(i >= 0 && i < N){ lane.bars[i].style.backgroundColor = col; lane.marked.push(i); }
      }
      if(last.pivot !== undefined && last.pivot >= 0 && last.pivot < N){
        lane.bars[last.pivot].style.backgroundColor = PIVOT;
        lane.marked.push(last.pivot);
      }
    }
  }

  function tick(){
    let allDone = true;
    for(const lane of lanes){
      if(!lane.finished){ stepLane(lane, STEPS_PER_FRAME); allDone = false; }
    }
    if(allDone){
      rafId = null;
      // crown the fewest comparisons
      let best = lanes[0];
      for(const l of lanes) if(l.comps < best.comps) best = l;
      best.el.classList.add('win');
      timerId = setTimeout(newRound, RESTART_DELAY);
      return;
    }
    rafId = requestAnimationFrame(tick);
  }

  function newRound(){
    if(rafId) cancelAnimationFrame(rafId);
    if(timerId) clearTimeout(timerId);
    rafId = null; timerId = null;
    roundEl.textContent = round++;
    const base = shuffled();
    for(const lane of lanes) resetLane(lane, base);
    rafId = requestAnimationFrame(tick);
  }

  // ---- boot ----
  lanes = ALGOS.map(buildLane);
  newRound();
})();
</script>
</body>
</html>