The Generator API (default_rng)
The modern, recommended way to make random numbers: rng = np.random.default_rng(seed) with rng.integers, rng.random, rng.normal and rng.choice.
Everything so far used the legacy functions (np.random.rand, np.random.seed …). They still work and you will see them in tutorials and old code, but since NumPy 1.17 the official recommendation is the Generator API. New projects — and most modern libraries — use it.
Definition
A
Generatoris an object that owns its own random state and exposes methods for every distribution. You create one withnp.random.default_rng(seed)and call methods on it:rng.integers(),rng.random(),rng.normal(),rng.choice(), …
- Owns its own state — instead of one hidden global state shared by your whole program, each generator object carries its own. Two generators never disturb each other.
- default_rng — "default random number generator". It currently uses the PCG64 algorithm, which is faster and statistically better than the legacy Mersenne Twister.
- Seeded object —
default_rng(42)is reproducible exactly likenp.random.seed(42), but the seed travels with the object.
Syntax
python
rng = np.random.default_rng(seed=None)
rng.integers(low, high=None, size=None, endpoint=False)
rng.random(size=None)
rng.normal(loc=0.0, scale=1.0, size=None)
rng.choice(a, size=None, replace=True, p=None)
rng.uniform(low, high, size) / rng.binomial(n, p, size) / rng.standard_normal(size)
rng.shuffle(x) / rng.permutation(x)| Parameter | Meaning | Default |
|---|---|---|
seed | Integer seed for reproducibility | None → fresh OS entropy |
low, high | Integer range for integers | high=None → range 0 .. low-1 |
endpoint | True makes high inclusive | False (exclusive, like randint) |
size | Output shape (int or tuple) — always a keyword/tuple, never separate ints | None → one value |
loc, scale | Mean and std for normal | 0.0, 1.0 |
a, replace, p | Same meaning as in np.random.choice | replace=True, p=None |
Example — the four core methods
python
import numpy as np
rng = np.random.default_rng(42)
print(rng.integers(1, 7, size=10)) # dice: high is EXCLUSIVE by default
print(rng.random(3)) # floats in [0, 1)
print(rng.normal(35, 12, size=4).round(1))
print(rng.choice(["CSK", "MI", "RCB", "KKR"], size=2, replace=False))text
[1 5 4 3 3 6 1 5 2 1]
[0.97562235 0.7611397 0.78606431]
[34.8 24.8 45.6 44.3]
['KKR' 'RCB']How it works: rng is created once with seed 42; every method call advances its state. Notice the numbers are different from np.random.seed(42) + randint — the two APIs use different algorithms, so the same seed does not give the same values across them.
Example — independent, reproducible generators
python
rng1 = np.random.default_rng(42)
rng2 = np.random.default_rng(42)
print(rng1.integers(0, 100, size=5))
print(rng2.integers(0, 100, size=5))
rng3 = np.random.default_rng(7)
print(rng3.integers(0, 100, size=5))text
[ 8 77 65 43 43]
[ 8 77 65 43 43]
[94 62 68 89 57]Two generators with the same seed give identical streams; using rng1 does not change what rng2 produces. With the legacy API, any library that called np.random.rand() behind your back would shift your sequence.
Example — more Generator methods
python
rng = np.random.default_rng(42)
print(rng.integers(1, 6, size=8, endpoint=True)) # 1..6 inclusive
print(rng.standard_normal(3).round(3))
print(rng.uniform(110, 180, size=3).round(1))
print(rng.binomial(14, 0.6, size=5))
order = np.array(["Rohit", "Virat", "Shubman", "Hardik"])
rng.shuffle(order)
print(order)
print(rng.permutation(5))text
[1 5 4 3 3 6 1 5]
[-1.951 -1.302 0.128]
[165. 119. 141.5]
[9 6 8 7 9]
['Virat' 'Rohit' 'Hardik' 'Shubman']
[1 0 2 4 3]endpoint=True is a nice touch: integers(1, 6, endpoint=True) reads exactly like "1 to 6" — no off-by-one.
Comparison — legacy vs Generator
| Task | Legacy (np.random.*) | Generator (rng = np.random.default_rng(seed)) |
|---|---|---|
| Seed | np.random.seed(42) (global) | rng = np.random.default_rng(42) (per object) |
| Uniform floats [0, 1) | np.random.rand(2, 3) / random((2, 3)) | rng.random((2, 3)) |
| Standard normal | np.random.randn(2, 3) | rng.standard_normal((2, 3)) |
| Normal(μ, σ) | np.random.normal(μ, σ, n) | rng.normal(μ, σ, n) |
| Integers | np.random.randint(1, 7, n) | rng.integers(1, 7, n) (or endpoint=True) |
| Sample items | np.random.choice(a, n, p=…) | rng.choice(a, n, p=…) |
| Shuffle / permute | np.random.shuffle(x) / permutation(x) | rng.shuffle(x) / rng.permutation(x) |
| Algorithm | Mersenne Twister (MT19937) | PCG64 (faster, better quality) |
| State | one hidden global state | each generator owns its state |
| Status | kept for backward compatibility | recommended for all new code |
Note: the same seed gives different numbers in the two APIs. If you follow an old tutorial that uses
np.random.seed, stay with the legacy calls to reproduce its exact output.
Common mistakes
- ❌
rng.randint(...)→AttributeError. ✅ The Generator method isrng.integers(...). - ❌
rng.random(2, 3)→TypeError. ✅ Shape as a tuple:rng.random((2, 3)). - ❌ Creating a new
default_rng(42)inside a function that is called many times — every call returns the same numbers. ✅ Create onerngat the top and pass it in. - ❌ Mixing
np.random.seed()withrngand expecting the seed to controlrng. ✅np.random.seedonly affects legacy functions.
Interview tip
Q: Why is np.random.default_rng preferred over np.random.seed?
A: It avoids global state — each Generator has its own reproducible stream, so libraries and functions can't accidentally change each other's random numbers. It also uses the faster, higher-quality PCG64 algorithm and has a cleaner API (integers with endpoint, consistent size arguments).
Key points to remember
- rng = np.random.default_rng(seed) creates a Generator — the modern, recommended random API.
- Each Generator owns its own state, so generators and libraries don't interfere with each other.
- Core methods: rng.integers, rng.random, rng.normal, rng.choice (plus uniform, binomial, shuffle, permutation).
- rng.integers excludes high by default; endpoint=True makes it inclusive.
- Shapes are always passed as size= or a tuple: rng.random((2, 3)).
- The same seed gives different numbers in the legacy API and the Generator API.
- Create one rng at the top of your program and pass it around instead of re-seeding.
Try it yourself
Generator basics
Legacy vs Generator for the same jobs
Pass one rng into your functions
Practice: dice with endpoint
Revision Flashcards
Tap a card to flip it and see the answer.
