Randomness with the random Module
Reviewed & published by Brayan K
The random module is Python's standard toolkit for generating pseudo-random numbers, picking and shuffling items, and reproducing the same sequence on demand with a seed.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Roll dice, deal cards, shuffle a playlist, or sample a dataset — it is one of the most fun and useful modules in the standard library. We will seed every example so your output matches exactly.
What You'll Learn in This Lesson
- Random floats with random() and uniform()
- Random integers with randint()
- Picking items with choice, choices, and sample
- Shuffling a list in place with shuffle()
- Reproducible results with seed()
- Why random is NOT safe for passwords (use secrets)
1 Random Numbers and the Seed
Three workhorses cover most needs: random() for a float in [0.0, 1.0), uniform(a, b) for a float in any range, and randint(a, b) for a whole number with both ends included:
import random
random.seed(42) # makes the output below reproducible
print(random.random()) # 0.6394267984578837 (0.0 to 1.0)
print(random.randint(1, 6)) # 1 (a die roll, 1-6)
print(random.uniform(0, 10)) # 7.415490453354112 (any float range)Calling seed() with a fixed value resets the generator to a known starting point, so re-running gives the identical sequence — invaluable for tests and debugging:
import random
random.seed(123)
print([random.randint(1, 100) for _ in range(3)]) # [7, 35, 12]
random.seed(123) # reset to the same seed
print([random.randint(1, 100) for _ in range(3)]) # [7, 35, 12] (identical!)import random
random.seed(1) # fixed seed -> reproducible output
print(round(random.random(), 4)) # 0.1344
print(random.randint(1, 6)) # 1
print(round(random.uniform(1, 100), 2)) # 26.25
# Expected output:
# 0.1344
# 1
# 26.252 Picking Items: choice, choices, sample
These three pick from a sequence in different ways. choice grabs one; choices grabs k items with replacement (repeats allowed); sample grabs k distinct items with no repeats:
import random
options = ["rock", "paper", "scissors"]
random.seed(42)
print(random.choice(options)) # scissors (one pick)
# choices: WITH replacement (can repeat), supports weights
print(random.choices(["a", "b", "c"], k=5)) # ['a', 'c', 'a', 'a', 'a']
# sample: WITHOUT replacement (all distinct), like dealing cards
print(random.sample(range(1, 50), 3)) # [48, 35, 6]Remember the rule: choices can repeat and accepts a weights= argument; sample never repeats. Asking sample for more items than exist raises a ValueError.
import random
colors = ["red", "green", "blue", "yellow"]
random.seed(10)
print(random.choice(colors)) # red
print(random.choices(colors, k=4)) # ['green', 'blue', 'red', 'yellow']
print(random.sample(colors, 2)) # ['blue', 'yellow']
# Expected output:
# red
# ['green', 'blue', 'red', 'yellow']
# ['blue', 'yellow']3 Shuffling and the Security Warning
shuffle() rearranges a list in place — it changes the original list and returns None. It is perfect for shuffling a deck of cards or a playlist:
import random
deck = [1, 2, 3, 4, 5]
random.seed(7)
random.shuffle(deck) # modifies deck in place; returns None
print(deck) # [5, 1, 4, 2, 3]
# Need a shuffled COPY without touching the original? Use sample:
original = ["A", "K", "Q", "J"]
random.seed(7)
shuffled = random.sample(original, len(original))
print(shuffled) # a new shuffled list
print(original) # unchangedimport secrets
# Use secrets — NOT random — for anything security-sensitive
token = secrets.token_hex(16) # a secure random hex string
pin = secrets.randbelow(10000) # a secure number in [0, 10000)
# (output differs every run — that is the point)import random
hand = ["A", "K", "Q", "J", "10"]
random.seed(5)
random.shuffle(hand) # shuffles in place, returns None
print(hand) # ['A', 'K', 'J', 'Q', '10']
# shuffle returns None — never write 'hand = random.shuffle(hand)'!
result = random.shuffle([1, 2, 3])
print(result) # None
# Expected output:
# ['A', 'K', 'J', 'Q', '10']
# None🎯 Your Turn: Fill in the Blanks
Complete the program so it seeds the generator and rolls a reproducible number. Replace each ___ and match the expected output.
# 🎯 YOUR TURN: complete the reproducible roll
import random
# 1. Set the seed to 0 so the result is reproducible:
random.___(0)
# 2. Roll an integer between 1 and 100 (both inclusive):
roll = random.___(1, 100)
print(roll) # 50
# Expected output:
# 50First blank: seed — so the line is random.seed(0).
Second blank: randint — so the line is random.randint(1, 100), which yields 50 for seed 0.
Common Errors to Avoid
❌ Capturing the return value of shuffle
deck = random.shuffle(deck) # deck is now None!✅ shuffle works in place — just call random.shuffle(deck) and keep using deck.
❌ Using random for passwords or tokens
token = random.choice(chars) # predictable -> insecure!✅ Use secrets.choice(chars) or secrets.token_hex() for anything security-related.
❌ Asking sample for more items than exist
random.sample([1, 2, 3], 5) # ValueError: Sample larger than population✅ Keep k <= the length of the sequence, or use choices (which allows repeats).
🎯 Mini Challenge: Two-Dice Simulator
Roll two dice many times and confirm that 7 is the most common total. Everything is seeded, so your numbers will match exactly.
import random
from collections import Counter
def roll_two():
return random.randint(1, 6) + random.randint(1, 6)
# First, a small reproducible sample of 5 rolls
random.seed(99)
print([roll_two() for _ in range(5)]) # [8, 7, 4, 4, 4]
# Now 1000 rolls and count how often 7 appears (the most likely total)
random.seed(99)
totals = Counter(roll_two() for _ in range(1000))
print("sevens:", totals[7]) # sevens: 181
print("most common:", totals.most_common(1)[0][0]) # most common: 7
# Expected output:
# [8, 7, 4, 4, 4]
# sevens: 181
# most common: 7📚 Keep Learning
Go deeper with the official Python documentation:
- random — Generate pseudo-random numbers
- secrets — Cryptographically strong random for tokens & passwords
- random.sample and random.choices reference
Frequently Asked Questions
Lesson complete — you can generate randomness on demand!
You can produce floats and integers, pick items with choice, choices, and sample, shuffle a list in place, reproduce results with seed(), and you know to reach for secrets when security matters.
Practice quiz
What does calling random.seed() with a fixed value achieve?
- Makes numbers more truly random
- Speeds up generation
- Makes the generated sequence reproducible on re-runs
- Prevents repeats within a sequence
Answer: Makes the generated sequence reproducible on re-runs. Seeding with the same value resets the generator to a known start, so you get the identical sequence every run — invaluable for tests and debugging.
Which range does random.randint(1, 6) cover?
- 1 to 6 (both ends inclusive)
- 1 to 5 (top excluded)
- 0 to 6
- 2 to 6
Answer: 1 to 6 (both ends inclusive). Unlike range, randint(a, b) includes BOTH endpoints, so randint(1, 6) can return 1, 2, 3, 4, 5, or 6 — a perfect die.
What does random.random() return?
- An integer from 0 to 1
- A float in [0.0, 1.0]
- A random list element
- A float in [0.0, 1.0)
Answer: A float in [0.0, 1.0). random.random() returns a float in the half-open interval [0.0, 1.0) — 0.0 is possible but 1.0 is not.
How does random.choices() differ from random.sample()?
- choices is without replacement; sample allows repeats
- choices allows repeats (with replacement) and weights; sample never repeats
- They are identical
- sample only works on strings
Answer: choices allows repeats (with replacement) and weights; sample never repeats. choices() picks k items WITH replacement (repeats allowed, supports weights); sample() picks k DISTINCT items without replacement.
What does random.shuffle(deck) return?
- None — it shuffles in place
- A new shuffled list
- The original list
- A tuple
Answer: None — it shuffles in place. shuffle() rearranges the list in place and returns None, so never write deck = random.shuffle(deck).
How can you get a shuffled COPY without modifying the original list?
- random.shuffle(copy)
- random.choice(original)
- random.sample(original, len(original))
- random.random(original)
Answer: random.sample(original, len(original)). random.sample(seq, len(seq)) returns a new shuffled list while leaving the original untouched.
Why should you NOT use the random module for passwords or tokens?
- It is too slow
- Its Mersenne Twister output is predictable and can be reconstructed by an attacker
- It only produces integers
- It requires a network connection
Answer: Its Mersenne Twister output is predictable and can be reconstructed by an attacker. random uses the predictable Mersenne Twister; for anything security-sensitive use the secrets module, which draws from a cryptographically secure source.
What happens with random.sample([1, 2, 3], 5)?
- Returns 5 items with repeats
- Returns [1, 2, 3]
- Returns None
- Raises ValueError: Sample larger than population
Answer: Raises ValueError: Sample larger than population. sample cannot draw more distinct items than exist, so asking for 5 from a 3-element population raises ValueError. Use choices() if repeats are acceptable.
Which function gives a range-style exclusive top, never returning the upper bound?
- random.randint(a, b)
- random.randrange(a, b)
- random.random()
- random.choice(seq)
Answer: random.randrange(a, b). random.randrange(a, b) follows the range() convention and never returns b, unlike randint which is inclusive.
Which secrets function produces a secure random hex string suitable for a token?
- random.token_hex(16)
- secrets.random()
- secrets.token_hex(16)
- random.choice(chars)
Answer: secrets.token_hex(16). secrets.token_hex(16) returns a cryptographically secure random hex string — the right tool for tokens, unlike anything in random.
Continue this course
- Previous: namedtuple & NamedTuple
- Next: Running Commands with subprocess — Run external programs with subprocess.run, capture output, and stay safe
- Quick reference: Python cheat sheet
Frequently asked questions
What does random.seed() do?
random.seed() sets the starting point of the random number generator. The numbers Python produces are actually pseudo-random — generated by a deterministic formula. If you seed with the same value, you get the exact same sequence every time. This is essential for tests, debugging, and reproducible experiments, because it lets you re-run a program and see identical 'random' results.
What is the difference between random.choice, choices, and sample?
choice() returns one random item from a sequence. choices() returns a list of k items WITH replacement, so the same item can appear more than once and you can pass weights. sample() returns k items WITHOUT replacement, so every result is distinct, like dealing cards from a deck. Use choices for weighted or repeatable picks, and sample when you need unique selections.
Why is random not safe for passwords or tokens?
The random module uses the Mersenne Twister, a fast pseudo-random generator that is predictable: given enough output an attacker can reconstruct its internal state and predict future values. That is fine for games, simulations, and shuffling, but dangerous for anything security-related. For passwords, tokens, API keys, or session IDs, use the secrets module instead, which draws from the operating system's cryptographically secure source.
How do I get a random integer in a range?
random.randint(a, b) returns a whole number between a and b inclusive — both ends are possible, so randint(1, 6) models a die. If you want to follow the range() convention where the top is excluded, use random.randrange(a, b), which never returns b. For a random float, use random.random() for 0.0 to 1.0 or random.uniform(a, b) for any float range.