Learning LibraryCore Coding LibraryTeens

The random Module in Python: controlled unpredictability

Randomness makes a game feel alive and a simulation realistic. The random module supplies it — and seed() lets you make that randomness repeatable when you need to.

The big idea

random produces pseudo-random values; seeding it with random.seed(n) makes the exact sequence reproducible.

See it in code

1The basics

random.randint(a, b) returns an integer in [a, b]. The catch that makes it usable: random.seed(n) pins the sequence, so a 'random' run becomes exactly reproducible:

python
import random

random.seed(42)
print(random.randint(1, 100))
print(random.randint(1, 100))
Run it — seeded, so these two values never change:
82
15

Seed with 42 and you'll always get 82 then 15. That reproducibility is what lets you debug something 'random' at all.

2A step further

Now use randomness to perturb a value. Each enemy sits on a clean grid at 60 + col * 90; adding a random jitter nudges it off that perfect line:

python
import random

random.seed(42)
for col in range(5):
    jitter = random.randint(-15, 15)
    print("enemy x:", 60 + col * 90 + jitter)
Run it — each x is the grid position plus a small nudge:
enemy x: 65
enemy x: 138
enemy x: 225
enemy x: 338
enemy x: 413

The grid x's would be 60, 150, 240, 330, 420; each is nudged by a jitter of +5, -12, -15, +8, -7. Seeded with 42, these are the exact numbers the formation below uses for its first row.

3In our world

Now apply that jitter to a real formation. A perfect grid looks robotic; a random nudge on each enemy makes it feel hand-placed. We seed first — same as the warm-up — so the run is reproducible, which matters when a random bug only shows up sometimes:

python
import random
from game import Stage, Enemy

screen = Stage.create()
Stage.clear(screen)

random.seed(42)
for row in range(2):
    for col in range(5):
        jitter = random.randint(-15, 15)
        Enemy(60 + col * 90 + jitter, 80 + row * 70).draw(screen)
Run it — a formation with natural-looking jitter, the same every run:
Two rows of five purple enemy sprites, each nudged slightly off a perfect grid.

It's the warm-up's jitter, now across two rows of sprites. Because we seeded with 42, the 'random' offsets are identical every run. That's the trick: seed during development so you can reproduce and debug, then remove the seed (or seed from the clock) to ship real variety.

The same idea, everywhere

Reproducible randomness is core to serious work: shuffle a dataset, sample rows, run a Monte Carlo simulation, generate test data. random.choice, random.shuffle, and random.random() cover most needs — and a fixed seed turns a flaky, non-repeatable run into one you can study step by step.

Try it yourself

Change the seed to 7 and note the whole formation shifts, but stays fixed across runs. Then remove random.seed(42) entirely and run twice — now each run differs. Try random.choice([...]) to pick a random enemy type.

The common mistake

Expecting cryptographic-grade secrecy from random. It's pseudo-random — fine for games and simulations, but predictable enough that you must never use it for passwords or keys. For security, Python has the separate secrets module.

What it unlocks

Controlled randomness builds on imports and feeds generative art and reproducible backtesting.

Want the simpler version? Read the Kids version →