A trivia quiz looks like the simplest app imaginable: show a question, show four options, check the answer. But the details decide whether it feels fair — and whether a room full of students (or friends at a party) quickly figures out that "the answer is usually C."
Trivia Questions and Answers is a free collection of trivia quizzes by category, used for everything from game nights to classroom review games for middle schoolers and teens. Here's what goes into a quiz engine that holds up.
1. Shuffle correctly (Fisher–Yates)
The classic mistake is array.sort(() => Math.random() - 0.5). It produces a biased shuffle — some orders appear far more often than others. Use Fisher–Yates:
function shuffle(arr, rng = Math.random) {
const a = [...arr];
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
Shuffle both the question order and each question's options.
2. Balance where the correct answer lands
Even with an unbiased shuffle, a 10-question round can randomly put the right answer in slot B five times. Players notice. You can balance positions across a round so each slot gets roughly equal use:
function balancedPositions(nQuestions, nOptions, rng) {
const slots = [];
while (slots.length < nQuestions) {
slots.push(...shuffle([...Array(nOptions).keys()], rng));
}
return slots.slice(0, nQuestions);
}
function placeAnswer(q, slot, rng) {
const wrong = shuffle(q.options.filter((o) => o !== q.answer), rng);
wrong.splice(slot, 0, q.answer);
return { ...q, options: wrong };
}
3. Watch out for "all of the above"
Options like "All of the above" or "Both A and B" break when shuffled. Mark them as pinned and keep them last:
const pinned = q.options.filter((o) => /all of the above|none of the above/i.test(o));
const free = q.options.filter((o) => !pinned.includes(o));
const options = [...shuffle(free), ...pinned];
4. Normalize typed answers
For free-response rounds, "The Beatles", "beatles" and "Beatles!" should all count:
const norm = (s) =>
s.toLowerCase()
.normalize("NFD").replace(/[\u0300-\u036f]/g, "") // strip accents
.replace(/^(the|a|an)\s+/, "")
.replace(/[^a-z0-9]/g, "");
const isCorrect = (input, answers) => answers.some((a) => norm(a) === norm(input));
5. Team scoring for classrooms
In a classroom the quiz is projected and the class plays in teams. A tiny scoring model covers it, including bonus points when a team explains why an answer is right — which turns a quiz into actual review:
function createScoreboard(teams) {
const scores = Object.fromEntries(teams.map((t) => [t, 0]));
return {
correct: (team, pts = 1) => (scores[team] += pts),
explainBonus: (team) => (scores[team] += 1),
standings: () => Object.entries(scores).sort((a, b) => b[1] - a[1]),
};
}
6. Write for the audience
The engine is only half of it. Questions for middle schoolers and teens need to be age-appropriate, unambiguous and checkable; every answer should have one clearly defensible correct option. Organizing by category and audience makes it easy for a teacher to grab a relevant round at the end of a lesson.
Takeaways
- Never shuffle with
sort(() => Math.random() - 0.5). - Balance the correct answer's position across a round.
- Pin "all/none of the above" options.
- Normalize typed answers generously.
Grab a quiz at triviaquestionsandanswers.net. What's the sneakiest bias you've found in a quiz or game?
Top comments (0)