A random boolean — a coin flip that returns 1 or 0 about half the time — is the smallest building block for simulations and generative art. The function is one line; the useful part is confirming it lands on each value roughly equally and never returns anything else. This tutorial writes the function as an ES module, pins its rounding behavior with a Node test suite, prints a distribution, and paints the flips on a browser canvas.
Prerequisites
- Node.js 18 or later (validated with Node 26.4.0)
- Python 3 for the local static server (
python3ships with the Xcode Command Line Tools on macOS) make(pre-installed on macOS and most Linux distributions)- A Unix-like terminal (macOS, Linux, or WSL on Windows)
No third-party npm packages are used. If you have not set up an ES-module project before, see How to Create a JavaScript Module (Node and Browser).
Step 1: Create the project and its package file
The "type": "module" field lets Node treat .js files as ES modules, so import/export work with no build step.
Create the file
mkdir -p js-coinflip-101
cd js-coinflip-101
touch package.json
Add the code: js-coinflip-101/package.json
{
"name": "js-coinflip-101",
"version": "1.0.0",
"description": "A random 1/0 coin-flip function for JavaScript (Node and browser).",
"type": "module",
"scripts": {
"demo": "node demo.js",
"test": "node --test"
},
"license": "MIT"
}
Detailed breakdown
"type": "module"switches the package to ES modules; without it,importthrowsSyntaxError: Cannot use import statement outside a module.- The
demoandtestscripts alias the two commands used later; there are no runtime dependencies.
Step 2: Write the coinFlip function
Create the file
touch coinflip.js
Add the code: js-coinflip-101/coinflip.js
// File: coinflip.js
// Return a random 1 or 0, roughly 50/50.
export const coinFlip = () => Math.round(Math.random());
Detailed breakdown
Math.random()returns a float in the range[0, 1)— always at least0and always less than1.Math.round()maps that to an integer: values from0up to (but not including)0.5round to0, and values from0.5up to (but not including)1round to1. Each half of the range has the same width, so the two outcomes are equally likely.- In JavaScript
1is truthy and0is falsy, so the result doubles as a boolean when used in a condition. Step 3’s tests pin down both the boundary at0.5and the two ends.
Step 3: Pin the behavior with tests
Node’s built-in test runner (node --test, stable since Node 20) covers the parts that must always hold, with no framework to install.
Create the file
mkdir -p test
touch test/coinflip.test.js
Add the code: js-coinflip-101/test/coinflip.test.js
// File: test/coinflip.test.js
// Runs with the built-in Node test runner: `node --test`.
import { test } from "node:test";
import assert from "node:assert/strict";
import { coinFlip } from "../coinflip.js";
// Run `fn` with Math.random pinned to `value`, then restore it.
function withRandom(value, fn) {
const original = Math.random;
Math.random = () => value;
try {
return fn();
} finally {
Math.random = original;
}
}
test("always returns 1 or 0", () => {
for (let i = 0; i < 1000; i++) {
const result = coinFlip();
assert.ok(result === 0 || result === 1);
}
});
test("rounds below 0.5 down to 0", () => {
assert.equal(withRandom(0, coinFlip), 0);
assert.equal(withRandom(0.49, coinFlip), 0);
});
test("rounds 0.5 and above up to 1", () => {
assert.equal(withRandom(0.5, coinFlip), 1);
assert.equal(withRandom(0.9999, coinFlip), 1);
});
test("both outcomes appear over many flips", () => {
const seen = new Set(Array.from({ length: 500 }, () => coinFlip()));
assert.deepEqual([...seen].sort(), [0, 1]);
});
Detailed breakdown
withRandomreplaces the globalMath.randomwith a stub that returns a fixed value, runs the function, then restores the original in afinallyblock so a failed assertion cannot leak the stub into a later test.- The two rounding tests nail the
0.5boundary from both sides:0.49rounds to0,0.5rounds to1, and0.9999(the top of the range) still rounds to1, never to2. - The first test is a property check across 1000 flips (capturing the result once so it asserts on a single flip); the last collects 500 flips into a
Setand confirms both values showed up.
Run the suite:
node --test
✔ always returns 1 or 0
✔ rounds below 0.5 down to 0
✔ rounds 0.5 and above up to 1
✔ both outcomes appear over many flips
ℹ tests 4
ℹ pass 4
ℹ fail 0
Step 4: Print the distribution
A quick sanity check is to flip many times and count each outcome. With a fair coin the two counts should land near an even split.
Create the file
touch demo.js
Add the code: js-coinflip-101/demo.js
// File: demo.js
// Flip the coin 100 times and count how many 1s and 0s came up.
import { coinFlip } from "./coinflip.js";
const LIMIT = 100;
const flips = Array.from({ length: LIMIT }, () => coinFlip());
console.log(flips);
const occurrences = flips.reduce((acc, value) => {
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {});
console.log(occurrences);
Detailed breakdown
Array.from({ length: LIMIT }, () => coinFlip())collects 100 flips into an array.- The
reducetallies each value into a counts object; the counts always sum toLIMIT.
Run it:
node demo.js
The output varies on every run. One run produced:
[
1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 0,
0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0,
0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1,
1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1,
0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0,
1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1,
0, 0, 0, 0
]
{ '0': 44, '1': 56 }
An exact 50/50 split is rare; most runs land within a 40/60 spread, and a lopsided run now and then is expected with only 100 flips.
Step 5: Visualize the flips in the browser
A grid makes the balance obvious at a glance. This version fills a 10x10 grid with flips and paints each 1 green and each 0 black.
Start with the page. Create index.html:
Create the file
touch index.html
Add the code: js-coinflip-101/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>js-coinflip-101</title>
<link rel="stylesheet" href="./app.css" />
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
<script type="module" src="./app.js"></script>
</body>
</html>
Detailed breakdown
- The
width/heightattributes set the canvas drawing buffer to 300x300; the CSS sizes how it appears on screen. <script type="module">loadsapp.jsas an ES module so it canimportthe coin-flip function.<canvas>takes an explicit closing tag; it is not a void element and cannot self-close.
Add the stylesheet. Create app.css:
Create the file
touch app.css
Add the code: js-coinflip-101/app.css
/* File: app.css
* Center the canvas in the middle of the viewport.
*/
canvas {
padding: 0;
margin: auto;
display: block;
width: 400px;
height: 400px;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
Detailed breakdown
position: absolutewith all four offsets at0plusmargin: autocenters the canvas both ways.- The
400pxdisplay size scales up the 300x300 drawing buffer.
Now the drawing code. Create app.js:
Create the file
touch app.js
Add the code: js-coinflip-101/app.js
// File: app.js
// Fill a 10x10 grid with coin flips: green for 1, black for 0.
import { coinFlip } from "./coinflip.js";
const canvas = document.getElementById("canvas");
const SCREEN_SIZE = 300; // must match the canvas width/height attributes
const DIM = 10; // grid is DIM x DIM cells
const CELL_SIZE = SCREEN_SIZE / DIM;
const BORDER = 1;
const NEON_GREEN = "#39FF14";
// One coin flip per grid cell.
const cells = Array.from({ length: DIM * DIM }, () => coinFlip());
console.log(cells);
const ctx = canvas.getContext("2d");
if (ctx) {
ctx.clearRect(0, 0, SCREEN_SIZE, SCREEN_SIZE);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, SCREEN_SIZE, SCREEN_SIZE);
let cursor = 0;
for (let i = 0; i < DIM; i++) {
for (let j = 0; j < DIM; j++) {
ctx.fillStyle = cells[cursor++] ? NEON_GREEN : "black";
ctx.fillRect(
i * CELL_SIZE + BORDER,
j * CELL_SIZE + BORDER,
CELL_SIZE - BORDER * 2,
CELL_SIZE - BORDER * 2,
);
}
}
}
Detailed breakdown
DIM * DIMis 100, socellsholds one flip per grid cell.CELL_SIZEisSCREEN_SIZE / DIM(30 pixels);BORDERshrinks each painted square by one pixel per side so the black background reads as gridlines.- The ternary
cells[cursor++] ? NEON_GREEN : "black"paints a1green and leaves a0on the black background. With a fair coin, close to half the cells light up.
Step 6: Serve the page and drive it with a Makefile
Browsers block module imports over file://, so serve the folder over HTTP. A short Makefile wraps the demo, tests, and server.
Create the file
touch Makefile
Add the code: js-coinflip-101/Makefile
.DEFAULT_GOAL := help
SHELL := /bin/bash
PORT ?= 8000
.PHONY: help demo test serve
help: ## Show this help screen
@echo "Targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| sort \
| awk 'BEGIN {FS = ":.*?## "}; {printf " %-8s %s\n", $$1, $$2}'
demo: ## Flip the coin 100 times and print the distribution
@node demo.js
test: ## Run the Node test suite (node --test)
@node --test
serve: ## Serve the browser visualization at http://localhost:$(PORT)/
@echo "Serving on http://localhost:$(PORT)/ (Ctrl-C to stop)"
@python3 -m http.server $(PORT)
Detailed breakdown
.DEFAULT_GOAL := helpmakes a baremakeprint the help screen.PORT ?= 8000sets a default port a caller can override, e.g.make serve PORT=9000.
Confirm the help screen, then serve:
make
Targets:
demo Flip the coin 100 times and print the distribution
help Show this help screen
serve Serve the browser visualization at http://localhost:$(PORT)/
test Run the Node test suite (node --test)
Run make serve, open http://localhost:8000/ in a browser, and reload a few times. Each load repaints the grid with a fresh set of flips, and roughly half the cells glow green. Press Ctrl-C to stop the server.
Recap
The function is Math.round(Math.random()). The work around it is what makes it trustworthy: "type": "module" for clean imports, a node --test suite that stubs Math.random to pin the 0.5 boundary and both ends, a distribution demo, and a canvas grid to see the balance. When you need something other than a 50/50 split, reach for a weighted version instead.
Related articles
- How to Create a Weighted Random Function in JavaScript (Node and Browser)
- How to Select a Random Item from a JavaScript Array (Node and Browser)
- How to Create a JavaScript Module (Node and Browser)