random.randint / randrange
randint(a, b) is literally randrange(a, b + 1). Both draw exactly uniform integers with getrandbits() and rejection, work for arbitrarily large ranges, and refuse floats since Python 3.12.
Common call
random.randint(1, 6)
Returns
int, both ends included (randrange: stop excluded)
Replaces
int(random.random() * n) + 1
Watch out
randint(5, 1) fails with a message about randrange(5, 2)
random.randint(a, b) / random.randrange(start, stopstop — randrange: excluded upper (or lower, for a negative step) bound.type: int · default: None=None, stepstep — randrange: spacing of the candidates; may be negative, never 0. Needs an explicit stop.type: int · default: 1=1)
→ int
Demo
Live evaluation
Eight integers from a to b inclusive. Try a > b to see the error message.
Try:
Inputs
seedint | stran int or a string
aintlowest
binthighest
Code
import random random.seed(42) [random.randint(1, 10) for _ in range(8)]
Result
[2, 1, 5, 4, 4, 3, 2, 9]
randint(5, 1) calls randrange(5, 2), and that is the call the error names: "empty range in randrange(5, 2)". A negative step counts down from start, so randrange(10, 0, -2) picks from 10, 8, 6, 4, 2 and never 0. Step 0 and a step pointing away from stop are both ValueErrors.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| a, b | int | yes | randint: the lowest and highest possible result. b must be >= a. |
| start | int | yes | randrange: with one argument this is stop and start is 0, like range(stop). |
| stop | int | no (None) | randrange: excluded upper (or lower, for a negative step) bound. |
| step | int | no (1) | randrange: spacing of the candidates; may be negative, never 0. Needs an explicit stop. |
Return value
int — randint: a <= N <= b. randrange: an element of range(start, stop, step).
Common patterns
Roll dice
Both ends included.
import random roll = random.randint(1, 6) three_d6 = sum(random.randint(1, 6) for _ in range(3))
A random index
randrange(len(seq)) can never be out of range (but choice(seq) is simpler for the item).
import random i = random.randrange(len(items))
An even number from 0 to 100
Step 2 with stop 101 includes 100.
import random even = random.randrange(0, 101, 2)
A random big id
Arbitrarily large ranges work (getrandbits rejection sampling) — but use secrets for anything guessable.
import random row_id = random.randrange(10 ** 30)
Examples
1. randint includes both ends
import random
random.seed(42)
[random.randint(1, 10) for _ in range(8)]
Returns
[2, 1, 5, 4, 4, 3, 2, 9]2. randrange(n): 0 to n - 1
import random
random.seed(42)
[random.randrange(10) for _ in range(8)]
Returns
[1, 0, 4, 3, 3, 2, 1, 8]3. randrange with a step
import random
random.seed(42)
[random.randrange(0, 100, 5) for _ in range(6)]
Returns
[15, 0, 40, 35, 35, 20]4. Same draws: randint(a, b) is randrange(a, b + 1)
import random
random.seed(42)
[random.randrange(1, 7) for _ in range(5)]
Returns
[6, 1, 1, 6, 3]5. Huge ranges are exact
import random
random.seed(42)
random.randint(10 ** 20, 10 ** 20 + 5)
Returns
1000000000000000000056. a > b
import random
random.randint(5, 1)
Returns
ValueError: empty range in randrange(5, 2)7. Floats are rejected (3.12+)
import random
random.randint(1, 6.0)
Returns
TypeError: 'float' object cannot be interpreted as an integer8. step needs an explicit stop
import random
random.randrange(10, step=2)
Returns
TypeError: Missing a non-None stop argumentPitfalls
1. randrange(1, 6) for a die
randrange excludes stop, like range(), so 6 never comes up. randint(1, 6), or randrange(1, 7), includes it.
randrange(1, 6)
import random random.seed(42) rolls = [random.randrange(1, 6) for _ in range(6000)] (min(rolls), max(rolls))
(1, 5)
randint(1, 6)
import random random.seed(42) rolls = [random.randint(1, 6) for _ in range(6000)] (min(rolls), max(rolls))
(1, 6)
2. Passing a float bound
Since 3.12 there is no silent int() conversion: 6.0 from a division or float() input raises TypeError. Convert explicitly.
n = 12 / 2
import random n = 12 / 2 random.randint(1, n)
TypeError: 'float' object cannot be interpreted as an integer
n // 2
import random random.seed(42) n = 12 // 2 random.randint(1, n)
6
3. randrange(0) or an empty range
There is nothing to pick from range(0) or range(5, 5); randrange raises instead of returning a default.
randrange(0)
import random random.randrange(0)
ValueError: empty range for randrange()
guard the empty case
import random n = 0 random.randrange(n) if n > 0 else 'nothing to pick'
'nothing to pick'
When to use
Use it
- Dice, card indexes, random ids in a range
- Exactly uniform integers, including huge ranges like randrange(10 ** 30)
- Every k-th value: randrange(start, stop, k)
Reach for something else
- Picking an item → choice(seq)
- Several distinct ints → sample(range(a, b + 1), k)
- Security codes and PINs → secrets.randbelow(n)
Notes
CPython impl
Lib/random.py: randint returns self.randrange(a, b+1); randrange converts its arguments with operator.index() and draws _randbelow(n), which calls getrandbits(n.bit_length()) until the value is below n — exact integer arithmetic, identical on every platform
Messages
The ValueError shows the arguments as you passed them: empty range in randrange(5, 5) / zero step for randrange() / empty range for randrange() for a single argument <= 0
Keywords
The docs warn against keyword arguments: randrange(start=100) is interpreted as randrange(0, 100, 1)
FAQ
Yes, randint(a, b) can return both a and b. randrange(a, b) excludes b, like range(a, b).