probability
random selection
weighted choice
algorithms
decision-making

Random weighted choice

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

Random weighted choice means choosing one item at random, but not with equal probability. Each item has a weight, and larger weights make that item more likely to be selected.

The Core Idea

Suppose you have three items with weights 1, 2, and 7. The total weight is 10, so the items are chosen with probabilities 0.1, 0.2, and 0.7.

The standard approach is:

  1. compute the total weight
  2. generate a random number between 0 and that total
  3. walk through the items until the cumulative weight crosses the random number

That works because each item owns a segment of the total numeric range, and the random number lands inside one of those segments.

Simple Python Implementation

Here is a small implementation that works well for occasional sampling:

python
1import random
2
3
4def weighted_choice(items, weights):
5    total = sum(weights)
6    threshold = random.uniform(0, total)
7    cumulative = 0.0
8
9    for item, weight in zip(items, weights):
10        cumulative += weight
11        if threshold <= cumulative:
12            return item
13
14    return items[-1]
15
16
17fruits = ["apple", "banana", "orange"]
18weights = [1, 2, 7]
19
20print(weighted_choice(fruits, weights))

This algorithm is easy to understand and is good enough when the list is small or sampling happens infrequently.

Use the Standard Library When Available

In Python, the built-in solution is usually cleaner than writing your own helper. random.choices already supports weights.

python
1import random
2
3fruits = ["apple", "banana", "orange"]
4weights = [1, 2, 7]
5
6picked = random.choices(fruits, weights=weights, k=5)
7print(picked)

This is the best option for most application code because it is short, tested, and easy to read.

Scale Better With Prefix Sums

If you are sampling repeatedly from a fixed set of weights, build cumulative weights once and then use binary search for each draw. That reduces the selection step from a linear scan to logarithmic time.

python
1import bisect
2import itertools
3import random
4
5
6class WeightedSampler:
7    def __init__(self, items, weights):
8        self.items = items
9        self.cumulative = list(itertools.accumulate(weights))
10
11    def pick(self):
12        threshold = random.uniform(0, self.cumulative[-1])
13        index = bisect.bisect_left(self.cumulative, threshold)
14        return self.items[index]
15
16
17sampler = WeightedSampler(["A", "B", "C"], [5, 1, 4])
18print(sampler.pick())

This is a better design when the item set stays the same and you need many random draws, such as in simulations or recommendation experiments.

Why Weight Validation Matters

Weighted sampling only makes sense when weights are non-negative and the total weight is positive. A zero weight means the item should never be selected. Negative weights usually indicate a bug in the upstream logic.

Adding a validation step prevents silent nonsense:

python
1def validate_weights(weights):
2    if any(w < 0 for w in weights):
3        raise ValueError("weights must be non-negative")
4    if sum(weights) == 0:
5        raise ValueError("at least one weight must be positive")

That is worth doing whenever the weights come from user input, configuration, or a model output that may be malformed.

Performance Notes

For one-off selections, the simple cumulative scan is fine. For many selections from the same distribution, prefix sums plus binary search are better. For very high-throughput systems, there are more advanced structures such as the alias method, but they are usually unnecessary unless sampling speed is a major bottleneck.

The right algorithm depends on whether weights change often. If the weights are updated every draw, rebuilding a sampling structure may cost more than the optimization is worth.

Common Pitfalls

The first mistake is forgetting that weights are relative, not percentages. The values 1, 2, 7 and 10, 20, 70 produce the same distribution.

Another common issue is allowing negative weights or a total weight of zero. In that case, the distribution is undefined and the code should fail fast.

Floating-point edge cases can also appear when weights are extremely small or extremely large. For most application code this is fine, but numerically sensitive systems may need careful scaling or higher-precision arithmetic.

Finally, do not confuse sampling without replacement with weighted choice. The examples here select with replacement. If an item should disappear after selection, you need a different update step.

Summary

  • Weighted choice selects items with probability proportional to their weights.
  • A cumulative-weight scan is the simplest correct algorithm.
  • 'random.choices is the easiest Python solution for most code.'
  • Prefix sums plus binary search help when you sample many times from fixed weights.
  • Validate weights so negative or all-zero inputs fail clearly.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.