The Constraint You Won't Name
About This Sketch
Particles flowing left to right must pass through a single narrow bottleneck at center. Most accumulate and slow before itβthe whole system's throughput determined by this one point. When the bottleneck briefly opens, everything moves freely. When it closes again, backup resumes immediately.
Visualizing Goldratt's Theory of Constraints: in any system, one binding constraint determines total output. Improving non-constraints just creates more work that piles up at the same narrow point.
Algorithm
A flow visualization showing particles moving from left to right through a channel
that narrows to a tight bottleneck at the center. Particles accumulate and slow before
the bottleneck, squeeze through at reduced speed, then accelerate freely afterward.
The bottleneck periodically widens (representing the brief moments when the true
constraint is addressed), and the whole system flows freelyβthen narrows again.
Color encodes position relative to constraint: particles waiting behind the bottleneck
appear in a deeper tone; particles past it move in the lighter accent color.
This sketch accompanies the blog post "The Constraint You Won't Name" and visualizes
Goldratt's Theory of Constraints applied to human performance: one binding limit
determines total throughput, and improving anything else just creates backup at the same point.
Pseudocode
SETUP:
Initialize canvas (400x300)
Set bottleneck to closed state with timer
Seed 55 particles at random positions across canvas
EACH FRAME:
Apply translucent background for trail effect
Advance time counter
MANAGE BOTTLENECK CYCLE:
Decrement timer
If closed timer expires β open bottleneck (80 frames)
If open timer expires β close bottleneck (260+ frames)
Lerp current neck width toward target (smooth transition)
DRAW CHANNEL WALLS:
Bezier curves funnel from full width β bottleneck β full width
Glow effect near closed bottleneck
SPAWN new particle every 5 frames if under limit
FOR EACH PARTICLE:
Increment age, fade in alpha
Compute channel bounds at particle's x position
Push away from walls with margin
If approaching bottleneck from left: slow down based on crowding
If past bottleneck: accelerate to exit speed
Apply Perlin noise perturbation for organic drift
Constrain velocities
Move particle
Remove if off-canvas or too old
Color blend: waiting (deep) β passed (light)
DRAW label
Source Code
let sketch = function(p) {
// Flow network: particles flow from left to right through a wide system
// but must all pass through one narrow bottleneck in the center
// Particles pile up behind the bottleneck, slow through, then disperse
// Occasionally the bottleneck briefly widens and everything flows freely
// Represents: one constraint limits the whole system; improving anything else just creates backup
let particles = [];
let time = 0;
let bottleneckOpen = 0;
let openTimer = 0;
let closedTimer = 0;
const BOTTLENECK_X = 200;
const NECK_W = 18;
const NECK_W_OPEN = 90;
const TOP_WALL = 40;
const BOT_WALL = 260;
function spawnParticle() {
return {
x: p.random(10, 60),
y: p.random(TOP_WALL + 5, BOT_WALL - 5),
vx: p.random(0.6, 1.3),
vy: p.random(-0.25, 0.25),
size: p.random(2.4, 3.8),
age: 0,
alpha: 0,
passed: false
};
}
p.setup = function() {
p.createCanvas(400, 300);
p.colorMode(p.RGB);
closedTimer = 200;
p._neckW = NECK_W;
for (let i = 0; i < 55; i++) {
let pt = spawnParticle();
pt.x = p.random(10, 370);
pt.age = p.random(60);
particles.push(pt);
}
};
p.draw = function() {
const colors = getThemeColors();
p.noStroke();
p.fill(...colors.bg, 42);
p.rect(0, 0, 400, 300);
time += 0.013;
if (bottleneckOpen === 0) {
closedTimer--;
if (closedTimer <= 0) {
bottleneckOpen = 1;
openTimer = 80;
}
} else {
openTimer--;
if (openTimer <= 0) {
bottleneckOpen = 0;
closedTimer = 260 + p.random(80);
}
}
let targetW = bottleneckOpen === 1 ? NECK_W_OPEN : NECK_W;
p._neckW = p.lerp(p._neckW, targetW, 0.04);
let neckW = p._neckW;
let wallColorA = [...colors.accent3, 55];
p.stroke(...wallColorA);
p.strokeWeight(1.5);
p.noFill();
p.beginShape();
p.vertex(0, TOP_WALL);
p.bezierVertex(120, TOP_WALL, 160, 150 - neckW, BOTTLENECK_X, 150 - neckW);
p.bezierVertex(240, 150 - neckW, 280, TOP_WALL, 400, TOP_WALL);
p.endShape();
p.beginShape();
p.vertex(0, BOT_WALL);
p.bezierVertex(120, BOT_WALL, 160, 150 + neckW, BOTTLENECK_X, 150 + neckW);
p.bezierVertex(240, 150 + neckW, 280, BOT_WALL, 400, BOT_WALL);
p.endShape();
if (bottleneckOpen === 0 && neckW < NECK_W + 15) {
let glowA = p.map(neckW, NECK_W, NECK_W + 15, 60, 0);
p.noStroke();
for (let r = neckW + 20; r > neckW; r -= 4) {
p.fill(...colors.accent2, glowA * (1 - (r - neckW) / 20));
p.rect(BOTTLENECK_X - r, 150 - r * 0.4, r * 2, r * 0.8, 4);
}
}
if (p.frameCount % 5 === 0 && particles.length < 110) {
particles.push(spawnParticle());
}
for (let i = particles.length - 1; i >= 0; i--) {
let pt = particles[i];
pt.age++;
pt.alpha = Math.min(pt.alpha + 8, 180);
function getChannelBounds(x) {
let topY, botY;
if (x < BOTTLENECK_X) {
let s = x / BOTTLENECK_X;
topY = p.lerp(TOP_WALL, 150 - neckW, s * s);
botY = p.lerp(BOT_WALL, 150 + neckW, s * s);
} else {
let s = (x - BOTTLENECK_X) / (400 - BOTTLENECK_X);
topY = p.lerp(150 - neckW, TOP_WALL, s * s);
botY = p.lerp(150 + neckW, BOT_WALL, s * s);
}
return { topY, botY };
}
let bounds = getChannelBounds(pt.x);
let margin = pt.size + 2;
if (pt.y < bounds.topY + margin) pt.vy += 0.3;
if (pt.y > bounds.botY - margin) pt.vy -= 0.3;
let distToNeck = Math.abs(pt.x - BOTTLENECK_X);
if (!pt.passed && distToNeck < 80 && pt.x < BOTTLENECK_X) {
let channelHalfH = (bounds.botY - bounds.topY) / 2;
let crowding = 1 - Math.min(1, channelHalfH / 80);
pt.vx = p.lerp(pt.vx, 0.25, 0.06 + crowding * 0.08);
} else {
pt.vx = p.lerp(pt.vx, 1.4, 0.04);
}
if (pt.x >= BOTTLENECK_X) pt.passed = true;
if (pt.passed) pt.vx = p.lerp(pt.vx, 2.0, 0.05);
let n = p.noise(pt.x * 0.007 + time * 0.2, pt.y * 0.007 + time * 0.15);
pt.vy = p.lerp(pt.vy, (n - 0.5) * 1.2, 0.05);
pt.vx = p.constrain(pt.vx, 0.1, 3.5);
pt.vy = p.constrain(pt.vy, -1.8, 1.8);
pt.x += pt.vx;
pt.y += pt.vy;
if (pt.x > 410 || pt.age > 600) {
particles.splice(i, 1);
continue;
}
let t = pt.passed ? 1 : p.constrain((BOTTLENECK_X - pt.x) / 120, 0, 1);
let cr = p.lerp(colors.accent1[0], colors.accent2[0], t);
let cg = p.lerp(colors.accent1[1], colors.accent2[1], t);
let cb = p.lerp(colors.accent1[2], colors.accent2[2], t);
p.noStroke();
p.fill(cr, cg, cb, pt.alpha);
p.circle(pt.x, pt.y, pt.size * 2);
}
p.noStroke();
p.fill(...colors.accent3, 80);
p.textAlign(p.CENTER);
p.textSize(9);
p.text('the system β and its constraint', 200, 290);
};
};