W3docs

Python random Module

Learn Python's random module: generate random numbers, make weighted choices, shuffle sequences, seed for reproducibility, and use cryptographic randomness.

Python's built-in random module generates pseudo-random numbers and makes random selections. It is based on the Mersenne Twister algorithm, which produces high-quality pseudo-random sequences suitable for simulations, games, sampling, and testing — but not for security-sensitive purposes like password generation or cryptographic keys. This chapter covers:

  • Generating random floats, integers, and ranges
  • Picking random elements: choice, choices, sample
  • Shuffling sequences in-place with shuffle
  • Seeding for reproducible results
  • Continuous distributions: uniform, gauss, triangular
  • When to use secrets instead of random

No installation is needed — import random is all it takes.

Importing the module

import random

All examples below assume this import is in scope. You can also import individual functions to avoid the random. prefix, though the prefix makes it clear where each function comes from:

from random import randint, choice, shuffle

Generating random floats

random()

random.random() returns a float in [0.0, 1.0) — including 0.0 but never reaching 1.0.

import random

print(random.random())  # e.g. 0.6394267984578837
print(random.random())  # e.g. 0.025010755222666936

The value changes on every call because the generator advances its internal state.

uniform(a, b)

random.uniform(a, b) returns a float N such that a <= N <= b:

import random

random.seed(42)
print(random.uniform(1.5, 10.5))  # 7.254841186120954
print(random.uniform(-1.0, 1.0))  # e.g. -0.82  (some float in [-1, 1])

Unlike random(), uniform lets you specify both ends of the range, and the upper bound can be returned.

Generating random integers

randint(a, b)

random.randint(a, b) returns a random integer N such that a <= N <= b. Both endpoints are inclusive:

import random

random.seed(42)
die = random.randint(1, 6)
print(die)  # 6

This is the most common way to simulate dice rolls, card draws, or any discrete integer range.

randrange(start, stop[, step])

random.randrange mirrors Python's range()stop is exclusive and you can specify a step:

import random

random.seed(42)
# Pick an even number from 0 to 98 (0, 2, 4, ..., 98)
print(random.randrange(0, 100, 2))   # e.g. a random even integer

# Pick a multiple of 5 from 0 to 95
random.seed(42)
print(random.randrange(0, 100, 5))   # 15

When you need random integers from a range that does not start at 0, or needs a step, randrange is more expressive than randint.

getrandbits(k)

random.getrandbits(k) returns a random non-negative integer with exactly k random bits:

import random

random.seed(42)
print(random.getrandbits(8))   # 163 (an integer in 0..255)
print(random.getrandbits(16))  # an integer in 0..65535

This is useful when you need random integers larger than what randint is called with, or when working at the bit level.

Choosing from sequences

choice(seq)

random.choice(seq) picks one element at random from a non-empty sequence. It works with lists, tuples, and strings:

import random

random.seed(42)
fruits = ['apple', 'banana', 'cherry']
print(random.choice(fruits))    # cherry

random.seed(42)
print(random.choice('ABCDE'))   # A  (picks one character)

If the sequence is empty, a IndexError is raised.

choices(population, weights=None, k=1)

random.choices picks k elements with replacement (the same element can appear more than once). You can bias the selection with weights:

import random

colors = ['red', 'blue', 'green']

# Equal probability — each run may differ
print(random.choices(colors, k=3))
# e.g. ['blue', 'red', 'red']

# Weighted: green is 3× more likely than red, blue is 2×
random.seed(42)
print(random.choices(colors, weights=[1, 2, 3], k=4))
# ['green', 'red', 'blue', 'blue']

choices always returns a list, even when k=1. The weights argument is relative — [1, 2, 3] gives the same distribution as [10, 20, 30].

sample(population, k)

random.sample picks k elements without replacement — each element can only appear once in the result. It does not modify the original sequence:

import random

random.seed(42)
# Pick 4 unique numbers from 0-9
print(random.sample(range(10), 4))  # [1, 0, 4, 9]

# Simulate a lottery: 6 unique numbers from 1-49
random.seed(7)
ticket = sorted(random.sample(range(1, 50), 6))
print(ticket)  # [4, 5, 10, 21, 26, 42]

If k is larger than the population size, random.sample raises a ValueError:

import random

try:
    random.sample([1, 2, 3], 5)
except ValueError as e:
    print(e)  # Sample larger than population or is negative

choices vs sample at a glance:

choicessample
ReplacementYesNo
Duplicate resultsPossibleNever
Modifies originalNoNo
Supports weightsYesNo (use choices)

Shuffling sequences

random.shuffle(seq) shuffles the list in-place and returns None. The original list is modified:

import random

random.seed(42)
deck = [1, 2, 3, 4, 5]
random.shuffle(deck)
print(deck)  # [4, 2, 3, 5, 1]  (original list is modified)

If you need a shuffled copy without touching the original, use random.sample:

import random

random.seed(42)
original = [1, 2, 3, 4, 5]
shuffled = random.sample(original, len(original))
print(original)  # [1, 2, 3, 4, 5]  (unchanged)
print(shuffled)  # [4, 2, 3, 5, 1]

Seeding for reproducible results

