The Closure Myth
About This Sketch
A visual comparison of two approaches to moving forward: waiting for perfect closure before starting the next thing, versus continuing despite incompleteness. The animation shows how the "waiting for closure" approach creates sequential bottlenecks, while the "continuing" approach allows multiple overlapping progressions. Watch how one flows while the other stalls.
Algorithm
This sketch visualizes two different approaches to progression: waiting for closure versus continuing with incompleteness.
The top section shows paths that wait for each previous path to complete before starting the next one—representing the "need for closure" mindset. The bottom section shows paths that start regardless of whether previous paths are complete—representing continuation despite incompleteness.
Over time, the "continuing" approach shows more simultaneous activity and progression, while the "waiting for closure" approach shows sequential, slower progress with paths waiting in queue.
This sketch accompanies the blog post "The Closure Myth" and explores how demanding closure before moving forward creates artificial delays.
Pseudocode
SETUP:
Create 8 paths total (4 in each mode)
Top 4: Wait for previous paths to complete before starting
Bottom 4: Start on schedule regardless of previous paths
Initialize with staggered start times
DRAW (every frame):
Get current theme colors
Clear background
For each path:
CHECK if should activate:
- If "wait for closure" mode: only activate if previous paths complete
- If "continue" mode: activate on schedule
IF active and not complete:
- Advance progress along path
- Add new point to the line
- Check if reached completion
DISPLAY path:
- Draw flowing line with all accumulated points
- Show current state (waiting, active, complete)
- Use color to distinguish modes
SHOW statistics:
- Number of active paths in each mode
- Number of completed paths
- Real-time comparison of progress
Source Code
let sketch = function(p) {
// Visualization of waiting for closure vs. continuing with incompleteness
// Lines that either wait to complete before starting next, or overlap messily
let paths = [];
let maxPaths = 8;
let time = 0;
class Path {
constructor(yPos, startTime, waitForClosure) {
this.yPos = yPos;
this.startTime = startTime;
this.waitForClosure = waitForClosure;
this.progress = 0;
this.maxProgress = p.random(200, 350);
this.complete = false;
this.active = false;
this.points = [];
this.targetY = yPos + p.random(-15, 15);
this.speed = waitForClosure ? 2 : 1.2;
}
update(currentTime, allPaths) {
// Check if we should start
if (!this.active && currentTime >= this.startTime) {
if (this.waitForClosure) {
// Only start if previous paths are complete
let canStart = true;
for (let path of allPaths) {
if (path.startTime < this.startTime && !path.complete) {
canStart = false;
break;
}
}
this.active = canStart;
} else {
// Just start regardless of others
this.active = true;
}
}
if (this.active && !this.complete) {
this.progress += this.speed;
// Add point to path
let x = this.progress;
let y = this.yPos + p.sin(this.progress * 0.03) * 8;
this.points.push({x, y});
// Check if complete
if (this.progress >= this.maxProgress) {
this.complete = true;
}
}
}
display(colors) {
if (this.points.length < 2) return;
// Draw the path
let color = this.waitForClosure ? colors.accent2 : colors.accent1;
let opacity = this.complete ? 150 : 200;
p.strokeWeight(2);
p.stroke(...color, opacity);
p.noFill();
p.beginShape();
for (let point of this.points) {
p.vertex(point.x + 25, point.y);
}
p.endShape();
// Draw end state
if (this.complete) {
// Completed paths get a dot
let lastPoint = this.points[this.points.length - 1];
p.noStroke();
p.fill(...color, opacity);
p.circle(lastPoint.x + 25, lastPoint.y, 6);
} else if (this.active) {
// Active paths get a moving head
let lastPoint = this.points[this.points.length - 1];
p.noStroke();
p.fill(...color, opacity + 50);
p.circle(lastPoint.x + 25, lastPoint.y, 8);
// Glow
p.fill(...color, (opacity + 50) * 0.3);
p.circle(lastPoint.x + 25, lastPoint.y, 12);
} else if (currentTime >= this.startTime) {
// Waiting paths show waiting indicator
p.noStroke();
p.fill(...color, 100);
p.circle(25, this.yPos, 6);
// Pulsing waiting indicator
let pulse = p.sin(time * 0.05) * 2 + 3;
p.stroke(...color, 80);
p.strokeWeight(1);
p.noFill();
p.circle(25, this.yPos, pulse + 8);
}
}
}
p.setup = function() {
p.createCanvas(400, 300);
// Create paths that wait for closure (top half)
let closureY = 80;
for (let i = 0; i < maxPaths / 2; i++) {
paths.push(new Path(
closureY + i * 20,
i * 120, // Staggered starts
true // Wait for closure
));
}
// Create paths that continue without closure (bottom half)
let continueY = 200;
for (let i = 0; i < maxPaths / 2; i++) {
paths.push(new Path(
continueY + i * 20,
i * 60, // Staggered starts (same timing)
false // Don't wait
));
}
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
time++;
// Title
p.fill(...colors.accent3);
p.noStroke();
p.textAlign(p.CENTER);
p.textSize(12);
p.text('The Closure Myth', 200, 20);
// Subtitle
p.textSize(7);
p.fill(...colors.accent3, 180);
p.text('Waiting for closure stalls. Continuing with incompleteness flows.', 200, 32);
// Section labels
p.textAlign(p.LEFT);
p.textSize(8);
// Waiting for closure section
p.fill(...colors.accent2, 200);
p.text('Waiting for Closure', 25, 60);
p.textSize(6);
p.fill(...colors.accent3, 150);
p.text('Must finish before starting next', 25, 69);
// Continuing without closure section
p.fill(...colors.accent1, 200);
p.textSize(8);
p.text('Continuing Without Closure', 25, 180);
p.textSize(6);
p.fill(...colors.accent3, 150);
p.text('Start next while previous incomplete', 25, 189);
// Update and display all paths
for (let path of paths) {
path.update(time, paths);
path.display(colors);
}
// Progress comparison
if (time > 200) {
let closurePaths = paths.filter(p => p.waitForClosure);
let continuePaths = paths.filter(p => !p.waitForClosure);
let closureComplete = closurePaths.filter(p => p.complete).length;
let continueComplete = continuePaths.filter(p => p.complete).length;
let closureActive = closurePaths.filter(p => p.active).length;
let continueActive = continuePaths.filter(p => p.active).length;
// Stats
p.textAlign(p.RIGHT);
p.textSize(6);
p.fill(...colors.accent3, 180);
p.text(`Active: ${closureActive}/${maxPaths/2} Complete: ${closureComplete}/${maxPaths/2}`, 375, 130);
p.text(`Active: ${continueActive}/${maxPaths/2} Complete: ${continueComplete}/${maxPaths/2}`, 375, 250);
// Insight
if (time > 400 && continueActive > closureActive + 1) {
p.fill(...colors.accent1, 200);
p.textAlign(p.CENTER);
p.textSize(7);
p.text('Continuing flows; waiting stalls', 200, 50);
}
}
};
};