# Random vs Perlin noise in p5.js: from jumpy dots to flow fields

![Versions: p5.js 2.3.4, tested October 2026](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/378846d1-e89f-4b25-a01a-4a91f29e6265.png align="center")

> **Tested with:** p5.js 2.3.4 (the current release), Chrome, October 2026. Every sketch runs in the [p5.js Web Editor](https://editor.p5js.org/pedbad/collections/6WlV-G8W5): open one, press play, and press the keys listed under the canvas.

If you are working through generative art on CM2030 Graphics Programming, you will meet two functions that look similar and behave completely differently: `random()` and `noise()`. This post explains what Perlin noise is and how it is built, then builds up the difference in ten small sketches, numbered 00 to 09 from simple to challenging. They are all in one collection on the p5.js Web Editor:

**[Perlin noise (p5.js 2.x) collection](https://editor.p5js.org/pedbad/collections/6WlV-G8W5)**

Each section below shows the key code and links to its sketch. The sketches are written as tutor material to go with the course, not as course material, so they don't copy the course's code; they explain the same ideas in my own examples.

## What is Perlin noise?

### A little history

In 1982 Ken Perlin worked on the Disney film *Tron* at the animation company MAGI. He was frustrated by how "machine-like" computer graphics looked: perfectly smooth surfaces, perfectly clean edges. Real things such as marble, clouds, smoke and skin have detail that is irregular but not chaotic. So he invented a way to make that kind of detail with a formula, which he described in his 1985 SIGGRAPH paper "An Image Synthesizer". In 1997 he received an Academy Award for Technical Achievement for it. He later designed a faster successor, simplex noise (2001), and published an improved version of the original (2002).

### From random to noise, step by step

**Start with `random()`.** Each call ignores the one before, so a graph of `random()` is all spikes. Nothing in nature looks like that.

![160 values from random(), drawn one after another: a jagged line of spikes with no shape](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/fc94b89d-3bc5-4dc4-8591-a0fa7d4e6e24.png align="center")

**Step 1: random values only at whole numbers.** Choose one random height at 0, 1, 2, 3 and so on, and nothing in between yet. This grid of points is called the *lattice*.

![Random heights between 0 and 1 at the whole numbers 0 to 8, shown as dots on stalks](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/5a4025c1-e059-4f66-b0d7-b4ce2d1b43a0.png align="center")

**Step 2: join the dots smoothly.** Straight lines between the dots would give sharp corners. An S-shaped curve gives smooth hills instead, so inputs that are close together give outputs that are close together. That is the whole idea: random at a coarse scale, smooth in between.

![The same dots joined twice: dashed straight lines with sharp corners, and a smooth violet curve through every dot](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/c8d6ea34-eb59-48b5-a852-4887e4e070a1.png align="center")

**Perlin's version: random slopes, not heights.** Ken Perlin's own method, called *gradient noise*, gives every whole number a random *slope* instead of a random height. The curve passes the middle value at each whole number, tilted by that slope, and is blended smoothly in between. This hides the regular grid better.

![Gradient noise: at each whole number the curve crosses the middle line, tilted to match a short black slope marker](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/c9dd2582-f682-4386-9798-42e1d07c76ac.png align="center")

> **How p5.js does it:** p5.js's `noise()` uses steps 1 and 2: random *heights* at whole numbers, joined with a cosine S-curve. That is called *value noise*, a simpler cousin of Perlin's gradient noise. You can check this in the p5.js source code. For sketching it behaves the same way, and p5.js keeps the name "Perlin noise", but now you know the difference.

### Octaves: adding detail

One smooth layer looks like soft, blobby hills. Real coastlines and clouds have big shapes *and* small detail. So the same kind of curve is added again at a higher frequency and a lower strength. Each layer is called an *octave*, borrowing the music term: each one has double the frequency of the one before.

![Four rows: octave 1 has slow, tall hills; each following octave is twice as fast and half as tall](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/9a18ee62-6ef5-48bc-9daf-1cd323536f84.png align="center")

Add the octaves together and the big hills keep their shape while gaining finer and finer detail:

![Four rows showing the running sum: octave 1, then octaves 1 and 2, then 1 to 3, then 1 to 4, which is what noise() returns by default](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/2858372d-63bf-4291-919c-507c18f7ff32.png align="center")

In p5.js, `noiseDetail(octaves, falloff)` sets this. The defaults are 4 octaves and a falloff of 0.5: the first octave counts ½, the next ¼, then ⅛, then 1/16. The same thing happens in 2D, where one octave gives soft blobs and four give clouds:

![Two squares of 2D noise: one octave gives large soft blobs; four octaves gives detailed clouds](https://cdn.hashnode.com/uploads/covers/5fdbaa361485f22e24b325e3/7c08cea6-6efc-4965-bb2d-68a747c965b6.png align="center")

You can build noise one octave at a time yourself in the first sketch of the collection.

## 00 How noise is built

[Open sketch 00](https://editor.p5js.org/pedbad/sketches/m5NHK8MPP)

Yellow dots mark the random heights at whole numbers, and the thick line joins them smoothly: one octave. Press ↑ to add octaves one at a time. Thin lines show each octave on its own, and the thick line is their sum. → and ← change the falloff. Each octave's own layer is simply the difference between two running sums:

```javascript
const sums = [];
for (let k = 1; k <= this.#octaves; k++) {
  sums.push(this.#sample(k));
}
```

## How the sketches are organised

Each sketch is small, but it is laid out the way a bigger project would be. That is deliberate: the habits matter more as your projects grow, and your coursework will grow.

```text
.
├── index.html
├── style.css
├── README.md
├── config.js
├── sketch.js
└── classes/
    ├── InfoPanel.js
    ├── KeyboardController.js
    ├── Marker.js
    ├── NoiseSampler.js
    ├── Playback.js
    └── RandomSampler.js
```

- **`config.js` holds every number.** Sizes, colours, speeds and keys all live in one frozen `CONFIG` object, so there are no unexplained "magic numbers" in the rest of the code. To experiment, you change a setting, not the logic.
- **One class per file** in `classes/`, each with one job, documented with [JSDoc](https://jsdoc.app/) comments.
- **`sketch.js` stays short:** it builds the objects in `setup()`, runs them in `draw()` and passes key presses on.
- **Each README** explains what to look for, gives a couple of things to try, and lists the controls.

Key presses switch between the different effects in each sketch. A small `KeyboardController` class maps keys to actions, and the help text on the canvas is built from the same bindings, so the controls on screen can never drift out of step with the code:

```javascript
keyboard = new KeyboardController()
  .bind(CONFIG.keys.pause, 'pause / resume', Playback.toggle)
  .bind(CONFIG.keys.reseed, 'new noise seed', reseed);
```

```javascript
function keyPressed(event) {
  return keyboard.handle(event.key);
}
```

Returning `false` from `keyPressed()` tells p5.js to cancel the browser's default action, so Space pauses the sketch instead of scrolling the page. `handle()` returns `false` only for keys the sketch uses.

> Click the canvas first if a key doesn't seem to do anything: the canvas needs the keyboard focus.

## 01 Random vs noise: random has no memory

[Open sketch 01](https://editor.p5js.org/pedbad/sketches/pkqDdj2rC)

Three dots move along three lanes:

1. **`random()`** jumps to an unrelated position every frame. Each value has nothing to do with the last one.
2. **`noise()` with the same input every frame** doesn't move at all. Noise is not random: the same input always gives the same output.
3. **`noise()` with an input that grows a little every frame** glides smoothly, because neighbouring inputs give neighbouring outputs.

A good way to picture Perlin noise is a smooth, endless hill profile. Calling `noise(t)` reads the height of the hills at position `t`, like a record-player needle on a groove. To get movement, you move the needle. The sketch wraps that idea in a `NoiseSampler` class:

```javascript
next() {
  const value = noise(this.#offset);
  this.#offset += this.#step;
  return value;
}
```

`RandomSampler` has a `next()` method too, so the code that draws a dot doesn't care which kind of sampler it has. The three lanes differ only in the sampler they are given:

```javascript
const samplers = {
  random: () => new RandomSampler(),
  fixedNoise: () => new NoiseSampler({ offset, step: 0 }),
  movingNoise: () => new NoiseSampler({ offset, step }),
};
```

Two facts to remember from this sketch:

- With the default settings, `noise()` returns a value **between 0 and 1**. Use `map()` to turn it into the range you need, such as 0 to `width`.
- The noise is the same every time for a given **seed**. p5.js picks a new seed each time the sketch starts; `noiseSeed()` sets one yourself (press **R** in the sketch).

## 02 Coupled and decoupled

[Open sketch 02](https://editor.p5js.org/pedbad/sketches/wHlveulvY)

A square moves, tilts and changes colour, all from noise. The question is *which* noise value drives each property.

- **Coupled (key 1):** x, y and the angle all read the same value. x and y are always equal, so the square can only slide along a diagonal.
- **Decoupled (key 2):** each property reads its own stretch of the noise line, far from the others, so the square wanders freely.

The modes are just data in `config.js`: which noise "channel" each property reads.

```javascript
modes: Object.freeze({
  coupled: Object.freeze({
    label: 'coupled: x, y, angle share one value',
    channels: Object.freeze({ x: 0, y: 0, angle: 0 }),
  }),
  decoupled: Object.freeze({
    label: 'decoupled: x, y, angle read separate values',
    channels: Object.freeze({ x: 0, y: 1, angle: 2 }),
  }),
}),
```

The channels start at 0, 10000 and 20000 on the noise line. Any starting points work, as long as they are far enough apart that the stretches are unrelated. Coupling is not a mistake, though: the square's colour is deliberately coupled to x, so it warms up as it moves right. Choosing what to couple is a design decision.

## 03 Graphing noise

[Open sketch 03](https://editor.p5js.org/pedbad/sketches/CM2HG17Bz)

A moving dot shows one value at a time. A graph shows the character of a whole sequence at once:

- `random()`: spiky, with no pattern.
- `noise(0)` at every pixel: a flat line. Moving from pixel to pixel does **not** move you along the noise line; only changing the input does.
- `noise(xoff)` with `xoff` growing by 0.01 per pixel: smooth hills.

```javascript
for (let x = left; x <= right; x++) {
  // Larger values plot higher up, so map MAX to the top.
  vertex(x, map(sampler.next(), MIN, MAX, bottom, top));
}
```

This picture only changes when you press a key, so the sketch calls `noLoop()` in `setup()` and `redraw()` after each change, rather than drawing 60 identical frames a second.

## 04 Step size: zoom and speed in one number

[Open sketch 04](https://editor.p5js.org/pedbad/sketches/Mjrjov731)

How far you move along the noise line per pixel (or per frame) is the **step size**, and it is the most important number in any noise sketch. Drag the slider:

- **Small steps** (0.001–0.01): zoomed in. Slow, gentle, smooth.
- **Large steps** (0.1 and above): zoomed out. Busy and jagged; at large enough steps noise starts to look like random, although its values still cluster around the middle.

Keys 1–3 switch between noise, a sine wave, and a sine wave with a little noise added. The sine is smooth but perfectly regular; adding noise keeps the overall shape and makes the detail organic. Because `sin()` returns −1 to 1 and noise returns 0 to 1, each wave is mapped into 0 to 1 first, so they are interchangeable:

```javascript
static combined(t) {
  const { MIN, MAX } = Waves;
  const amount = CONFIG.wobble;
  // Noise (0 to 1) becomes a wobble centred on zero: −amount to +amount.
  const wobble = map(noise(t), MIN, MAX, -amount, amount);
  // Squeeze the sine wave into amount to (1 − amount), leaving room for the
  // wobble, so the sum always stays between 0 and 1.
  const squeezedSine = map(Waves.sine(t), MIN, MAX, MIN + amount, MAX - amount);
  return squeezedSine + wobble;
}
```

## 05 Noise in pixels

[Open sketch 05](https://editor.p5js.org/pedbad/sketches/EGsGnQQmL)

Now noise sets the brightness of every pixel, through the `pixels` array. It is a flat list with four numbers per pixel (red, green, blue, alpha), row by row, so pixel (x, y) starts at index `(x + y * width) * 4`. The sketch keeps that arithmetic in a `PixelPainter` class and describes each mode as a small function:

```javascript
const BRIGHTNESS = Object.freeze({
  solid: () => CONFIG.solidBrightness,
  random: () => random(MAX_BRIGHTNESS),
  oneValue: () => noise(CONFIG.noise.fixedInput) * MAX_BRIGHTNESS,
  noise1D: (x, y, index) => noise(index * CONFIG.noise.step) * MAX_BRIGHTNESS,
});
```

Mode 4 is the interesting one. It lays **1D** noise out row by row, like text on a page. Each row is smooth from left to right, but it carries on from where the row above ended, so neighbouring rows don't match and you get horizontal streaks. For an image that is smooth in both directions, you need 2D noise.

Two practical details: call `pixelDensity(1)` in `setup()`, or high-density (Retina) screens will have more array pixels than you expect; and only the random mode needs to redraw every frame.

## 06 2D noise and octaves

[Open sketch 06](https://editor.p5js.org/pedbad/sketches/Xffow0wdi)

`noise(x, y)` reads a noise *plane* that is smooth in every direction, so the same loop now gives clouds:

```javascript
function brightnessAt(x, y) {
  return noise(x * settings.scale, y * settings.scale) * MAX_BRIGHTNESS;
}
```

p5.js builds its noise from several layers called **octaves**, each with finer detail than the one before. `noiseDetail(octaves, falloff)` controls them; the defaults are 4 octaves and a falloff of 0.5 (each layer counts half as much as the previous one). In the sketch, the arrow keys change both:

- More octaves add finer detail on top of the large shapes.
- Higher falloff makes that fine detail stronger, so the texture gets rougher.

A `NoiseSettings` class keeps each value within the limits set in `config.js` and passes them to `noiseDetail()`.

## 07 Animated noise and flow fields

[Open sketch 07](https://editor.p5js.org/pedbad/sketches/vtc-eW5Zx)

Add a third input and the 2D pattern can move. The column and row of each grid cell give the first two inputs, and time gives the third:

```javascript
const value = noise(column * settings.scale, row * settings.scale, this.#time);
```

Increasing `time` a little every frame is like slicing through a block of marble one thin layer at a time: each slice is very like the last, so the pattern flows. The same values can be drawn as colour (key 1), as size (2) or as a direction (3). A grid of directions is a **flow field**: objects placed in it can follow the arrows, which is the basis of many noise drawings. Daniel Shiffman covers flow fields in *The Nature of Code* (see further reading).

## 08 Noise landscape

[Open sketch 08](https://editor.p5js.org/pedbad/sketches/QDvVEcd2n)

Three mountain ranges, each a strip of 2D noise. Moving the mouse sideways travels along the first noise input; moving it up and down changes the second input, so the hills slowly change shape. Far ranges scroll more slowly than near ones (parallax), which makes flat layers look deep. Each range is an entry in `config.js`, so adding a fourth range is a change to data, not to code. The sketch is inspired by the `noiseScale` example in the p5.js reference.

## 09 Instance mode

[Open sketch 09](https://editor.p5js.org/pedbad/sketches/0Zp23Hp3e)

All the sketches before this one use p5.js **global mode**: `setup()`, `fill()`, `width` and the rest are global. It's quick to write, but you get one sketch per page, and every name you create shares the global scope with hundreds of p5.js names.

In **instance mode**, a sketch is a function that receives its own p5 instance, usually called `p`, and reaches everything through it: `p.fill()`, `p.width`. Sketch 09 uses one function to build two sketches on one page, a random walker and a noise walker:

```javascript
CONFIG.sketches.forEach((settings) => {
  new p5(createWalkerSketch(settings), document.getElementById(settings.container));
});
```

It is also a short lesson in inheritance. A `Walker` base class handles the trail, the bounds and the drawing, and leaves one method for subclasses:

```javascript
class RandomWalker extends Walker {
```

```javascript
class NoiseWalker extends Walker {
```

Each subclass overrides only `nextPosition()`, so the one difference between the two canvases is random versus noise. Press **R** to restart both: the random walker takes a new path every time, while the noise walker repeats exactly the same one. Same inputs, same outputs.

## Summary

| Idea | Sketch |
|---|---|
| How noise is built: lattice, smooth curve, octaves | [00](https://editor.p5js.org/pedbad/sketches/m5NHK8MPP) |
| `random()` has no memory; `noise()` is smooth and repeatable | [01](https://editor.p5js.org/pedbad/sketches/pkqDdj2rC) |
| Coupled vs decoupled properties | [02](https://editor.p5js.org/pedbad/sketches/wHlveulvY) |
| Moving through the noise line by changing the input | [03](https://editor.p5js.org/pedbad/sketches/CM2HG17Bz) |
| Step size sets zoom and speed | [04](https://editor.p5js.org/pedbad/sketches/Mjrjov731) |
| Noise in the `pixels` array; 1D noise row by row | [05](https://editor.p5js.org/pedbad/sketches/EGsGnQQmL) |
| 2D noise, octaves and falloff (`noiseDetail()`) | [06](https://editor.p5js.org/pedbad/sketches/Xffow0wdi) |
| 3D noise for animation; flow fields | [07](https://editor.p5js.org/pedbad/sketches/vtc-eW5Zx) |
| Layers of 2D noise; parallax | [08](https://editor.p5js.org/pedbad/sketches/QDvVEcd2n) |
| Instance mode and inheritance | [09](https://editor.p5js.org/pedbad/sketches/0Zp23Hp3e) |

## Further reading

- [The Nature of Code](https://natureofcode.com/) by Daniel Shiffman, free online: [chapter 0, Randomness](https://natureofcode.com/random/) covers random walks and Perlin noise, and [chapter 5, Autonomous Agents](https://natureofcode.com/autonomous-agents/) covers flow fields. My earlier post on the book: [The Nature of Code](https://pedbad.hashnode.dev/the-nature-of-code).
- p5.js reference: [`noise()`](https://p5js.org/reference/p5/noise/), [`noiseDetail()`](https://p5js.org/reference/p5/noiseDetail/), [`noiseSeed()`](https://p5js.org/reference/p5/noiseSeed/).
- p5.js: [Instance mode explained (p5.js wiki)](https://github.com/processing/p5.js/wiki/Global-and-instance-mode).
- [Perlin noise on Wikipedia](https://en.wikipedia.org/wiki/Perlin_noise) and Ken Perlin's [Improved Noise reference implementation](https://cs.nyu.edu/~perlin/noise/).

