All posts

Felix Felicis Is Just a Seeded Random Number

In the Harry Potter books, Felix Felicis makes everything line up. A seeded random number generator in your test suite does the exact same thing, minus the magic.

In the sixth Harry Potter book, Harry drinks a tiny dose of Felix Felicis before a difficult afternoon and everything lines up. Doors open at the right time. The right person says the right thing. He is not smarter. He is just lucky in a way that feels suspiciously well-timed.

I thought about that potion again last week, debugging a test suite that failed on CI but passed every single time on my laptop. The difference was not luck. It was randomness. My local run used one seed, CI generated another, and somewhere in that gap a sort order flipped and a test that assumed a specific order broke. Felix Felicis, it turns out, is just a seeded random number generator with a marketing budget.

The chaos you did not know you had

Flaky tests usually get blamed on timing, or the network, or "CI being CI." Half the time the real cause is quieter: test data that is different every run. A fixture builds a user with a fake-data library, a list gets shuffled, an id gets generated fresh each time. None of that is wrong by itself. It becomes a problem the moment a test's correctness depends on a property that random data only sometimes has, like a list that happens to sort cleanly or two records that happen to land on different pages.

The worst part is how it hides. The test passes ninety-five times out of a hundred, which is exactly often enough to make you trust it and exactly rare enough to burn an afternoon when it fails during a release.

Drinking the potion on purpose

The fix is almost embarrassingly simple: seed the random generator. Pin it to a fixed value at the start of the test run, and every "random" choice becomes deterministic. The fake-data library gives the same fake name every time. The shuffle produces the same order every time. Nothing about the data changed. What changed is that luck became a setting instead of an accident.

This is Felix Felicis, decanted into code. You are not removing randomness from the system, because real randomness is often exactly what you want to simulate real-world messiness. You are choosing when to drink it. Run with a fixed seed for a reproducible bug report. Run with a rotating seed in a nightly job to go hunting for the next hidden assumption. Both are useful. Confusing them is where teams get hurt.

A well-instrumented debug setup is the same trick

The same idea shows up away from tests. A good debugging setup is just a way of buying yourself a lucky afternoon on demand. Structured logs with a request id let you replay exactly what happened instead of guessing. A local stack with the same versions as production removes the "works on my machine, not there" gap. A seeded database snapshot means the bug you are chasing is sitting in front of you instead of appearing once a week in production and vanishing by the time you open a terminal.

None of this makes you a better engineer in the moment of debugging. It makes the moment of debugging unnecessary to be lucky in. Harry did not get better at defense against the dark arts because of the potion. He got the chance to use the skill he already had, without chaos getting in the way first. A deterministic repro does the same thing for you: it clears the noise so the actual bug is the only thing left standing.

Where I stopped trusting luck

I used to treat flaky tests as a tax I paid for having a test suite at all. Re-run the pipeline, shrug, move on. That worked until a release where the flaky test was quietly catching a real bug, and I had trained myself to ignore it because it "sometimes just failed." That was the week I went through our fixtures and pinned every seed I could find, and wrote down the ones I deliberately left random.

Now, if a test can only pass by assuming a specific order, a specific id, or a specific random draw, I make that assumption visible instead of hoping it stays favorable. A fixed seed is a small line of code, but it is also an admission: I do not want to rely on getting lucky. I want to know exactly which spell I am casting and why.

Keep the potion in the cabinet

Felix Felicis also comes with a warning in the books: drink it too often and it gets you reckless and dependent on luck holding up. The same is true of seeded, overly tidy test environments. If every fixture is pinned and every input is predictable, you can ship code that only ever meets the nicest version of reality, and the real world is not that nice.

So I use both. Seeded and boring for the tests where I need to know precisely what broke. Random and a little wild for the ones meant to surface what I have not thought of yet. The lesson was never that luck is bad. It is that luck, like a potion, works best when you decide when to take it, instead of discovering on a Friday night release that you ran out.