Two Sketches, One Page: When p5.js Instance Mode Is Worth the Boilerplate
Two global-mode p5.js sketches share one window.setup — and one silently wins. Instance mode gives each sketch its own scope for the price of a p. prefix.
08 Aug 2026, 08:23 UTC

Drop two p5.js sketches onto the same HTML page in global mode and one of them quietly stops working. No error, no stack trace — the second script's setup() and draw() simply overwrite the first, and p5's auto-start runs only the survivor. If both sketches declared a variable named speed, they were sharing that, too.
Instance mode is p5's built-in answer: each sketch becomes a function that receives its own p5 object, so nothing leaks into the page's global scope. The cost is a p. prefix on nearly every call. For anything that shares a page with other code — dashboards, CMS embeds, documentation sites — that trade is usually worth it.
What global mode actually does
When you load p5.js with a script tag and define setup() at the top level, p5 scans window for setup, draw, and event handlers like mousePressed, then creates one sketch instance for you. That is why tutorials need zero ceremony. It also means your sketch's names live in the same shared scope as every other script on the page — there is exactly one window.setup.
That is fine for a standalone sketch. It breaks the moment two sketches, or a sketch and a helper library, want the same names.
The same page, in instance mode
In instance mode you write a sketch function that receives the instance — conventionally named p — and attach everything to it. You then decide when and where each sketch starts, passing a container element as the second argument:
<div id="sparkline"></div>
<div id="loader"></div>
<script src="p5.min.js"></script>
<script>
function sparklineSketch(p) {
let values = [];
p.setup = function () {
p.createCanvas(240, 80);
};
p.draw = function () {
p.background(245);
values.push(p.random(10, 70));
if (values.length > 40) values.shift();
p.stroke(30, 120, 200);
p.noFill();
p.beginShape();
for (let i = 0; i < values.length; i++) {
p.vertex(i * 6, p.height - values[i]);
}
p.endShape();
};
}
function loaderSketch(p) {
let angle = 0;
p.setup = function () {
p.createCanvas(80, 80);
};
p.draw = function () {
p.background(245);
p.translate(p.width / 2, p.height / 2);
p.rotate(angle);
angle += 0.08;
p.stroke(200, 90, 40);
p.arc(0, 0, 50, 50, 0, p.PI + p.HALF_PI);
};
}
new p5(sparklineSketch, 'sparkline');
new p5(loaderSketch, 'loader');
</script>Each call to new p5() builds an independent sketch with its own closure: sparklineSketch's values array and loaderSketch's angle never see each other. Event handlers work the same way — define p.mousePressed on the instance instead of a global mousePressed, and only that sketch reacts.
Helpers and the p. tax
Any helper that needs p5 functions must receive the instance explicitly:
function jitter(p, n) {
return n + p.random(-2, 2);
}Alternatively, define helpers inside the sketch function, where the closure already has p in scope. Either way it is a consistent, mechanical prefix — that is the entire cost of the pattern.
What you give up
- Every p5 call gets a
p.prefix, and most tutorials show global mode, so translating examples is manual work. - Some add-on libraries assume a global p5 variable and misbehave without one; check a library's documentation for instance-mode support before committing to the pattern.
- The p5 constructor itself is still a global when loaded via a script tag. Instance mode isolates your sketch code, not the library — ES module imports are the separate fix for that.
The differences at a glance:
| Global mode | Instance mode | |
|---|---|---|
| Where setup/draw live | window | the p5 instance |
| Sketch variables | shared page scope | closure per sketch |
| Two sketches, one page | one silently wins | both run |
| Event handlers | window.mousePressed | p.mousePressed |
Confirming the isolation
- Both canvases render and animate independently; comment out one sketch's
drawbody and the other keeps going. - In the browser console,
typeof window.setupreturns"undefined"— instance mode never registers globals, provided you did not define one yourself. - Rename a variable in one sketch and confirm the other sketch's behavior does not change.
One honest caveat about testing: because a sketch is now a plain function that produces an instance, it is straightforward to construct and tear down in a test — but p5 still expects a DOM and a canvas, so headless runs need jsdom or a canvas stub. Instance mode makes sketches testable in shape; it does not remove the environment work.
The rule of thumb
Use global mode for standalone sketches and learning. Switch to instance mode the moment a sketch shares a page, gets embedded in an app, or needs to be constructed on demand. Refactoring an existing sketch is mechanical: wrap the file's body in function sketch(p) { ... }, prefix p5 API calls and event handlers with p., then start it with new p5(sketch, 'container-id').
Version note: this post assumes the p5.js 1.x line, where the global auto-start behavior described here is long-standing. If you are on 2.x, verify the details against the current documentation before refactoring.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.