Let's talk
product

AI quiz generator putting the right answer first

An AI quiz generator put the right answer in the same position on a hundred and fifty questions. Each was correct; the set was broken. How to check yours.

· · updated

A multiple-choice answer sheet and a red pencil

If your AI quiz generator always puts the right answer in the same position, the cause is that nothing told it to vary the position, so it wrote the correct answer first every time. It matters because anyone can score full marks by tapping the same option, and every question still looks correct when read on its own. The check is to count where the correct answers fall across the whole set, not to read the questions one by one.

Here is how that looked in a children’s learning app, and how we fixed it.

A ten-year-old could have scored full marks on a hundred and fifty questions by tapping the first option every time and never reading anything. Not because the questions were easy. Because every correct answer sat in position one.

This was content generated for a learning app that pays points for quiz performance, so the child was one pattern away from a payout with no learning attached. The questions were already published and already on her tablet.

Nobody caught it in review, and by review I mean my own. I had checked the questions. Each question was factually correct, each had four plausible options, each pointed at the right one. Reading them one at a time, there is nothing to find. The defect does not exist in any single question. It only exists in the set.

Why the bias was there at all

When a generator has a free choice that nothing constrains, it does not make that choice randomly. It makes the same choice repeatedly. Writing a question, the natural order is to state the correct answer first — that is the thing you know — and then invent three distractors. Nothing in the process ever says “now move it”. So the correct answer stays where it was written, in every question, forever.

That is not a quirk of one tool. It is what happens to any unconstrained degree of freedom in a generated artefact. The order of keys in a produced object, the choice of a placeholder name, which of two equivalent branches an emitter takes: if nothing specifies it, it will be constant, and constant is not the same as arbitrary. Arbitrary would have been fine. A quarter of the answers would have landed in each position by accident.

It was found by an independent review of a later batch, run before publishing rather than after, which is the only reason it was found before the child did. Two reviewers with different briefs — one on safeguarding, one on factual accuracy — both flagged it, neither having been asked to.

Fixing published content is a different problem from fixing a generator

The generator can be fixed going forward. The hundred and fifty questions already sitting on a device cannot be regenerated, because a child had already answered some of them and her scores refer to the questions as they exist.

So the correction had to move the answer index without changing which answer is correct. That is a narrow thing to get right, and it is very easy to write a migration that shuffles the options and leaves the index pointing at whatever now occupies the old slot. The child then sees a quiz where the marking is wrong, which is enormously worse than one where the marking is predictable.

The load ran in a single transaction with the check written against the meaning rather than the mechanism:

  • Before touching anything, record the text of the correct option for every question.
  • Shuffle the options and rewrite the index.
  • Re-read every question and compare the text now sitting at the new index against the recorded text.
  • Commit only if every one matches. Otherwise roll the whole thing back.

The important detail is that the assertion is on the string, not on the number. The number is the thing being changed, so an assertion about the number cannot distinguish a correct migration from a broken one. When a migration’s whole purpose is to change a value, your invariant has to be stated in terms of something that is not allowed to change. In a data migration the thing that must not change is almost always the meaning, and the meaning is almost always in a different column from the one you are editing.

What replaced hand-checking

The next batches were two hundred questions and forty-eight questions. Hand-tracking the spread across four positions over two hundred items is a task that is guaranteed to fail somewhere in the middle.

So the answer position stopped being a thing anyone chose. The questions are still written with the correct answer first, because that is the natural way to write one. A deterministic rotation then moves it into a fixed pattern per quiz, so the even spread across four positions is produced by the process rather than maintained by attention.

The load then refused to commit unless it observed all of this at once: the expected count of lessons, quizzes and questions; every answer index inside the bounds of a four-option array; an even spread across the four positions within each quiz; and no duplicated option text on any question. Any one of those failing rolls back everything.

Sixteen answers were then read back by hand, not to check the rotation worked — the transaction already proved that — but to confirm the index and the text had moved together, which is the one failure the transaction’s own arithmetic could conceivably have shared a bug with.

Two things this changed in how I think about content

Content is data, and data gets migrations, transactions and invariants. It is tempting to treat authored material as a soft artefact that gets reviewed by a human and typed in. Once it is a hundred and fifty rows with a foreign key and a numeric index into an array, it has all the failure modes of any other data, and none of the tooling unless you build it.

Verify the distribution, not the items. Every review process I have run over generated material reviews items. Item review is necessary and it is structurally blind to the class of defect that lives in aggregate. The questions to ask of a generated set are the boring statistical ones. Are the answers spread across the positions? Do the same three distractors keep reappearing? Is one option consistently the longest, which is its own well-known tell? None of those are visible at the item level, and all of them are visible in about four lines of query.

The honest limit is that this only catches the biases you thought to measure. An even spread across four positions says nothing about whether the correct option is systematically the most detailed one, and I have not checked that. What I would say with confidence is that any generated set has at least one constant where you expected variety, and the cheapest way to find it is to count things rather than read them.

How to check your own quiz set

  1. Count how often the correct answer sits in each position across the whole set. With four options, each position should hold roughly a quarter.
  2. Count whether the same few wrong options keep reappearing, and whether the correct option is usually the longest.
  3. Ask your supplier whether the process or a person decides the answer position, and whether the load refuses to publish a set that is lopsided.
  4. If a set is already published, ask how it will be corrected without changing which answer is marked right. The text of the correct option should be compared before and after, not the position number.

Where this ends up

Fluent, internally consistent and wrong is the failure mode generated work actually has, and counting a set rather than reading its items is one of the few checks that catches it. The others, and the places where the assistance does not help at all, are under AI-first delivery.

Working on something like this?

We build this kind of software, and we staff the teams that do.

Get in touch