Generating Random Numbers
Randomness powers everything from shuffled playlists and dice rolls to particle systems and procedural generation. JavaScript’s everyday tool for this is Math.random(), which returns a floating-point number — but turning that single primitive into a random integer in a range, a random array element, or a fair shuffle takes a few well-known recipes. This page collects those recipes, explains the off-by-one traps that bite beginners, and shows when to reach for the cryptographically secure API instead.
The building block: Math.random
Math.random() returns a pseudo-random float in the half-open interval [0, 1) — that is, zero is possible but one never is. Every other recipe on this page is built by scaling and shifting this value.
console.log(Math.random()); // e.g. 0.4729103857...
console.log(Math.random()); // a different value each call
Because the range is [0, 1), multiplying by n gives a float in [0, n), which is exactly the shape you want before flooring into bucket indices.
Math.random()is not cryptographically secure and must never be used for passwords, tokens, session IDs, or anything an attacker should not predict. See the security section below.
Random float in a range
To get a float in [min, max), scale the [0, 1) value by the span and add the offset.
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
console.log(randomFloat(1, 5)); // e.g. 3.871...
console.log(randomFloat(-1, 1)); // e.g. -0.244...
The result includes min but excludes max, matching the convention of Math.random() itself.
Random integer in a range
The most common need is a whole number. The classic inclusive formula uses Math.floor on a scaled value. To make both min and max reachable, the span is (max - min + 1).
function randomInt(min, max) {
// inclusive of both min and max
return Math.floor(Math.random() * (max - min + 1)) + min;
}
console.log(randomInt(1, 6)); // a dice roll: 1..6
console.log(randomInt(0, 9)); // a single digit: 0..9
console.log(randomInt(-5, 5)); // negatives work too
Output:
4
7
-2
The + 1 is the part people forget. Without it the maximum value is unreachable, producing a subtle bias against the top of the range.
| Goal | Expression | Range |
|---|---|---|
| Float | Math.random() | [0, 1) |
| Float in range | Math.random() * (max - min) + min | [min, max) |
| Integer in range | Math.floor(Math.random() * (max - min + 1)) + min | [min, max] |
| Coin flip | Math.random() < 0.5 | true / false |
Picking a random array element
Reuse the integer recipe to index into an array. The valid index range is 0 to length - 1.
function randomItem(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
const colors = ["red", "green", "blue", "purple"];
console.log(randomItem(colors)); // e.g. "blue"
Here Math.floor(Math.random() * arr.length) already produces 0..length-1 because the upper bound is exclusive — no + 1 needed, and no risk of an out-of-bounds undefined.
Shuffling with Fisher-Yates
To randomize the order of an array, do not sort with Math.random() - 0.5. That trick is biased and produces uneven distributions because comparison sorts call the comparator inconsistently. The correct algorithm is Fisher-Yates, which walks from the end and swaps each element with a randomly chosen earlier (or current) one.
function shuffle(arr) {
// returns a new shuffled array, leaving the original intact
const result = [...arr];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
console.log(shuffle([1, 2, 3, 4, 5]));
Output:
[ 3, 1, 5, 2, 4 ]
Each of the n! orderings is equally likely, and the loop runs in linear time. Spreading into a copy keeps the function pure; drop the copy and operate on arr directly for an in-place shuffle.
Avoid
arr.sort(() => Math.random() - 0.5). It looks clever but the distribution is provably skewed and depends on the engine’s sort implementation. Always use Fisher-Yates for fairness.
Cryptographically secure randomness
When unpredictability matters — tokens, salts, game-fairness guarantees, anything security-sensitive — use the Web Crypto API’s crypto.getRandomValues(). It fills a typed array with values from a CSPRNG and is available in browsers and modern Node (via the global crypto).
// a secure integer in [0, max) without modulo bias for power-of-two ranges
function secureRandomInt(max) {
const arr = new Uint32Array(1);
crypto.getRandomValues(arr);
return arr[0] % max;
}
// a secure hex token
function token(bytes = 16) {
const arr = new Uint8Array(bytes);
crypto.getRandomValues(arr);
return [...arr].map(b => b.toString(16).padStart(2, "0")).join("");
}
console.log(secureRandomInt(100)); // e.g. 73
console.log(token()); // e.g. "9f2a1c...e4"
For a ready-made UUID, crypto.randomUUID() returns an RFC 4122 v4 string with no manual byte juggling. Note that the simple % max above introduces slight modulo bias for ranges that do not divide evenly into 2^32; for strict uniformity, reject-and-retry values in the unfair tail.
Best Practices
- Use
Math.random()for visuals, games, and simulations; never for security. - Memorize the inclusive integer formula
Math.floor(Math.random() * (max - min + 1)) + minand remember the+ 1. - Index arrays with
Math.floor(Math.random() * arr.length)— no+ 1, since indices stop atlength - 1. - Shuffle with Fisher-Yates, not
sort(() => Math.random() - 0.5). - Reach for
crypto.getRandomValues()orcrypto.randomUUID()whenever values must be unpredictable. - Wrap your recipes in named helper functions (
randomInt,randomItem,shuffle) so intent is clear and the off-by-one logic lives in one place.