Learning Goals
3 minBy the end of this lesson you can:
- Match each random tool to the question it answers: dice, coin, single pick, weighted pick, shuffle, multi-pick, random fraction.
- Use
random.seed(n)to make "random" runs repeatable while you're debugging. - Avoid the duplicate trap by choosing
random.sampleoverrandom.choicein a loop.
Warm-Up
5 minWhat does each of these print? Try to answer without running the code first.
import random print(random.randint(1, 6)) # ? print(random.choice(["heads", "tails"])) # ? print(random.sample([1, 2, 3, 4, 5], 3)) # ? cards = ["A", "K", "Q", "J"] random.shuffle(cards) print(cards) # ?
What you should see (yours will vary)
4 heads [2, 5, 3] ['Q', 'A', 'J', 'K']
Each call answered a different question: roll a die, flip a coin, pick three different cards, shuffle a deck. Same module, four jobs.
Don't default to random.randint. Each random tool has a question it's perfect for — and a few it's wrong for. Picking the right one usually halves the code.
New Concept · Seven Random Tools
14 min1 · randint(a, b) — pick a whole number
Inclusive at both ends. random.randint(1, 6) can give you a 1 or a 6 — unlike range which stops before its end.
import random die = random.randint(1, 6) coin = random.randint(0, 1) # 0 or 1
2 · choice(seq) — pick one item
Pass any list, tuple or string. Returns one item, uniformly at random.
drinks = ["cocoa", "coffee", "hot chocolate", "horlicks"] print(random.choice(drinks)) # A coin flip from a list of two: print(random.choice(["heads", "tails"]))
3 · choices(seq, k=n) — pick many, with replacement
Notice the plural choices. You get a list of n picks; duplicates are allowed.
# Roll three dice print(random.choices([1,2,3,4,5,6], k=3)) # e.g. [3, 5, 3] # With weighted picks (loaded die!) weights = [1, 1, 1, 1, 1, 5] # 6 is five times more likely print(random.choices([1,2,3,4,5,6], weights=weights, k=10))
4 · sample(seq, k=n) — pick many, no duplicates
Same shape as choices, but never picks the same item twice. Perfect for dealing cards or drawing raffle tickets.
deck = ["A", "K", "Q", "J", "10", "9", "8", "7"] hand = random.sample(deck, 3) print(hand) # e.g. ['Q', '7', 'A'] -- all different
5 · shuffle(lst) — scramble in place
Rearranges the list itself. Returns None — never write lst = random.shuffle(lst), that wipes it out.
deck = ["A", "K", "Q", "J"] random.shuffle(deck) print(deck) # e.g. ['J', 'Q', 'A', 'K']
6 · random() — a fraction between 0 and 1
Returns a float in [0.0, 1.0). Great for percentage chances.
if random.random() < 0.3: print("Rare drop! 30% chance.") else: print("Common.")
7 · seed(n) — make randomness repeatable
Random numbers are actually deterministic — every run starts from a hidden "seed". random.seed(42) fixes the seed so the same calls always give the same answers. Brilliant for debugging.
random.seed(42) print(random.randint(1, 100)) # always 82 on Python 3.x print(random.randint(1, 100)) # always 15 print(random.randint(1, 100)) # always 4 random.seed(42) # reset! print(random.randint(1, 100)) # 82 again
Use seed while you're hunting a bug — once it's fixed, comment the line out so the program is "random" again.
The decision tree
Question Tool Roll a die / pick a whole number in range randint(a, b) Pick one item from a list choice(seq) Pick n items, duplicates OK (with weights?) choices(seq, k=n, weights=...) Pick n different items sample(seq, k=n) Scramble a list in place shuffle(lst) A fraction between 0 and 1 (for chance) random() Make a run reproducible while debugging seed(n)
The classic bug: dealing five cards with a loop of random.choice(deck). You can get the same card twice. Use random.sample(deck, 5) instead. The plural-vs-singular and the choices-vs-sample distinctions are exactly what trips most beginners up.
Worked Example · The Casino Demo
12 minRun all seven tools in one file. Save as casino.py:
Code
# casino.py — every random tool in one demo import random # Use a seed for the demo so the output is the same every run random.seed(42) # 1 — dice print("Dice roll :", random.randint(1, 6)) # 2 — coin print("Coin flip :", random.choice(["heads", "tails"])) # 3 — three dice rolls, duplicates allowed print("3 dice :", random.choices([1,2,3,4,5,6], k=3)) # 4 — loaded die: 6 is five times more likely print("Loaded 1d6 :", random.choices([1,2,3,4,5,6], weights=[1,1,1,1,1,5], k=10)) # 5 — deal three different cards deck = ["A", "K", "Q", "J", "10", "9", "8", "7"] print("Hand :", random.sample(deck, 3)) # 6 — shuffle the whole deck random.shuffle(deck) print("Shuffled :", deck) # 7 — random chance if random.random() < 0.3: print("Bonus drop!") else: print("No drop.")
Output (same every run because of the seed)
Dice roll : 1 Coin flip : heads 3 dice : [4, 1, 1] Loaded 1d6 : [6, 5, 6, 6, 5, 6, 6, 6, 6, 4] Hand : ['7', 'K', '10'] Shuffled : ['8', 'A', '7', 'Q', 'K', '9', 'J', '10'] Bonus drop!
Read the diff
Notice the loaded-die output — five of the ten rolls landed on a 6, because the weights tilted toward it. That's how loot tables and rare-drop chances work in real games. Notice also that the seed fixes the entire run — comment out random.seed(42) and every line will change next time.
Basic
5 minRoll five dice in one call and print the list. Then print the total and the maximum.
Hint
import random rolls = random.choices([1,2,3,4,5,6], k=5) print("Rolls :", rolls) print("Total :", sum(rolls)) print("Best :", max(rolls))
choices with k=5 handles all five rolls in one line.
Challenge 1
4 minBuild a 52-card deck (4 suits × 13 ranks) and deal a 5-card hand using random.sample. Confirm there are no duplicates.
Hint
import random suits = ["♠", "♥", "♦", "♣"] ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] deck = [r + s for s in suits for r in ranks] print("Deck size :", len(deck)) # → 52 hand = random.sample(deck, 5) print("Hand :", hand) print("Unique? :", len(set(hand)) == len(hand))
The double list comprehension [r + s for s in suits for r in ranks] builds all 52 cards in one line. Get used to it; it'll feel weird at first.
Challenge 2
4 minBuild a loot table where common items appear often and rares appear rarely. Open 50 chests and print how many of each item appeared.
items = ["potion", "sword", "shield", "gold ring", "dragon scale"] weights = [50, 20, 15, 10, 5]
Hint
import random items = ["potion", "sword", "shield", "gold ring", "dragon scale"] weights = [50, 20, 15, 10, 5] opens = random.choices(items, weights=weights, k=50) counts = {} for it in opens: counts[it] = counts.get(it, 0) + 1 for it in items: print(f"{it:<14}: {counts.get(it, 0)}")
The output won't exactly match the weights — randomness is wobbly — but over 50 opens the distribution should be roughly in proportion. weights + choices is the standard pattern for loot, gacha pulls and ad placements.
Challenge 3 · The Loaded-Coin Lab
8 minBuild coin_lab.py. Flip a coin a lot of times and confirm that the proportion of heads matches the weight you set. This is the core of all probability simulation.
Your file must:
- Define a function
flip(n_flips, weight_heads)that returns the proportion of heads inn_flipsflips. Userandom.choices(["H", "T"], weights=[weight_heads, 1 - weight_heads], k=n_flips). - Call it with
(100, 0.5),(100, 0.7),(10_000, 0.7). Print each. - Observe that the proportion is closer to the target with more flips.
Stretch goal. Use random.seed(0) so the run is repeatable. Build a small table comparing n_flips = 10, 100, 1000, 10000 against target 0.7.
Show one possible solution
# coin_lab.py — confirm that simulation approaches the true weight import random def flip(n_flips, weight_heads): out = random.choices( ["H", "T"], weights=[weight_heads, 1 - weight_heads], k=n_flips, ) heads = out.count("H") return heads / n_flips random.seed(0) print(f"100 flips, fair : {flip(100, 0.5):.3f}") print(f"100 flips, 70% H : {flip(100, 0.7):.3f}") print(f"10000 flips, 70% H : {flip(10_000, 0.7):.4f}") # Stretch — convergence table print() print(f"{'n':>7}{'observed':>12}{'target':>10}") for n in (10, 100, 1000, 10_000): print(f"{n:>7}{flip(n, 0.7):>12.4f}{0.7:>10.4f}")
Non-negotiables: a function that flips with a weight, the out.count("H") / n_flips trick to measure heads, and a comparison that shows higher n_flips = closer-to-target. The Law of Large Numbers in five lines.
Recap
3 minSeven random tools cover almost every randomness question. randint(a, b) for whole numbers in a range (inclusive both ends!). choice for one item, choices for many with duplicates and optional weights, sample for many without duplicates. shuffle reorders in place. random() gives a fraction for percentage chances. seed(n) locks the run so you can debug. Match the tool to the question and the code halves.
Vocabulary Card
- random.randint(a, b)
- A whole number between
aandb, both inclusive. - random.choice(seq)
- One item, uniformly at random.
- random.choices(seq, weights=..., k=n)
npicks with duplicates;weightstilts the odds.- random.sample(seq, k=n)
ndifferent picks — never the same item twice.- random.shuffle(lst)
- Rearrange the list in place. Returns
None. - random.seed(n)
- Make the random sequence reproducible. Comment out for real randomness.
Extra Mission
4 minBuild quiz_picker.py. You have ten quiz questions. Pick three of them without repeats for tonight's class, then shuffle their order.
questions = [ "Capital of the world?", "Capital of Indonesia?", "Square root of 81?", "Largest planet?", "Smallest prime?", "Name a noble gas.", "Year of independence?", "What does HTML stand for?", "Highest mountain where you live?", "Programming language we're using?", ]
Your file must:
- Use
random.sampleto draw 3 questions. - Use
random.shuffleon the drawn list — yes, even after the sample. - Print them numbered
1. ... 2. ... 3. ...using f-strings and the padding from PY-L2-16.
Stretch. At the top of the file, random.seed(int(input("Seed (press enter for random): ") or 0)) — if the user enters a number, the same questions will appear next time. Why might a teacher want that?
Sample · quiz_picker.py
# quiz_picker.py — pick + shuffle a small quiz import random questions = [ "Capital of the world?", "Capital of Indonesia?", "Square root of 81?", "Largest planet?", "Smallest prime?", "Name a noble gas.", "Year of independence?", "What does HTML stand for?", "Highest mountain where you live?", "Programming language we're using?", ] # Stretch — optional seed raw = input("Seed (enter to randomise): ") if raw.strip(): random.seed(int(raw)) picked = random.sample(questions, 3) random.shuffle(picked) print() for i, q in enumerate(picked, start=1): print(f"{i:02}. {q}")
Non-negotiables: random.sample(..., 3), random.shuffle, and an enumerate(..., start=1) printed loop. Why might a teacher want a seed? So that two classes get the same quiz on Monday and Tuesday — fairness.