How to generate random numbers in Python

Random number generation powers everything from cryptography to game development in Python. The language's built-in modules provide robust tools for creating both pseudo-random and true random numbers, with options for different probability distributions and ranges.

This guide covers essential techniques, practical applications, and troubleshooting tips for random number generation, featuring code examples created with Claude, an AI assistant built by Anthropic.

Basic random numbers with random.random()

import random
random_number = random.random()
print(random_number)
0.7237845954323937

The random.random() function generates a floating-point number between 0.0 and 1.0, following a uniform distribution where each value has an equal probability of being selected. This fundamental random number generator serves as a building block for creating random values in other ranges or distributions.

Python's random module uses the Mersenne Twister algorithm to produce these numbers. While not cryptographically secure, it provides sufficient randomness for most general applications like:

  • Simulating probability-based events
  • Generating test data
  • Creating random selections from datasets
  • Building simple games and animations

Basic random number techniques

Building on the basic random.random() function, Python's random module provides specialized methods for generating integers, floats within ranges, and random selections from sequences.

Generating integers with random.randint()

import random
random_integer = random.randint(1, 100)
print(random_integer)
42

The random.randint() function generates random integers within a specified inclusive range. Unlike random.random(), it returns whole numbers instead of decimals.

When calling random.randint(1, 100), Python selects a random integer between 1 and 100, including both endpoints. Each number has an equal probability of being chosen.

  • Both arguments must be integers
  • The first number must be less than or equal to the second
  • The function includes both boundary values in its selection pool

This makes randint() particularly useful for simulating dice rolls, generating random ages, or creating test data that requires whole numbers within specific bounds.

Creating random floats in a range with random.uniform()

import random
random_float = random.uniform(1.5, 8.5)
print(random_float)
5.273546789123456

The random.uniform() function generates floating-point numbers within a specified range, giving you precise control over the minimum and maximum values. Unlike random.random(), you can define any numerical boundaries you need.

  • The function takes two parameters: a minimum and maximum value
  • It returns a random float that includes the minimum but not the maximum value
  • Each possible float within the range has an equal probability of being selected

In the example, random.uniform(1.5, 8.5) produces a random decimal number between 1.5 and 8.5. This makes it ideal for simulating measurements, calculating prices, or generating test data that requires decimal precision.

Selecting random elements with random.choice()

import random
fruits = ["apple", "banana", "cherry", "date", "elderberry"]
random_fruit = random.choice(fruits)
print(random_fruit)
banana

The random.choice() function selects a single random element from any sequence like a list, tuple, or string. Each item has an equal probability of being chosen.

In the example, random.choice(fruits) randomly picks one fruit from the list and assigns it to random_fruit. The function handles all the complexity of generating random indices internally.

  • Works with any sequence type in Python
  • Returns exactly one element each time it's called
  • Raises an error if the sequence is empty

This makes random.choice() perfect for simulating random selections, creating game mechanics, or sampling data points from collections.

Advanced random number techniques

Building on these foundational techniques, Python offers powerful tools for generating large arrays of random numbers, working with statistical distributions, and creating reproducible results through seeding.

Generating arrays of random numbers with NumPy

import numpy as np
random_array = np.random.random(5)
print(random_array)
[0.12345678 0.23456789 0.34567891 0.45678912 0.56789123]

NumPy's random.random() function efficiently generates arrays of random floating-point numbers between 0 and 1. The integer argument specifies how many random numbers to create. In this case, np.random.random(5) produces an array containing exactly 5 random values.

  • NumPy arrays process data much faster than Python lists for large-scale random number generation
  • The generated numbers follow a uniform distribution, giving each value an equal probability
  • You can reshape these arrays into different dimensions for matrices or multi-dimensional data structures

This approach proves especially valuable when you need to generate thousands or millions of random numbers for simulations, data analysis, or machine learning applications. NumPy's optimized C backend handles the heavy lifting, making it significantly more efficient than using Python's built-in random module in a loop.

Creating random numbers with specific distributions

import numpy as np
normal_distribution = np.random.normal(0, 1, 5)
print(normal_distribution)
[-0.12345678  0.23456789 -1.34567891  0.45678912  1.56789123]

NumPy's random.normal() function generates random numbers that follow a normal (Gaussian) distribution. The function takes three key parameters: mean (center of the distribution), standard deviation (spread), and size (number of samples).

In the example, np.random.normal(0, 1, 5) creates 5 random numbers centered around 0 with a standard deviation of 1. This produces values that cluster around the mean, with decreasing probability as numbers get further from 0.

  • Values have a higher chance of being close to the mean (0)
  • About 68% of values fall within one standard deviation of the mean
  • The distribution creates a classic bell curve pattern

