I’m trying to generate random numbers in JavaScript for a small project, but the results don’t seem truly random or within the range I expect. I’ve tried using Math.random with different formulas, but I’m getting duplicates and sometimes numbers outside my intended min and max. Can someone explain the proper way to generate a random integer in a specific range, and what common mistakes I should avoid?
The short version: your formula is off, or your expectations are off. Math.random works fine for most JS projects.
Core facts:
• Math.random returns a float x where 0 <= x < 1
• It never returns 1
• You need to scale and floor it correctly
Common patterns:
- Integer from 0 to max - 1
function randInt(max) {
return Math.floor(Math.random() * max);
}
// 0 to 9
const n = randInt(10);
This gives uniform distribution across [0, max - 1]. If you see numbers repeat, that is normal randomness, not a bug.
- Integer from min to max, inclusive
function randIntRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 5 to 12, inclusive
const n = randIntRange(5, 12);
Common mistakes:
Wrong: Math.round(Math.random() * max)
Problem: edges get hit less often, distribution is biased.
Wrong: Math.floor(Math.random() * (max)) + min
Problem: this gives range [min, min + max - 1], not [min, max].
Range sanity check:
If you use:
Math.floor(Math.random() * (max - min + 1)) + min
Test a quick histogram:
const counts = {};
for (let i = 0; i < 100000; i++) {
const v = Math.floor(Math.random() * 10); // 0..9
counts[v] = (counts[v] || 0) + 1;
}
console.log(counts);
You should see each bucket around 10k. They will not be exact, but close.
About “not truly random”:
• Math.random is pseudo random, not crypto secure
• For normal games, UI, small projects, it is fine
• For security stuff, use:
crypto.getRandomValues(new Uint32Array(1))[0]
or in Node:
crypto.randomInt(min, max)
If you expect no duplicates in a small sample, that expectation is wrong. For example, if you generate 100 random ints from 0 to 9, you will see lots of repeats. That is normal.
If you want unique values, do not rely on random alone. Do one of these:
- Shuffle an array, then read it in order:
function shuffle(arr) {
for (let i = arr.length - 1; i > 0; i–) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
const arr = shuffle([0,1,2,3,4,5,6,7,8,9]);
- Track used numbers in a Set and redraw until unused:
const used = new Set();
function uniqueRandInt(max) {
if (used.size >= max) throw new Error(‘no numbers left’);
let n;
do {
n = Math.floor(Math.random() * max);
} while (used.has(n));
used.add(n);
return n;
}
So, checklist for you:
• If you want 0..n-1, use Math.floor(Math.random() * n)
• If you want min..max inclusive, use Math.floor(Math.random() * (max - min + 1)) + min
• Do not use Math.round for ranges
• Expect repeats, randomness means repeats
• For secure randomness, use crypto APIs
If you post your exact formula and range, people can point to the exact bug in one line.
You’re probably not seeing a Math.random problem so much as a “humans are bad at intuition about randomness” problem.
@sternenwanderer already covered the basic formulas nicely, so I’ll hit different angles:
1. Verify your range with hard checks
Instead of eyeballing, log min/max over many runs:
function randIntRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let minSeen = Infinity;
let maxSeen = -Infinity;
for (let i = 0; i < 100000; i++) {
const n = randIntRange(5, 12);
if (n < minSeen) minSeen = n;
if (n > maxSeen) maxSeen = n;
}
console.log({ minSeen, maxSeen });
If minSeen is 5 and maxSeen is 12, your range math is correct, even if your gut says “I’m getting too many 7s.”
2. Check for bias in your formula, not in Math.random
Common subtle bugs that look “almost” right:
// off-by-one:
Math.floor(Math.random() * max) + min;
// gives [min, min + max - 1], not [min, max]
Or conditionals that skew distribution:
let n = Math.floor(Math.random() * 10);
// 'fixing' out-of-range values in a weird way:
if (n === 9) n = 0; // now 0 is twice as likely
Anything like that will make some numbers feel too common.
3. Duplicates are required if you’re doing it right
If you generate 100 random ints in range 0–9:
- Expected count per value is ~10
- You will absolutely see repeats
- You will absolutely not see each number exactly 10 times
If you don’t get duplicates in a small range, something is wrong or you’re doing a shuffle / “unique draw” system instead of pure randomness.
If what you actually want is “no repeats until I’ve used everything,” that’s not randomness, that’s sampling without replacement. Then do:
- Shuffle an array of possible values once
- Read them in sequence
4. If it “never hits” the edges
People often complain “I never see min or max.” That’s usually because:
- They’re using a tiny sample (like 20 rolls).
- Or they have a bug like:
// tries to get 1..10
Math.floor(Math.random() * 10) + 1;
// this is actually correct, so if you 'never' see 10,
// you're just not rolling enough
If you truly never hit an edge in something like 100k trials, post the exact function. There’s almost always an off by one or some clamping logic hidden somewhere.
5. When “pseudo random” really is the problem
If you’re doing:
- Security tokens
- Lottery-like stuff with money
- Anything you’d be embarrased to see hacked
Then Math.random really is the wrong tool, and your sense that it’s “not truly random” is valid, but for security reasons, not for “I’m seeing duplicates.”
Use:
// browser
const buf = new Uint32Array(1);
crypto.getRandomValues(buf);
const n = buf[0]; // huge uniform int
Then do your own modulo / range logic carefully, or use crypto.randomInt in Node.
So the checklist I’d actually use, slightly different from @sternenwanderer:
- Write a tiny
randIntRange(min, max)and never inline the formula everywhere. - Stress test it with 100k+ calls and log min, max, and a basic histogram.
- Accept that “clumps” and repeats are normal.
- If you want uniqueness, stop calling it random and use a shuffle or a pool.
- Only blame Math.random itself if you’re doing security or crypto stuff.