DEV Community

Cover image for Building a Yes No Wheel: Why Streaks Look Rigged (and 3 Other Gotchas)
Devid E
Devid E

Posted on

Building a Yes No Wheel: Why Streaks Look Rigged (and 3 Other Gotchas)

I added a Yes or No page to my NameWheelPro project. It's vanilla JS with zero dependencies, so every fix below works in any project.

If you're building your own yes no wheel, here are four gotchas I'd want to know about up front.

  1. Streaks are normal, but they look like bugs

Spin a fair 50/50 wheel and sooner or later you'll see Yes, Yes, Yes, Yes, Yes. Users will assume it's rigged. Don't "fix" it by preventing repeats, because that makes the tool less random. Prove it instead:

function longestStreak(results) {
  let best = 1, run = 1;
  for (let i = 1; i < results.length; i++) {
    run = results[i] === results[i - 1] ? run + 1 : 1;
    best = Math.max(best, run);
  }
  return best;
}

const spins = Array.from({ length: 100 }, () => (randomInt(2) ? 'Yes' : 'No'));
console.log(longestStreak(spins)); // usually somewhere between 5 and 8
Enter fullscreen mode Exit fullscreen mode

Run it a few times. Streaks of five or more show up in most 100-spin sessions, and that's what real randomness looks like.

  1. Holding the spacebar spins repeatedly

A keyboard shortcut is easy until someone holds the key down and the browser fires keydown over and over. Ignore repeats and keep a guard for double triggers:

document.addEventListener('keydown', (e) => {
  if (e.code !== 'Space' || e.repeat) return;        // ignore a held-down key
  if (['INPUT', 'TEXTAREA', 'SELECT'].includes(document.activeElement.tagName)) return;
  e.preventDefault();                                // no page scroll
  if (!state.spinning) spin();                       // second trigger becomes a no-op
});
Enter fullscreen mode Exit fullscreen mode
  1. Show a tally so fairness is visible

A History list is good, but a running count is better because it shows the balance at a glance:

function tally(history) {
  return history.reduce((t, h) => {
    const key = h.result.trim().toLowerCase();       // users can edit entries, so normalize
    t[key] = (t[key] || 0) + 1;
    return t;
  }, {});
}

tally(history); // { yes: 12, no: 9 }
Enter fullscreen mode Exit fullscreen mode

A 12-to-9 split looks suspicious until you know that's normal variance. Showing both the streaks and the tally sets the right expectation.

  1. "Clear All" needs an undo

The default wheel is preloaded, so one accidental click on Clear All wipes it. A snapshot and a toast fix that in a few lines:

let undoSnapshot = null;

function clearAll() {
  undoSnapshot = [...state.entries];
  setState({ entries: [] });
  showToast('Wheel cleared', 'Undo', () => setState({ entries: undoSnapshot }));
}
Enter fullscreen mode Exit fullscreen mode

Destructive buttons should always have a way back.

Wrapping up

None of these is hard, but together they make a tiny tool feel trustworthy: honest about randomness, safe with keyboard input, transparent with results, and forgiving of mistakes.

You can try the live yes no wheel here, with no signup. What's a tiny UX detail that made one of your projects feel finished?

Top comments (0)