How to use the 'random' module in Python
Learn how to use Python's random module. Explore methods, real-world applications, common errors, and debugging tips.

Random numbers in Python are crucial for simulations, games, and cryptography. The random module provides a versatile toolkit for tasks that require unpredictability, from simple choices to complex data shuffles.
In this article, you'll explore key techniques and tips for the random module. You'll find real-world applications and practical advice to help you debug and master randomness in your projects.
Basic usage of the random module
import random
random_number = random.random()
print(f"Random number between 0 and 1: {random_number}")--OUTPUT--Random number between 0 and 1: 0.7254897563178143
The first step is always to import the random module, which gives you access to its suite of functions. The code then calls random.random(), the module's most basic function. It generates a floating-point number between 0.0 and 1.0, which serves as the foundation for many other randomization tasks.
Understanding this core function is key. Many other functions in the random module, like those for generating integers or picking from a list, use the output of random.random() under the hood. It's the fundamental unit of randomness that gets scaled and transformed for more complex needs. For additional methods of generating random numbers in Python, you can explore other techniques and libraries.
Common random operations
The module builds on this foundation with specialized functions for generating integers, creating floats in a custom range, and making random choices from a sequence.
Generating random integers with randint()
import random
random_integer = random.randint(1, 100)
print(f"Random integer: {random_integer}")--OUTPUT--Random integer: 42
When you need a whole number, random.randint() is your go-to function. It takes two arguments, a starting point and an ending point, and returns a random integer that falls somewhere between them.
- Unlike many range functions,
randint(a, b)is inclusive, meaning the result can be eitheraorb.
In this example, random.randint(1, 100) will produce any integer from 1 to 100. It’s perfect for things like simulating a dice roll or generating a random percentage.
Working with random floating-point numbers using uniform()
import random
random_float = random.uniform(5.0, 10.0)
print(f"Random float between 5.0 and 10.0: {random_float}")--OUTPUT--Random float between 5.0 and 10.0: 7.823481978264493
For a random float within a custom range, uniform() is your tool. It works much like randint() but generates floating-point numbers instead of integers. You just provide a lower and upper bound, and it returns a random value anywhere between them.
- The distribution is uniform, which means every number in the specified range has an equal chance of being chosen.
It's perfect for memory-efficient simulations that need precise measurements, like generating random coordinates or sensor readings.
Making random choices from sequences with choice()
import random
fruits = ['apple', 'banana', 'cherry', 'durian', 'elderberry']
selected_fruit = random.choice(fruits)
print(f"Selected fruit: {selected_fruit}")--OUTPUT--Selected fruit: cherry
When you need to select a single item from a list or another sequence, random.choice() is the perfect tool. It takes a sequence—in this case, the fruits list—and returns one of its elements at random.
- This function isn't limited to lists; it works just as well with tuples and strings.
- Each element in the sequence has an equal probability of being chosen, making it fair and unbiased for random selections.
Advanced random techniques
Beyond generating single random values, the module provides powerful tools for shuffling entire sequences, ensuring your results are reproducible, and modeling complex statistical patterns.
Shuffling sequences with shuffle()
import random
cards = ['A', 'K', 'Q', 'J', '10']
print(f"Original order: {cards}")
random.shuffle(cards)
print(f"After shuffling: {cards}")--OUTPUT--Original order: ['A', 'K', 'Q', 'J', '10']
After shuffling: ['J', 'A', '10', 'Q', 'K']
The random.shuffle() function is your tool for reordering a sequence. It takes a list, like the cards in the example, and rearranges its elements into a new, random order. This is ideal for tasks like shuffling a deck for a game or randomizing a playlist, or even prototyping quick random features through vibe coding.
- Crucially,
shuffle()modifies the list in-place. This means it changes the original list directly and doesn't return a new one, which is why you don't assign its result to a variable. For more techniques on shuffling lists in Python, you can explore additional methods and use cases.
Using random seeds for reproducibility
import random
random.seed(42)
print(random.random())
print(random.random())
random.seed(42)
print(random.random())--OUTPUT--0.6394267984578837
0.025010755222666936
0.6394267984578837
When you need your random results to be predictable, random.seed() is the function to use. It initializes the random number generator with a specific starting value, ensuring you get the same sequence of numbers every time you run your code. This is crucial for debugging and testing, as it makes your 'random' outcomes consistent.
- As the code shows, setting the seed to
42and then callingrandom.random()produces the same number each time the seed is reset. The sequence starts over from the same point.
Working with statistical distributions
import random
gaussian_values = [random.gauss(0, 1) for _ in range(5)]
exponential_value = random.expovariate(1.5)
print(f"Gaussian samples: {gaussian_values}")
print(f"Exponential sample: {exponential_value}")--OUTPUT--Gaussian samples: [0.496714153011, -0.138264301171, 0.647689996795, 1.523030600511, -0.234153749427]
Exponential sample: 0.2845772583537913
This example demonstrates creating lists of random numbers using list comprehensions combined with statistical distribution functions.
The random module isn't limited to uniform distributions. You can also generate numbers that follow complex patterns, which is essential for building realistic simulations.
random.gauss(mu, sigma)pulls numbers from a normal distribution, often called a "bell curve." The first argument,mu, is the average value, whilesigmacontrols how spread out the numbers are.random.expovariate(lambda)models the time between events, like customer arrivals. Thelambdaargument represents the rate at which events occur.
Move faster with Replit
Replit is an AI-powered development platform that comes with all dependencies pre-installed, so you can skip setup and start coding instantly. Instead of just learning functions like randint(), you can describe what you want to build, and Agent 4 handles everything—from writing the code and connecting databases and APIs to deploying it live.
Instead of piecing together techniques, describe the app you want to build and Agent will take it from idea to working product:
- A dice rolling simulator for tabletop games that uses
randint()to generate fair rolls. - A playlist randomizer that uses
shuffle()to reorder a list of your favorite songs. - A data anonymizer that replaces sensitive information with random values generated by
choice()anduniform().
Simply describe your app, and Replit will write the code, test it, and fix issues automatically, all within your browser.
Common errors and challenges
While the random module is powerful, a few common pitfalls can lead to bugs and security vulnerabilities if you're not careful.
Understanding the difference between randint() and randrange()
A frequent point of confusion is the subtle difference between randint() and randrange(). Both generate random integers, but they handle their upper bounds differently, which can easily lead to off-by-one errors.
randint(a, b)is inclusive, meaning the result can bea,b, or any integer between them.randrange(start, stop)works like Python's standardrange()function—it's exclusive of thestopvalue. The result will never bestop.
Forgetting to reset the seed between test runs
Using random.seed() is great for making tests reproducible, but it can cause problems if not managed correctly. If you set a seed for one test, subsequent tests might unintentionally reuse the same sequence of "random" numbers, making them dependent on each other and hiding potential bugs.
To avoid this, ensure each test that requires a specific random sequence explicitly sets its own seed. For tests that just need randomness without being predictable, don't set a seed at all and let the system handle it.
Avoiding random for security-sensitive operations
This is perhaps the most critical point—the random module is not cryptographically secure. Its numbers are generated by a deterministic algorithm, making them predictable and unsuitable for anything security-related.
When you need to generate passwords, security tokens, or perform any cryptographic function, you should always use the secrets module instead. It's specifically designed to produce cryptographically strong random numbers that are safe for sensitive applications.
Understanding the difference between randint() and randrange()
It's easy to mix up randint() and randrange(), leading to subtle off-by-one errors. Their similar names hide a key difference in how they treat the upper limit, which can cause you to accidentally exclude numbers. The code below demonstrates this common pitfall.
import random
# Trying to get numbers from 1 to 10
numbers = [random.randrange(1, 10) for _ in range(5)]
print(f"Random numbers from 1-10: {numbers}")
The code attempts to generate numbers from 1 to 10, but because randrange() excludes the upper limit, it will never produce the number 10. This creates a classic off-by-one error. The corrected code below shows how to adjust for this.
import random
# For numbers 1-10 with randrange (end is exclusive)
numbers = [random.randrange(1, 11) for _ in range(5)]
# Or using randint which includes both endpoints
numbers_alt = [random.randint(1, 10) for _ in range(5)]
print(f"Random numbers from 1-10: {numbers}")
To get numbers up to 10, you must call randrange(1, 11) because its stop parameter is exclusive. A simpler solution is using randint(1, 10), which includes both endpoints and is often more intuitive. This distinction is critical when generating indices for a list or any time your upper boundary must be included. Forgetting this can lead to bugs that are hard to spot, as your code will run without errors but produce incorrect results.
Forgetting to reset the seed between test runs
While using random.seed() is key for reproducible tests, it can create a chain reaction if not handled carefully. One test can unintentionally influence the next, making your test suite unreliable and hiding bugs. The code below shows this problem in action.
import random
def test_random_function():
random.seed(42)
result1 = random.random()
# Doing other operations that use random
random.random()
result2 = random.random()
assert result2 == 0.025010755222666936
Because random.seed(42) is set without being reset, the random number generator's state is now predictable. Any subsequent tests will fail to be truly random, potentially masking issues. The following code demonstrates the proper approach.
import random
def test_random_function():
random.seed(42)
result1 = random.random()
# Reset seed for the next test
random.seed(42)
result2 = random.random()
assert result1 == result2
By calling random.seed(42) again, you reset the generator's state, ensuring the next call to random.random() produces the same predictable value for your assertion. This practice is vital in unit testing, as it guarantees each test runs independently. It prevents one test's random state from unintentionally influencing another, making your test suite far more reliable and easier to debug when failures occur.
Avoiding random for security-sensitive operations
Using the random module for security is a critical mistake. Because its numbers are predictable, it's unsuitable for generating passwords, tokens, or keys. The code below shows a common but insecure way to generate a 'security token' using random.choice().
import random
import string
# Generating a security token (insecure)
chars = string.ascii_letters + string.digits
token = ''.join(random.choice(chars) for _ in range(20))
print(f"Security token: {token}")
This token appears random, but the sequence from random.choice() can be reproduced if the generator's starting point is known. This creates a critical security flaw. The following code shows the correct approach for generating secure tokens.
import secrets
import string
# Generating a security token (secure)
chars = string.ascii_letters + string.digits
token = ''.join(secrets.choice(chars) for _ in range(20))
print(f"Secure token: {token}")
The corrected code replaces random.choice() with secrets.choice(). The secrets module is built specifically for generating cryptographically strong random numbers, making the token unpredictable and secure. You should always reach for the secrets module when handling anything security-sensitive, such as passwords, API keys, or session tokens, especially when securing AI-generated code. It's the standard for creating values that must be truly random and safe from prediction, including when generating random strings for secure applications.
Real-world applications
With the common pitfalls understood, you can see how these functions are correctly used in applications from dice games to security tools.
Generating secure passwords with choice()
While you can generate a password by repeatedly using random.choice() to select from a pool of characters, the secrets module should always be used for applications where security is critical.
import random
import string
characters = string.ascii_letters + string.digits + string.punctuation
password = ''.join(random.choice(characters) for _ in range(12))
print(f"Generated password: {password}")
This code generates a simple 12-character password by pulling from a complete set of characters defined in Python's string module. It first combines letters, numbers, and symbols into a single string, creating the pool of possible characters.
The core of the operation is a generator expression that runs 12 times.
- In each loop,
random.choice()selects one character from the pool. - The
join()method then assembles these 12 random characters into a single string, forming the final password.
For more comprehensive techniques on making passwords in Python, including security considerations and advanced patterns, you can explore additional approaches.
Simulating a dice game with randint()
The randint() function is the core of any dice game simulation, allowing you to generate fair and random rolls for each player.
import random
def roll_dice():
return random.randint(1, 6)
player1_score = sum(roll_dice() for _ in range(3))
player2_score = sum(roll_dice() for _ in range(3))
print(f"Player 1 scored: {player1_score}")
print(f"Player 2 scored: {player2_score}")
print(f"Winner: {'Player 1' if player1_score > player2_score else 'Player 2' if player2_score > player1_score else 'Tie'}")
This code pits two players against each other in a simple dice game. The roll_dice() function encapsulates a single die roll, using random.randint(1, 6) to produce a number between 1 and 6.
- Each player's score is calculated by calling this function three times inside a generator expression, with the
sum()function efficiently totaling the results. - The final
print()statement uses a compact ternary expression to compare the scores and declare a winner or a tie based on the outcome.
Get started with Replit
Put your knowledge into practice. Describe your tool to Replit Agent, like "a dice rolling simulator for a game" or "a secure password generator app."
Replit Agent writes the code, tests for errors, and deploys your app. It handles the entire development cycle. Start building with Replit.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.
Describe what you want to build, and Replit Agent writes the code, handles the infrastructure, and ships it live. Go from idea to real product, all in your browser.



