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 = ["milo", "kopi", "teh tarik", "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.
Try It Yourself
13 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.
Build 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.
Build 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.
Mini-Challenge · 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.
Homework
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 Malaysia?", "Capital of Indonesia?", "Square root of 81?", "Largest planet?", "Smallest prime?", "Name a noble gas.", "Year of Malaysian independence?", "What does HTML stand for?", "Highest mountain in Malaysia?", "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 Malaysia?", "Capital of Indonesia?", "Square root of 81?", "Largest planet?", "Smallest prime?", "Name a noble gas.", "Year of Malaysian independence?", "What does HTML stand for?", "Highest mountain in Malaysia?", "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.