By default, the random module seeds itself from the system time (or os.urandom()), so each run produces different values. Calling random.seed() with a fixed value locks the sequence so you get identical output every time — essential for debugging, unit tests, and reproducible simulations:

import random

random.seed(42)
print(random.random())          # 0.6394267984578837
print(random.randint(1, 10))    # 1
print(random.choice(['a', 'b', 'c']))  # c

# Reset to the same seed — identical sequence
random.seed(42)
print(random.random())          # 0.6394267984578837  (same as before)
print(random.randint(1, 10))    # 1
print(random.choice(['a', 'b', 'c']))  # c

random.seed() accepts an integer, a string, a float, bytes, or None (which re-seeds from the system). Seeding is a global operation — it affects all subsequent calls in the same program, so set the seed once at the start of a reproducible block.

Generating reproducible test data

import random

random.seed(999)
test_scores = [round(random.uniform(0, 100), 2) for _ in range(5)]
print(test_scores)  # [78.13, 8.01, 87.25, 57.38, 49.06]

Every run with seed(999) produces the exact same list, making this useful for unit tests that assert specific values.

Continuous distributions

The random module includes several continuous probability distributions, useful in simulations and statistical modelling.

uniform(a, b)

Already covered above — a flat distribution where every value in [a, b] is equally likely.

triangular(low, high, mode)

Returns a float from a triangular distribution where mode is the most common value:

import random

random.seed(42)
# Most values cluster near 5, between 0 and 10
t = random.triangular(0, 10, 5)
print(t)  # 5.753983033817951

Useful when you know the min, max, and most-likely value but not the exact distribution — common in project estimation and risk modelling.

gauss(mu, sigma)

Returns a float from a Gaussian (normal) distribution with mean mu and standard deviation sigma:

import random

random.seed(42)
# Heights in cm: mean 170, std 10
heights = [round(random.gauss(170, 10), 1) for _ in range(5)]
print(heights)  # [168.6, 168.3, 168.9, 177.0, 168.7]

random.normalvariate(mu, sigma) is equivalent but thread-safe; use normalvariate in multi-threaded programs.

expovariate(lambd)

Returns a float from an exponential distribution, where lambd is 1 / mean. Useful for modelling time between events (e.g. customer arrivals, network packets):

import random

random.seed(42)
# Mean inter-arrival time = 5 minutes; lambd = 1/5
wait = random.expovariate(1 / 5)
print(round(wait, 2))  # 5.1  (minutes until next arrival)

Practical examples

Simulating 60 dice rolls

import random

random.seed(42)
counts = {face: 0 for face in range(1, 7)}
for _ in range(60):
    counts[random.randint(1, 6)] += 1

print(counts)
# {1: 15, 2: 10, 3: 8, 4: 7, 5: 10, 6: 10}

With more rolls the distribution approaches the theoretical 1/6 probability for each face.

Picking a random team

import random

random.seed(42)
players = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank']
random.shuffle(players)

team_a = players[:3]
team_b = players[3:]
print('Team A:', team_a)
print('Team B:', team_b)

Simple random password

For non-security contexts (demos, throwaway tokens), random.choices works well:

import random
import string

random.seed(42)
chars = string.ascii_letters + string.digits
password = ''.join(random.choices(chars, k=12))
print(password)  # NbrnTP3fAbnF

For actual user passwords or tokens, use the secrets module instead (see below).

When to use secrets instead of random

The random module is not cryptographically secure. An attacker who observes enough output can predict future values. For anything security-sensitive, use the secrets module (Python 3.6+), which draws from the operating system's cryptographic random source:

import secrets

# Cryptographically secure random integer in [0, 100)
print(secrets.randbelow(100))

# Secure random token for a password-reset link (URL-safe base64)
token = secrets.token_urlsafe(32)
print(token)  # e.g. 'gIg9XzExxFkHLAH9JCK8Zxb1aBEFZ...'

# Secure random hex string for an API key
api_key = secrets.token_hex(16)
print(api_key)  # e.g. 'a3f1c2d4e5b6...'

Rule of thumb: if you are generating tokens, session IDs, passwords, or any value an adversary must not be able to guess, use secrets. Use random for everything else (games, simulations, test fixtures, sampling).

Quick reference

FunctionDescriptionReturns
random.random()Float in [0.0, 1.0)float
random.uniform(a, b)Float in [a, b]float
random.randint(a, b)Integer in [a, b] (both inclusive)int
random.randrange(start, stop[, step])Integer from range(start, stop, step)int
random.getrandbits(k)Non-negative integer with k random bitsint
random.choice(seq)One random elementelement type
random.choices(pop, weights, k)k elements with replacementlist
random.sample(pop, k)k elements without replacementlist
random.shuffle(seq)Shuffle list in-placeNone
random.seed(a)Set the random seedNone
random.gauss(mu, sigma)Gaussian (normal) variatefloat
random.normalvariate(mu, sigma)Normal variate (thread-safe)float
random.triangular(low, high, mode)Triangular distribution variatefloat
random.expovariate(lambd)Exponential distribution variatefloat

Practice

Practice
Which function picks elements without replacement (each item can appear only once in the result)?
Which function picks elements without replacement (each item can appear only once in the result)?
Was this page helpful?