When would you reach for `random.SystemRandom` instead of the `secrets` functions?
answer
- Two secure entry points, different widths
- One module is deliberately very small
- The other subclasses the ordinary generator
- Think shuffle and sample, not tokens
- Its seeding call does nothing at all
basics
~20 sWhen you need the wider random API from an unpredictable source. random.SystemRandom is a random.Random subclass fed by os.urandom, so it offers shuffle, sample, randrange and uniform; secrets exposes only choice, randbelow, randbits and the token helpers.
solid answer
~40 s`random.SystemRandom` subclasses `random.Random` and overrides the primitive draws to read the operating system's cryptographic source, so you inherit the whole distribution and selection API — `shuffle`, `sample`, `randrange`, `uniform` — with unpredictable output. `secrets` is deliberately narrow: tokens, `secrets.choice`, `secrets.randbelow`, `secrets.randbits`, and nothing that shuffles or samples. So reach for `SystemRandom` when the operation itself must be unguessable and is not one `secrets` provides — an unpredictable ordering, an unbiased draw of several distinct items. Two consequences follow from the source: seeding it does nothing and its state cannot be saved or restored, so no sequence is reproducible; and every draw reads OS entropy, which is noticeably slower than the Mersenne Twister and wrong for a hot simulation loop.
code
python · 7 linesimport random
sysrand = random.SystemRandom()
deck = list(range(10))
sysrand.shuffle(deck)
print(len(deck), sorted(deck) == list(range(10)))
print(len(sysrand.sample(range(100), 3)), sysrand.randrange(1000) < 1000)go deeper
Know that secrets covers tokens and a single unguessable pick, and that a wider secure API exists in random.SystemRandom when you need shuffling or sampling.
Explain the relationship: SystemRandom subclasses random.Random and swaps the source for the operating system's, secrets is built on such an instance, and seeding or saving state on it is meaningless.
Weigh the cost. Justify per-call OS entropy where unpredictability is required, keep plain random in hot statistical paths, and reject hand-rolled modulo reductions of raw bytes in review.
Fix the vocabulary for the codebase so engineers stop choosing per call site: which operations must be unguessable, which entry point expresses each one, and where a seeded generator is legitimately required for reproducibility.
## The same class hierarchy, a different source `random.SystemRandom` is a subclass of `random.Random` that replaces the generator underneath. Where `random.Random` produces its bits from MT19937, `SystemRandom` reads them from the operating system's cryptographic random source — the one `os.urandom` exposes. Everything built on top of those bits is inherited unchanged, so a `SystemRandom` instance answers to the full `random` API: `shuffle`, `sample`, `choice`, `randrange`, `randint`, `uniform`, `getrandbits` and the distribution helpers. Two methods behave differently because the source has no state you own: seeding does nothing at all, and there is no state to save or restore, so the save/restore methods refuse. That is the honest consequence of drawing from the OS rather than a defect — a sequence you can reproduce is exactly what a security generator must not offer. `secrets` is a small module built on top of one such instance. `secrets.choice`, `secrets.randbelow` and `secrets.randbits` are the operations the standard library judged that security code needs, plus the three token helpers. It deliberately stops there. ## When the narrow module is not enough Reach for `SystemRandom` when the operation you need is unguessable *and* is not in that short list: * An ordering that must not be predictable — randomised ballot or candidate ordering, shuffling challenge options, randomising the order in which a set of endpoints is probed so an observer cannot anticipate the next one. * Drawing several distinct items without replacement, where `sample` gives you the guarantee of distinctness that repeated `choice` calls do not. * A continuous value from an unpredictable source — an unguessable delay, for instance. For a single unguessable pick, `secrets.choice` is the clearer expression of intent and there is no reason to construct anything. The rule of thumb: `secrets` first, `SystemRandom` when the verb you need is missing from it. ## Why not build it yourself from `os.urandom` The tempting alternative is to take raw bytes and reduce them by hand, and it is where modulo bias enters. Reducing a uniform byte modulo 100 maps 256 equally likely values onto 100 outcomes, and since 256 is not a multiple of 100 the first 56 outcomes each receive three source values while the rest receive two — a measurable skew of roughly 50% toward the low end. The bias grows with the range and with any hand-rolled "make it fit" arithmetic. `secrets.randbelow` and the `SystemRandom` methods avoid this by drawing enough random bits and discarding results that fall outside the range, retrying until one lands inside. That rejection loop is not something to reimplement in application code. Worth stating clearly, because it is a common misconception: the ordinary `random.choice` and `random.sample` are *not* biased either — `random.Random` uses the same rejection technique internally. The difference between `random` and `SystemRandom` is predictability, not fairness. ## The cost, and where it stops being acceptable Every primitive draw from `SystemRandom` goes to the OS source, which is far more expensive than advancing a Mersenne Twister in userspace. For shuffling a few hundred items once per request that cost is invisible. For a simulation drawing millions of values, or a sampling loop in a hot path, it is the wrong tool and plain `random` is right — nothing there needs to be unguessable. ```python import random sysrand = random.SystemRandom() options = ["a", "b", "c", "d"] sysrand.shuffle(options) # unpredictable presentation order picks = sysrand.sample(range(1000), 5) # five distinct values, no repeats print(sorted(options), len(set(picks))) ``` The summary a candidate should be able to give in one breath: `secrets` for tokens and single choices, `SystemRandom` for the rest of the `random` API when predictability matters, and plain `random` everywhere it does not.
- What happens if you call the seed method on a `random.SystemRandom` instance?Nothing. The override exists so the class satisfies the `random.Random` interface, but there is no internal state to set — every draw goes to the operating system. For the same reason the instance cannot save or restore its state, so no sequence from it is reproducible. If you need reproducibility you need a seeded `random.Random`, and then the values are by definition predictable.
- Is `random.choice` on the default generator biased toward some elements?No. `random.Random` picks an index with an internal rejection-sampling helper rather than a modulo reduction, so the distribution is uniform. The problem with using it for security is purely that MT19937's outputs reveal its state and therefore its future values. Bias only appears when application code reduces raw bytes by hand, which is what `secrets.randbelow` exists to prevent.
- Would you use `random.SystemRandom` for a Monte Carlo simulation?No. Every primitive draw reads the operating system's random source, which is far slower than advancing the Mersenne Twister in userspace, and a simulation needs neither unpredictability nor resistance to state recovery. It also gives up the seeded reproducibility that makes a simulation result re-checkable. Plain `random` with an explicit `random.Random` instance is the right choice.
saying these in an interview costs you the question
- Thinks `secrets` provides a shuffle or a sample function
- Uses `random.SystemRandom` in a hot simulation loop
- Expects a seeded `SystemRandom` to replay a sequence
- Reduces `os.urandom` bytes with modulo to fit a range
- Claims `random.choice` is unsafe because it is biased