Random Numbers in JavaScript: From Chaos to Order
\nImagine: you are writing a game where you need to randomly select a treasure chest from 1 to 100. Or, perhaps, you are creating an A/B test that selects a random variant for a user. It would seem, what could be simpler? But the devil, as always, is in the details. The question of generating random integers in JavaScript within a specific range is one of the most popular on Stack Overflow, and for good reason. Even experienced developers sometimes stumble over the pitfalls of Math.random().
In this article, we will break down not only the classic methods but also the best practices of 2025: how to make code not only functional but also secure, predictable (in a good way), and performant. Let's go!
\n\nThe Classic Approach: Math.random() and Its Pitfalls
\nLet's start with the fundamentals. The built-in function Math.random() returns a pseudo-random floating-point number in the range [0, 1). The key point: 0 is included, but 1 is not. This is an "unfair" distribution if you want to get integers.
Here is the most common (and most incorrect) method, often found in old textbooks:
\n// BAD example: probability bias\nfunction getRandomBad(min, max) {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n}\n\nAt first glance, everything seems correct. But let's dig deeper. Math.floor() rounds down. If Math.random() generates a number very close to 1 (but not equal), multiplying by (max - min + 1) will give a value less than max + 1, and Math.floor() will truncate it. However, due to the limited precision of floating-point numbers (IEEE 754), some values occur less frequently than others. For a range of 0-1, this is imperceptible, but for large ranges (e.g., from 1 to 1,000,000), the bias becomes statistically significant.
Conclusion: This formula works for small ranges but is not "cryptographically clean" and can cause distribution skew.
\n\nThe Correct Formula from MDN
\nMozilla Developer Network (MDN) recommends a more accurate approach that minimizes rounding errors:
\n// Good, universal method\nfunction getRandomIntInclusive(min, max) {\n const minCeiled = Math.ceil(min);\n const maxFloored = Math.floor(max);\n return Math.floor(Math.random() * (maxFloored - minCeiled + 1) + minCeiled);\n}\n\nThe difference is that we explicitly convert the boundaries to integers using Math.ceil() and Math.floor(). This ensures that min and max are handled correctly, even if you accidentally pass a fractional number. Use this method as the basic standard in 2025.
Alternatives: When Math.random() Is Not Enough
\nThe classic Math.random() is a pseudo-random number generator (PRNG). It is deterministic: if you know the initial seed, you can predict the entire sequence. For most web applications (animations, random content), this is sufficient. But there are scenarios where true randomness is needed:
- \n
- Security: generating tokens, passwords, CSRF tokens. \n
- Cryptography: encrypting data on the client side. \n
- Gambling: fair distribution of winnings. \n
In these cases, crypto.getRandomValues() comes to the rescue — a cryptographically secure random number generator (CSPRNG). It uses system entropy sources and is much less predictable.
Generating Integers with crypto.getRandomValues()
\nHere is what a modern implementation looks like in 2025:
\n// Secure generation of a random integer in the range [min, max]\nfunction getSecureRandomInt(min, max) {\n const range = max - min + 1;\n // Maximum value that is a multiple of range (to avoid bias