Migrate to RunxBuild and earn up to $50 in hosting credit on your first deposit.

Calculate your savings
unxBuild

random.choices in Python: Weights, Replacement, and When to Use sample Instead

Sean

Platform Writer

Jul 21, 2026
7 min read

random.choices(population, weights=..., k=...) returns a list of k items drawn from a population, and the two things that define it are: it supports weights, so some items are more likely, and it draws with replacement, so the same item can come up more than once. That second property is the one that bites. If you use random.choices to pick lottery numbers or deal cards, you will get duplicates, because with replacement is the whole design. For a draw without repeats you want random.sample.

random.choices in Python: Weights, Replacement, and When to Use sample Instead

It arrived in Python 3.6 to fill the gap between random.choice (one item) and rolling your own weighted picker. Knowing when it is the right tool is mostly about the replacement question.

Table of contents

The basic call and weights

import random

# Unweighted: k picks, each equally likely, WITH replacement
random.choices(["a", "b", "c"], k=5)
# e.g. ['b', 'b', 'a', 'c', 'b'] - repeats are expected

# Weighted: 'c' is far more likely
random.choices(["a", "b", "c"], weights=[1, 1, 8], k=5)
# 'c' dominates over many draws

weights are relative, not probabilities - they do not need to sum to 1. [1, 1, 8] means c is eight times as likely as a. There is also cum_weights for pre-computed cumulative weights, which is a micro-optimization when you call choices in a tight loop with the same weights.

With replacement is the defining behavior

Every draw is independent, from the full population, every time. That is exactly right for a loaded die or a weighted A/B assignment, and exactly wrong for anything where each item should appear at most once.

# WRONG: dealing 5 unique cards
hand = random.choices(deck, k=5)     # can deal the same card twice

# RIGHT: 5 distinct cards
hand = random.sample(deck, k=5)      # guaranteed no repeats

The rule is one sentence: if repeats are acceptable or intended, choices; if each pick must be unique, sample. Reaching for choices and then deduplicating the result is a sign you wanted sample all along.

choices vs choice vs sample

  • random.choice(seq) - exactly one item. No weights, no k.
  • random.choices(pop, weights, k) - k items, weighted, WITH replacement.
  • random.sample(pop, k) - k distinct items, WITHOUT replacement. Weighted sampling without replacement needs the counts parameter or a different approach.

Since Python 3.9, random.sample accepts a counts argument to model a population with repeats without materializing the whole list - sample(['red','blue'], counts=[100, 200], k=5) samples as if there were 100 reds and 200 blues, without building a 300-element list.

The security caveat still applies

Like everything in random, choices uses the Mersenne Twister and is not cryptographically secure. For a weighted raffle where money or fairness is on the line and an attacker could benefit from predicting the outcome, that predictability is a problem. The secrets module does not offer weighted selection directly, so a secure weighted draw needs secrets.randbelow over a cumulative-weight table built by hand.

For simulations, games, sampling telemetry, and load generation - the overwhelming majority of uses - random.choices is exactly the right tool and the security caveat does not apply. Just know which side of the line you are on.

A realistic use: weighted work distribution

import random

workers = ["small", "medium", "large"]
capacity = [1, 3, 6]   # large handles 6x the load of small

# Assign 100 incoming jobs proportional to capacity
assignments = random.choices(workers, weights=capacity, k=100)

This is a clean way to model proportional distribution - traffic shaping, sharding by capacity, sampling logs at different rates per source. The weights carry the intent, and k is however many decisions you need to make in one call.

How this fits the rest of the stack

Weighted-with-replacement versus distinct-without is a small decision that changes the result completely, which is a good description of capacity planning too: the difference between ‘looks about right’ and ‘sized to the workload’ is where the surprises live. The RunxBuild hosting calculator breaks a service into compute, memory, database, and bandwidth line items so the sizing is deliberate, and the RunxBuild dashboard shows the real distribution of load once the service is handling traffic.

Useful related references:

FAQ

What is the difference between random.choices and random.sample?

random.choices draws with replacement, so the same item can be selected more than once, and it supports weights. random.sample draws without replacement, guaranteeing distinct items, and is the right choice for things like dealing cards or picking lottery numbers. If repeats are acceptable use choices; if each item must be unique use sample.

How do weights work in random.choices?

weights are relative likelihoods, not probabilities, so they need not sum to 1. weights=[1, 1, 8] makes the third item eight times as likely as each of the first two. You can instead pass cum_weights with pre-computed cumulative weights, which is slightly faster when you repeatedly sample with the same weights in a loop.

Can random.choices return duplicates?

Yes, by design. It samples with replacement, so each of the k draws is independent and taken from the full population, which means the same element can appear multiple times. If you need k distinct elements, use random.sample instead, which samples without replacement and never repeats an item.

Is random.choices secure for a raffle or lottery?

No. It uses the Mersenne Twister, which is not cryptographically secure and can be predicted from enough observed output. For a draw where fairness or money is at stake and prediction would be an advantage, build a secure weighted selection using secrets.randbelow over a cumulative-weight table. For simulations and games the standard random.choices is fine.

How do I sample without replacement but with weights?

random.sample does not take weights directly, but since Python 3.9 it accepts a counts parameter to represent a population with repeats efficiently: random.sample([‘red’,‘blue’], counts=[100, 200], k=5). For genuinely weighted sampling without replacement you typically draw one weighted item at a time with random.choices and remove it, or use a library like NumPy.

#random.choices python#python#random#sampling#dev-infra