This type of distribution proves invaluable when modeling real-world phenomena like measurement errors, natural variations, or financial market movements that tend to follow normal patterns.

Setting seeds for reproducible random numbers

import random
import numpy as np
random.seed(42)
np.random.seed(42)
print(random.random())
print(np.random.random())
0.6394267984578837
0.37454011884736133

Setting a random seed ensures your random numbers follow the same sequence every time you run your code. The random.seed() and np.random.seed() functions initialize this sequence with a number of your choice.

  • Seeds make random operations reproducible. Running the same seed produces identical results across different executions
  • This reproducibility proves essential for debugging, testing, and scientific experiments
  • Both Python's random module and NumPy need separate seed initialization for consistent results

The example uses seed 42. Any value works, but 42 appears frequently in examples because of its cultural significance. Each subsequent call to random() or np.random.random() will generate the same "random" numbers in sequence.

Generating random passwords with random.choice()

The random.choice() function enables secure password generation by randomly selecting characters from custom character sets, allowing you to create strong passwords that meet specific complexity requirements.

import random
import string
characters = string.ascii_letters + string.digits + string.punctuation
password = ''.join(random.choice(characters) for _ in range(12))
print(password)

This code combines Python's built-in string and random modules to create a random string. The string.ascii_letters contains all letters (both cases), string.digits provides numbers 0-9, and string.punctuation adds special characters. These combine into a single string stored in characters.

The core functionality uses a generator expression with random.choice() to select 12 random characters from this pool. The join() method then combines these selections into the final string. This approach ensures the password includes a diverse mix of character types.

  • The underscore variable _ indicates we don't need the loop variable
  • The empty string '' serves as the separator between joined characters
  • Each run produces a unique 12-character string

Estimating π with Monte Carlo simulation

Monte Carlo simulation estimates the value of π by randomly plotting points inside a square containing a quarter circle, then calculating the ratio between points that fall inside versus outside the circle.

import random
import math

points = 10000
inside_circle = sum(1 for _ in range(points) if random.random()**2 + random.random()**2 <= 1)
pi_estimate = 4 * inside_circle / points
print(f"Estimated π: {pi_estimate}")
print(f"Math.pi: {math.pi}")

This code uses random number generation to approximate the mathematical constant π through a probability-based approach. The program generates 10,000 random points within a 1x1 square using random.random(). It then calculates how many points fall inside a quarter circle with radius 1 by checking if the squared coordinates sum to less than or equal to 1.

The ratio of points inside the circle to total points, multiplied by 4, gives us an estimate of π. This works because the ratio of the quarter circle's area to the square's area equals π/4. More points generally yield more accurate estimates.

  • The sum() function with a generator expression efficiently counts qualifying points
  • The **2 operator squares the random coordinates
  • The program compares the estimate to Python's built-in math.pi constant

Common errors and challenges

Python developers frequently encounter three critical random number generation pitfalls that can derail their code's reliability and functionality.

Forgetting to set a seed with random.seed() for reproducible results

When developers skip setting a random seed, their code produces different results with each execution. This makes debugging nearly impossible and creates inconsistent test outcomes. The random.seed() function solves this by initializing the random number generator to a known state.

import random

# Different results each time the script runs
result1 = random.randint(1, 100)
# If we run the script again, we can't reproduce the same numbers
print(f"Random number: {result1}")

Without a seed value, the random.randint() function generates unpredictable numbers that change with each program execution. This makes it impossible to replicate specific test cases or debug random number-dependent code. The solution appears in the code below.

import random

# Set seed for reproducibility
random.seed(42)
result1 = random.randint(1, 100)
# Same seed will produce the same sequence of random numbers
print(f"Reproducible random number: {result1}")

Setting random.seed(42) initializes the random number generator to a specific starting point. This ensures your code produces identical random numbers in the same sequence every time it runs. The seed value itself doesn't matter. What matters is using the same seed consistently throughout your testing and debugging process.

  • Always set seeds when writing unit tests that involve randomization
  • Use seeds during development to reproduce and fix bugs involving random numbers
  • Remember that different Python versions might produce different sequences even with the same seed

Watch for this issue when your code needs predictable results or when collaborating with other developers who need to replicate your random number sequences.

Confusion between random.randint() and random.randrange() boundaries

Developers often mix up the boundary behaviors of Python's random number functions. The key difference lies in their ranges: random.randint() includes both endpoints while random.randrange() excludes the upper bound. This distinction causes unexpected results when generating random integers.

