The right way to generate a random number in JavaScript

OpenJavaScript 0
Reading Time: 3 minutes 🕑

Last updated: May 20, 2022.

There’s a lot of reason you’d want to generate a random number in JavaScript: randomizing the appearance of contents, shuffling an array, generating a password, and more.

The most common way is using the in-built Math.random(). But Math.random() does not generate cryptographically secure random numbers!

Therefore, depending upon the end-goal, methods on the crypto interface should also be used to create more strongly random passwords.

Table of contents

Weakly random but quick with Math.random()

The quick and simple way to generate a random number is using the Math.random() method. This method generates a number between 0 and 1 (not inclusive of 1).

So, to generate a random number between two values, the return of calling Math.random() is modified accordingly:

/* Use Math.random() to generate a random number (0-10) */

Math.random(); // Returns a value between 0 and <1

Math.floor(Math.random()*10); // Multiples result by 10 and rounds down (result: 0-9)

Math.floor(Math.random()*(10+1)); // Adds one to make return value 0-10

Externalizing this logic to a function creates a random number generator:

/* A random number generator using Math.random() */

function getRandomInt(max) {
  return Math.floor(Math.random() * (max+1) )
}

getRandomInt(3); // 0, 1, 2 or 3 

What's wrong with Math.random()?

The catch with Math.random() is that the number it generates is only weakly random.

This isn't a design flaw. When JavaScript was created, Math.random() was included to provide a utility function capable of quickly generating random numbers that appear like random to humans.

It was never intended to create cryptographically secure random numbers (i.e. numbers random enough that it is almost impossible for a third party to predict them).

So why hasn't it been replaced?

This is because it fulfills its intended purpose. Instead, there is an alternative.

Strongly random but slow with crypto.getRandomValues()

To create cryptographically secure randoms numbers, use the crypto interface available on the global object.

To do so, call the getRandomValues() method, passing in a typed array:

/* Get random values with crypto */

const typedArray = new Uint32Array(5); // Create new typed array object (length 5)

crypto.getRandomValues(typedArray); // Assign random values

console.log(typedArray);
{
    "0": 3445426891,
    "1": 550747900,
    "2": 612279700,
    "3": 1488281924,
    "4": 2641953635
}

console.log(typeof typedArray);
// object Uint32Array

To get random values between 0 and maximum value, you can get the remained of typedArray divided by the maximum value +1. This works because the remainder returned is never greater than max.

/* A random number generator using window.crypto */

function getRandomInt(max) {
  const typedArray = new Uint32Array(1);
  crypto.getRandomValues(typedArray);
  const res = typedArray % (max+1); // +1 because values should be inclusive of max value

  return res;
}

getRandomInt(3); // 0, 1, 2 or 3 

Speed test 🚀

The difference in speed in generating a single random number is significant: Math.random() is almost 10x faster than crypto.getRandomValues()!

The key takeaway is that there is a speed-to-randomness trade-off: if you want more of one, you will sacrifice the other.

Takeaway: Choose the right tool for the job!

If speed is important and random-like from a human perspective is good enough, you should probably use Math.random().

According to this rule of thumb, some good use cases for Math.random() would be:

  • Displaying a random image
  • Shuffling a playlist
  • In games

However, for tasks where randomness is a priority, using window.crypto() is strongly recommended.

For example:

  • Generating a password
  • Creating a security key
  • Lotto numbers

Summary

Math.random() is something of a misnomer: it generates what appears to us to be a very random number quickly. But its output is not generally considered cryptographically secure. In other words, the randomness of its output is weak enough that it could conceivably be predicted by a third party.

For a greater degree of randomness, use crypto.getRandomValues() instead. This does not produce perfectly random values (no generator can) and is slower. But, when security is a priority, it's the better alternative.

Related links