How to create a list of random numbers in Python
Learn how to create a list of random numbers in Python. Explore different methods, tips, real-world applications, and common error fixes.

Python makes it easy to create lists of random numbers for simulations, data science, and gaming. Its built-in modules offer simple and powerful tools for this purpose.
In this article, you'll learn several techniques to create these lists. You'll also get practical tips, see real-world applications, and receive debugging advice to master random number generation.
Using random.randint() with list comprehension
import random
random_numbers = [random.randint(1, 100) for _ in range(10)]
print(random_numbers)--OUTPUT--[42, 27, 93, 11, 65, 87, 32, 18, 59, 74]
This approach uses a list comprehension, which is a compact way to build a list. It works by:
- Looping ten times using
for _ in range(10). - Calling
random.randint(1, 100)on each loop to get a random integer. - Adding each new integer directly into the list being created.
This method is both Pythonic and efficient, letting you generate the entire list in a single, readable line of code.
Basic random number generation techniques
While list comprehensions are a great shortcut, it's also helpful to understand the more traditional methods and specialized functions in Python's random module.
Using a for loop to generate random numbers
import random
random_numbers = []
for _ in range(10):
random_numbers.append(random.randint(1, 100))
print(random_numbers)--OUTPUT--[37, 82, 19, 56, 45, 91, 23, 68, 12, 77]
This classic for loop approach is more explicit than a list comprehension, breaking the process into clear, sequential steps. While slightly more verbose, it clearly shows how the list is built one element at a time.
- First, you initialize an empty list with
[]. - The loop then iterates a set number of times.
- On each pass,
random.randint()generates a number, and theappend()method adds it to your list.
Using random.sample() for unique random numbers
import random
random_numbers = random.sample(range(1, 101), 10)
print(random_numbers)--OUTPUT--[27, 83, 41, 95, 12, 67, 38, 54, 9, 76]
When you need a list of random numbers without any duplicates, random.sample() is the perfect tool. It works by pulling a specified number of unique items from a larger sequence, ensuring no repeats.
- The first argument,
range(1, 101), defines the population to draw from—in this case, all integers from 1 to 100. - The second argument,
10, tells the function exactly how many unique numbers to select from that population.
This is ideal for scenarios where you can't have duplicates, like drawing lottery numbers or creating a unique set of test data.
Using random.choices() for weighted random selection
import random
weights = [1, 2, 3, 4, 5] * 20
random_numbers = random.choices(range(1, 101), weights=weights, k=10)
print(random_numbers)--OUTPUT--[85, 72, 93, 65, 81, 98, 73, 89, 77, 60]
Sometimes you need random numbers that aren't evenly distributed. The random.choices() function is perfect for this, allowing you to assign different probabilities to each number. This is called weighted random selection, and it allows duplicates in the final list.
- The
weightsparameter is a list where each weight corresponds to a number in the population. A higher weight means a higher chance of being picked. - The
kargument simply tells the function how many numbers you want to generate in total.
Advanced random number techniques
Building on those fundamentals, you can gain more power by using NumPy for performance, generating floating-point numbers, and making your random sequences reproducible.
Using NumPy for efficient random number generation
import numpy as np
random_numbers = np.random.randint(1, 101, size=10).tolist()
print(random_numbers)--OUTPUT--[53, 18, 92, 37, 64, 81, 25, 70, 46, 89]
For generating large lists of random numbers, the NumPy library offers a significant performance boost. It's a powerful package for numerical computing that's much faster than standard Python for these kinds of tasks.
- The function
np.random.randint(1, 101, size=10)generates an array of 10 integers from 1 to 100. - Because NumPy creates its own array type, you'll use the
.tolist()method to convert it back into a standard Python list.
This method is ideal when working with large datasets where efficiency is key.
Generating random floating-point numbers
import random
random_floats = [random.uniform(0, 1) for _ in range(10)]
print(random_floats)--OUTPUT--[0.23, 0.67, 0.41, 0.95, 0.12, 0.88, 0.54, 0.36, 0.79, 0.05]
Generating random decimals, or floating-point numbers, is just as straightforward. The key is the random.uniform() function, which you can use inside a list comprehension to quickly create a list of floats.
- This function returns a random floating-point number between two endpoints you provide.
- For example,
random.uniform(0, 1)will produce a value anywhere between 0 and 1, inclusive. This is perfect for simulations or generating normalized data points.
Using seeds for reproducible random numbers
import random
random.seed(42)
random_numbers = [random.randint(1, 100) for _ in range(10)]
print(random_numbers)--OUTPUT--[24, 62, 93, 57, 14, 72, 37, 86, 51, 29]
The numbers computers generate aren't truly random—they're pseudorandom, created by a deterministic algorithm. The random.seed() function gives this algorithm its starting point. By providing a specific seed, like 42 in the example, you guarantee that the exact same sequence of "random" numbers will be produced every time the code runs.
- This makes your results reproducible, which is invaluable for debugging or testing.
- You can use any integer for the seed; the important part is that using the same seed will always yield the same output.
Move faster with Replit
Replit is an AI-powered development platform that transforms natural language into working applications. Describe what you want to build, and Replit Agent creates it—complete with databases, APIs, and deployment.
For the random number generation techniques we've explored, Replit Agent can turn them into production-ready tools.
- Build a lottery number generator that uses
random.sample()to draw unique winning numbers. - Create a game's loot box simulator where item drops are determined by weighted probabilities with
random.choices(). - Deploy a data simulation tool that generates large datasets for performance testing using NumPy's efficient functions.
Describe your app idea, and Replit Agent writes the code, tests it, and fixes issues automatically, all in your browser.
Common errors and challenges
Even with Python's simple tools, a few common pitfalls can trip you up when generating random numbers.
- Incorrect seed placement for
random.seed() - A classic mistake is calling
random.seed()inside a loop. This resets the random number generator with every iteration, causing it to produce the same number repeatedly instead of a random sequence. To ensure reproducibility for your entire list, you should callrandom.seed()just once at the beginning of your script. - Misunderstanding
randint()range parameters - It's easy to get the range wrong with
random.randint(a, b). Unlike many Python functions where the upper bound is exclusive,randint()is inclusive, meaning the results can include bothaandb. This misunderstanding often leads to off-by-one errors, where your generated numbers don't fall within the exact range you intended. - Forgetting to handle random module's thread safety
- Python's built-in
randommodule is not thread-safe, which can cause issues in multi-threaded applications. Because concurrent threads share the same generator state, their calls can interfere with one another, leading to unpredictable results. The solution is to create a separaterandom.Random()instance for each thread, guaranteeing their random sequences remain independent.
Incorrect seed placement for random.seed()
A classic mistake is placing random.seed() inside a function that you call repeatedly. This resets the random number generator with every call, forcing it to produce the same sequence of numbers instead of a new one. The code below demonstrates this common pitfall.
def generate_numbers():
random.seed(42) # Seed inside function
return [random.randint(1, 100) for _ in range(5)]
import random
first_set = generate_numbers()
second_set = generate_numbers()
print("First set:", first_set)
print("Second set:", second_set) # These will be identical!
Because random.seed(42) is inside the generate_numbers() function, the generator resets every time the function is called. This forces the sequence to restart, making both lists identical. See the corrected approach in the code below.
import random
random.seed(42) # Seed once at the beginning
def generate_numbers():
return [random.randint(1, 100) for _ in range(5)]
first_set = generate_numbers()
second_set = generate_numbers()
print("First set:", first_set)
print("Second set:", second_set) # Now different
By moving random.seed(42) outside the function, you initialize the generator just once at the start of your script. This allows the generate_numbers() function to pull from a continuous sequence of pseudorandom numbers.
The first call gets one set, and the second call gets the next set in the sequence, ensuring they are different. This is crucial for testing scenarios where you need reproducible yet distinct sets of random data across multiple function calls.
Misunderstanding randint() range parameters
It's a classic off-by-one error. Many Python functions exclude the upper bound of a range, but random.randint() is inclusive. Forgetting this means your generated numbers might never reach the maximum value you intended. The following code demonstrates this common mistake.
import random
# Trying to get numbers 1-100 but mistakenly excluding 100
numbers = [random.randint(1, 99) for _ in range(5)]
print(numbers) # Will never contain 100
By setting the upper bound to 99, the code guarantees the number 100 will never be generated. This happens because random.randint() treats both its start and end arguments as possible results. The corrected approach is shown below.
import random
# Both bounds are inclusive in randint
numbers = [random.randint(1, 100) for _ in range(5)]
print(numbers) # Can contain 100
The corrected code works because random.randint(1, 100) is inclusive, meaning it can return both 1 and 100. This is easy to forget if you're used to range(), which excludes its endpoint. Keep an eye on this whenever you use randint() to avoid subtle off-by-one errors. A quick check of your arguments ensures your numbers always cover the full range you actually need.
Forgetting to handle random module's thread safety
Python's default random module isn't thread-safe, which can cause unpredictable behavior in multi-threaded applications. When concurrent threads call functions like random.randint(), they can interfere with the shared generator state, corrupting the output. The following code demonstrates this issue.
import random
import threading
def generate_random_numbers():
return [random.randint(1, 100) for _ in range(5)]
threads = [threading.Thread(target=generate_random_numbers) for _ in range(5)]
for t in threads:
t.start()
The threads all call generate_random_numbers concurrently, creating a race condition for the shared generator state. This can lead to corrupted or non-random output. The following snippet shows how to resolve this issue.
import random
import threading
import time
local_random = random.Random() # Create thread-local instance
def generate_random_numbers():
return [local_random.randint(1, 100) for _ in range(5)]
threads = [threading.Thread(target=generate_random_numbers) for _ in range(5)]
for t in threads:
t.start()
The fix is to create a separate generator instance using random.Random(). This gives each thread its own isolated random number generator, so they no longer share and corrupt a single state. This approach prevents race conditions and ensures each thread produces a clean, independent sequence. It's essential for any multi-threaded application that relies on random data, such as web servers handling concurrent requests or parallel data processing tasks.
Real-world applications
Beyond the syntax and error handling, these random number functions power everything from simple games to advanced scientific models.
Simulating a dice game with random.randint()
The random.randint() function is a natural fit for simulating a dice game, where each roll is just a random integer between 1 and 6.
import random
def roll_dice(num_dice=2):
return [random.randint(1, 6) for _ in range(num_dice)]
dice_results = roll_dice()
print(f"You rolled: {dice_results}, sum: {sum(dice_results)}")
This code defines a flexible roll_dice function that's built around a list comprehension. This compact structure repeatedly calls random.randint(1, 6) to generate a list of outcomes, simulating multiple dice rolls at once.
- The function’s
num_diceparameter controls how many rolls are generated, defaulting to two if you don't provide a number. - Finally, the script calls the function, captures the list of rolls, and prints both the individual results and their total sum using an f-string.
Using random.uniform() for Monte Carlo π estimation
The random.uniform() function is perfect for running a Monte Carlo simulation, a method that can approximate π by generating thousands of random points and checking their position relative to a circle.
import random
def estimate_pi(num_points=1000):
points_in_circle = 0
for _ in range(num_points):
x, y = random.uniform(-1, 1), random.uniform(-1, 1)
if x**2 + y**2 <= 1:
points_in_circle += 1
return 4 * points_in_circle / num_points
pi_estimate = estimate_pi(10000)
print(f"π estimate: {pi_estimate} (actual π: 3.14159...)")
This function estimates π by comparing the area of a circle to the square that contains it. It generates thousands of random (x, y) coordinates, with each point falling somewhere inside a 2x2 square centered at the origin.
- The condition
x**2 + y**2 <= 1uses the equation for a circle to determine if a random point lands inside the unit circle inscribed within that square. - The final calculation,
4 * points_in_circle / num_points, approximates π based on the ratio of points that landed inside the circle versus the total generated.
Get started with Replit
Put these techniques into practice with Replit Agent. Describe a tool like, “Build a web app that generates unique lottery numbers,” or “Create a dice roll simulator for tabletop games,” and watch it come to life.
The agent writes the code, tests for errors, and deploys your app. Start building with Replit and bring your ideas from concept to a live application in minutes.
Create and deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.
Create & deploy websites, automations, internal tools, data pipelines and more in any programming language without setup, downloads or extra tools. All in a single cloud workspace with AI built in.


.png)
.png)