import random
# Trying to get random number between 1 and 10
value1 = random.randint(1, 10)  # Includes 10
value2 = random.randrange(1, 10)  # Excludes 10
print(f"Values: {value1}, {value2}")

The code demonstrates how randint() and randrange() produce different outputs for seemingly identical ranges. This unexpected behavior can lead to off-by-one errors in your programs. Let's examine the corrected implementation below.

import random
# Getting random number between 1 and 10
value1 = random.randint(1, 10)  # Includes 10
value2 = random.randrange(1, 11)  # Now includes 10
print(f"Values: {value1}, {value2}")

To include the number 10 in the range when using random.randrange(), you must specify an upper bound of 11 instead of 10. This adjustment compensates for the function's exclusive upper boundary behavior. The corrected code demonstrates both approaches achieving the same outcome: random.randint(1, 10) and random.randrange(1, 11) will both generate numbers from 1 to 10 inclusive.

  • Always verify the boundary behavior when switching between these functions
  • Consider using randint() for simpler inclusive ranges
  • Watch for this issue when porting code between different random number generators

Error when using random.sample() with sample size larger than population

The random.sample() function generates unique random selections from a sequence. A common error occurs when developers request more items than exist in the source population. This raises a ValueError that can crash your program unexpectedly.

import random
fruits = ["apple", "banana", "cherry"]
# Trying to get 5 random fruits from a list of 3
random_fruits = random.sample(fruits, 5)
print(random_fruits)

The random.sample() function raises a ValueError because it can't select 5 unique items from a list containing only 3 fruits. The code below demonstrates the proper way to handle this limitation.

import random
fruits = ["apple", "banana", "cherry"]
# Sample safely with min() function
sample_size = min(5, len(fruits))
random_fruits = random.sample(fruits, sample_size)
print(random_fruits)

The min() function provides an elegant solution by dynamically adjusting the sample size to match the available items. This prevents the ValueError while still returning as many unique random selections as possible from the source sequence.

  • Always verify your sample size doesn't exceed the population size
  • Consider using error handling with try-except blocks for more complex scenarios
  • Watch for this issue when working with user-defined sample sizes or dynamic collections

This pattern proves especially useful in data processing applications where the size of input collections may vary. The solution maintains the random selection's integrity while gracefully handling edge cases.

FAQs

What is the difference between random() and randint() functions?

The random() function generates a floating-point number between 0 and 1, making it ideal for probability calculations and normalized ranges. In contrast, randint() produces whole numbers within a specified range, which better suits tasks like simulating dice rolls or selecting array indices.

  • Use random() when you need decimal precision or want to scale results to any range
  • Choose randint() for generating integers within defined boundaries without manual rounding

This fundamental difference shapes how we handle randomization in practical applications—from statistical sampling to game development.

How do you set a seed for reproducible random number generation?

Random number generators use mathematical algorithms that produce predictable sequences when initialized with the same starting value. Setting this value—called a seed—with functions like random.seed() ensures your random numbers remain consistent across program runs.

This reproducibility proves essential for debugging, testing, and scientific computing where you need to replicate exact results. The seed value itself can be any integer, but many developers use meaningful numbers like timestamps or experiment IDs to track different random sequences.

Can you generate random numbers within a specific range using uniform()?

The uniform() function generates random numbers within a specified range using uniform distribution. This means each value in the range has an equal probability of being selected—making it ideal for simulations and games where fairness matters.

You can control both the lower and upper bounds of the generated numbers. For example, uniform(1, 10) produces random floating-point values between 1 and 10, while uniform(0, 1) creates numbers between 0 and 1. The distribution remains consistent across the entire range, ensuring unbiased randomization.

What's the purpose of the 'random' module in Python?

The random module generates pseudo-random numbers in Python, enabling you to simulate chance and uncertainty in your programs. It powers everything from games to scientific simulations.

  • The module uses a deterministic algorithm that produces numbers that appear random but follow a predictable sequence when given the same starting point (seed)
  • This predictability makes testing and debugging easier while still providing the randomness needed for most applications
  • Common functions include random() for floating-point numbers and randint() for integers within a specified range

How do you generate random floating-point numbers between 0 and 1?

Most programming languages generate random floating-point numbers between 0 and 1 using a uniform distribution. The random() function produces a sequence of pseudorandom numbers using a deterministic algorithm. Each number appears random but follows a mathematical pattern that eventually repeats.

For true randomness in critical applications like cryptography, specialized hardware generates random numbers from unpredictable physical processes. The standard random() works well for most everyday needs: simulations, games, and statistical sampling.

🏠