Where ideas percolate and thoughts brew

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);
            }
        }
    };
};