JavaScript’s Math.random() returns a pseudo-random decimal from 0 up to, but less than 1.
Math.random() feels mysterious when you first meet it. You call one tiny function, and out comes a number that looks fresh every time. The trick is that JavaScript is not pulling raw chance out of thin air. It is asking the engine for the next value in a pseudo-random sequence, then handing that value back as a floating-point number between 0 and 1.
That one detail explains almost everything people get stuck on. It explains why the result can be scaled into dice rolls, card picks, and shuffled lists. It explains why the same call is fine for games and UI flourishes but wrong for passwords and reset tokens. It also explains why Math.random() can look messy in tiny samples and still behave well across a large batch.
What Math.random() Actually Returns
Math.random() takes no arguments. Each call gives you a decimal number that is greater than or equal to 0 and lower than 1. You will never get 1 itself. You might get 0, though hitting that exact value is rare.
The output is a JavaScript number, so you are dealing with floating-point math. That matters when you turn the raw decimal into a range. A line like Math.random() * 10 gives you a value from 0 up to, but not including, 10. If you need whole numbers, you still have one more step to take.
- Raw output: a decimal in the half-open range [0, 1)
- Shape over time: close to even across many calls
- Input: none
- User control over the seed: none in plain JavaScript
How Does Math.random Work? Inside A JavaScript Engine
The engine keeps an internal state for its random number generator. When you call Math.random(), the engine runs that state through a fixed set of math steps, updates the state, and turns part of the result into a decimal from 0 to less than 1. Call it again, and the engine repeats the cycle with the new state.
So the numbers are pseudo-random, not truly random. If you started with the same seed and the same algorithm, you would get the same sequence again. In day-to-day JavaScript, you do not get seed control for Math.random(), which is why the sequence feels fresh from one page load to the next.
From State To Decimal
- The engine starts with a hidden seed and internal state.
- Your call asks for the next value in the sequence.
- The state changes through the engine’s chosen algorithm.
- Part of that result is mapped to a floating-point number in the range JavaScript promises.
The ECMAScript language specification leaves the exact algorithm up to the engine. The contract is the output range and the near-even spread, not one fixed formula for every browser. That is why engine teams can swap algorithms over time while keeping everyday code working the same way.
| Part | What It Means | Why You Care |
|---|---|---|
| Range | Returns 0 or more, but never 1 | Upper bounds need care when you build integer ranges |
| Pseudo-randomness | Values come from a computed sequence | Good enough for many app tasks, wrong for secrets |
| Hidden seed | The engine picks the starting state | You cannot reset it with plain Math.random() |
| Internal state | Each call mutates stored generator data | The next output depends on the previous one |
| Distribution | Spread should be close to even over many calls | Small samples can still look streaky |
| Floating-point output | The result is a decimal number | You need extra math for dice rolls or index picks |
| Algorithm choice | Each engine can choose its own generator | You should not rely on one engine’s hidden internals |
| Security status | Not meant for cryptographic use | Passwords, tokens, and secret links need another API |
If you want the plain reference wording, MDN’s Math.random reference spells out the return range, the seed rule, and the warning against security use in one place. That matches how working developers treat it: handy, fast to type, and safe only for the right jobs.
Turning The Decimal Into Useful Results
Most code never uses the raw decimal for long. It turns that decimal into a range that matches a task. Once you see the pattern, the function stops feeling magical and starts feeling mechanical.
Picking A Whole Number From Zero Up To A Limit
Use Math.floor(Math.random() * max). If max is 6, the result can be 0, 1, 2, 3, 4, or 5.
const roll = Math.floor(Math.random() * 6);
That pattern works because multiplying by 6 stretches the [0, 1) range into [0, 6), and Math.floor() cuts that range into six equal buckets.
Picking A Whole Number Between Two Limits
When you need an integer from min up to but not including max, shift the range after scaling it.
const value = Math.floor(Math.random() * (max - min) + min);
Say min is 10 and max is 15. The result can be 10, 11, 12, 13, or 14. If you want both ends included, the formula changes a bit, and you need to add 1 to the span before flooring.
Picking A Random Item From An Array
Arrays use zero-based indexes, so the same integer pattern fits neatly.
const index = Math.floor(Math.random() * items.length);
const item = items[index];
This is where the range rule matters. Since Math.random() never returns 1, the index never reaches items.length. That keeps you inside the array.
Math.random In Real Code And Common Traps
Most bugs around random code come from range mistakes, not from the generator itself. A few habits save a lot of head-scratching.
- Do not use
Math.round()for integer ranges. It makes edge values less likely or more likely, depending on the setup. - Do not forget the upper bound that stays out.
Math.floor(Math.random() * 10)stops at 9. - Do not trust tiny samples. Five rolls can clump. That is normal.
- Do not use it for repeatable tests. Pick a seeded generator when you need the same run again.
- Do not shuffle with
array.sort(() => Math.random() - 0.5). The result is biased and engine behavior can get quirky.
| Task | Solid Pattern | Watch Out For |
|---|---|---|
| 0 to max-1 integer | Math.floor(Math.random() * max) |
Using Math.round() |
| min to max decimal | Math.random() * (max - min) + min |
Forgetting the upper end stays out |
| Random array item | items[Math.floor(Math.random() * items.length)] |
Empty arrays |
| Dice roll 1 to 6 | Math.floor(Math.random() * 6) + 1 |
Off-by-one errors |
| Repeatable test data | Use a seeded PRNG package | Expecting Math.random() to replay a run |
When Math.random() Is The Wrong Tool
Math.random() is fine for a confetti burst, a placeholder avatar hue, a card shuffle in a casual game, or a mock data set. It is the wrong pick when a guessable value could hurt someone or break trust.
Do not use it for password reset links, session IDs, invite codes, access tokens, one-time tokens, or secret links. The browser gives you a stronger option through Crypto.getRandomValues(), which fills a typed array with cryptographically strong random values. That API exists for jobs where predictability is not acceptable.
There is another case where Math.random() falls short: reproducible work. If you are building tests, simulations, or generative art where one seed should replay the same output, you need a seeded generator you can control. Math.random() keeps that seed hidden, so it cannot give you stable reruns on demand.
A Mental Model That Sticks
The easiest way to hold Math.random() in your head is this: every call asks for the next decimal from a hidden number machine. The machine keeps state, updates that state, and hands you a value in the range JavaScript promises. Your job is to map that decimal into the shape you need without bending the odds by mistake.
Once that clicks, the usual recipes stop feeling like copy-paste spells.
- Need a raw decimal? Use it as is.
- Need an integer bucket? Scale, then floor.
- Need a list pick? Turn the decimal into an index.
- Need replayable randomness? Use a seeded generator.
- Need secret-grade randomness? Use Web Crypto, not Math.random().
That is the whole story in plain terms. Math.random() is a small API with a lot of mileage, as long as you treat it like a pseudo-random generator and not a source of secure chance.
References & Sources
- Ecma International / TC39.“ECMAScript Language Specification: Math.random.”Defines the return contract for Math.random() and leaves the exact algorithm up to the engine.
- MDN Web Docs.“Math.random().”Explains the output range, seed handling, scaling patterns, and the warning against security use.
- MDN Web Docs.“Crypto: getRandomValues() method.”Describes the browser API for cryptographically strong random values and when to use it instead.
