Where ideas percolate and thoughts brew

The Expertise Trap

About This Sketch

This sketch accompanies the blog post "The Expertise Trap" and visualizes how expertise creates efficient but constraining patterns. Expert agents (brown) quickly follow well-worn paths, creating a deep groove that becomes increasingly difficult to deviate from. Beginner agents (tan) wander more widely, occasionally discovering unexplored territory that experts miss because they're locked into the optimized path.

The accumulating heat map shows how repeated traversal deepens the expertise groove—making it faster to follow but harder to escape. Watch how experts efficiently converge on the central path while beginners explore the edges. This represents the trade-off between expertise's optimization and beginner's mind's exploration.

Algorithm

This sketch visualizes the expertise trap through path-following behavior. Agents travel from start to goal, with experts (brown dots) and beginners (lighter dots) moving at different speeds and with different exploration patterns. The key mechanism: each agent records its position on a 2D grid, building up a "heat map" of frequently-traveled paths. Expert agents are strongly attracted to well-worn paths (high path attraction = 0.85), making them efficient but constrained to the groove created by previous experts. Beginner agents have weak path attraction (0.2) and high exploration radius, causing them to wander more and occasionally discover new territory. Over time, the visualization shows a deep groove forming in the center—the optimized expert path—while occasional beginner agents wander into unexplored space. This represents how expertise creates efficiency through pattern recognition but can also create rigidity by constraining exploration to known paths. The fade effect leaves trails showing the history of movement, and the varying spawn rates (experts spawn 3x more frequently) demonstrate how expertise dominates a field once established.

Pseudocode

SETUP:
  Initialize 400x300 canvas
  Create empty 2D pathMap array to track position visits
  Initialize empty walkers array

DRAW LOOP (every frame):
  Fade background slightly (creates trail effect)

  FOR each grid cell in pathMap:
    IF cell has been visited (weight > 0):
      Draw translucent rectangle (darker = more visits)
      This creates the visible "groove" of the expert path

  SPAWN new agents periodically:
    Every 30 frames: spawn expert walker (brown, fast, follows path)
    Every 90 frames: spawn beginner walker (tan, slow, explores widely)

  FOR each walker:
    UPDATE:
      Calculate direction toward goal

      IF expert:
        Sample nearby positions for most-worn path
        Blend movement between goal direction and path-following (85% path)

      IF beginner:
        Add significant random exploration (20% path, 80% exploration)

      Move in calculated direction at walker's speed
      Record current position in pathMap (increment cell weight)
      Fade opacity as approaching goal

      IF reached goal OR exceeded max age:
        Remove walker

    DISPLAY:
      Draw walker as colored circle
      IF beginner: add subtle exploration aura

  DRAW annotations (start, goal, labels)

KEY VARIABLES:
  pathAttraction: 0.85 for experts, 0.2 for beginners
  explorationRadius: 8 for experts, 35 for beginners
  speed: 2.5 for experts, 1.2 for beginners

Source Code

let sketch = function(p) {
    // The Expertise Trap
    // Visualization: A path being walked repeatedly
    // Creates a deep groove (expertise) that's efficient but constraining
    // Occasional explorers wander off the path and discover new territory
    // Shows the trade-off between optimized paths (expertise) and exploration (beginner's mind)

    let walkers = [];
    let pathMap = []; // 2D array tracking how often each position is visited
    let gridResolution = 4; // Lower number = finer detail
    let mapWidth, mapHeight;
    let time = 0;

    class Walker {
        constructor(isExpert) {
            this.isExpert = isExpert;
            this.x = 50;
            this.y = p.height / 2;
            this.targetX = 350;
            this.targetY = p.height / 2 + p.random(-30, 30);
            this.speed = isExpert ? 2.5 : 1.2;
            this.explorationRadius = isExpert ? 8 : 35;
            this.age = 0;
            this.maxAge = isExpert ? 200 : 350;
            this.trailOpacity = 255;

            // Experts follow the worn path more closely
            this.pathAttraction = isExpert ? 0.85 : 0.2;
        }

        update() {
            this.age++;

            // Find direction to target
            let dx = this.targetX - this.x;
            let dy = this.targetY - this.y;
            let distance = p.sqrt(dx * dx + dy * dy);

            if (distance < 5 || this.age > this.maxAge) {
                return false; // Remove this walker
            }

            // Normalize direction
            dx /= distance;
            dy /= distance;

            // Experts are attracted to the most-worn path
            if (this.isExpert && this.pathAttraction > 0) {
                let maxWeight = 0;
                let bestDx = dx;
                let bestDy = dy;

                // Sample nearby positions to find the well-worn path
                for (let angle = 0; angle < p.TWO_PI; angle += p.PI / 4) {
                    let checkX = this.x + p.cos(angle) * 20;
                    let checkY = this.y + p.sin(angle) * 20;
                    let weight = this.getPathWeight(checkX, checkY);

                    if (weight > maxWeight && checkX > this.x) { // Only go forward
                        maxWeight = weight;
                        bestDx = (checkX - this.x) / 20;
                        bestDy = (checkY - this.y) / 20;
                    }
                }

                // Blend between target direction and path-following
                if (maxWeight > 0) {
                    dx = p.lerp(dx, bestDx, this.pathAttraction);
                    dy = p.lerp(dy, bestDy, this.pathAttraction);

                    // Renormalize
                    let mag = p.sqrt(dx * dx + dy * dy);
                    dx /= mag;
                    dy /= mag;
                }
            }

            // Add some random exploration (more for beginners)
            dx += p.random(-0.3, 0.3) * (this.explorationRadius / 35);
            dy += p.random(-0.3, 0.3) * (this.explorationRadius / 35);

            // Move
            this.x += dx * this.speed;
            this.y += dy * this.speed;

            // Keep in bounds
            this.y = p.constrain(this.y, 30, p.height - 30);

            // Record this position in the path map
            this.recordPosition();

            // Fade as approaching target
            if (distance < 50) {
                this.trailOpacity = p.map(distance, 0, 50, 0, 255);
            }

            return true;
        }

        recordPosition() {
            let gridX = p.floor(this.x / gridResolution);
            let gridY = p.floor(this.y / gridResolution);

            if (gridX >= 0 && gridX < mapWidth && gridY >= 0 && gridY < mapHeight) {
                pathMap[gridX][gridY] = Math.min(pathMap[gridX][gridY] + 1, 100);
            }
        }

        getPathWeight(x, y) {
            let gridX = p.floor(x / gridResolution);
            let gridY = p.floor(y / gridResolution);

            if (gridX >= 0 && gridX < mapWidth && gridY >= 0 && gridY < mapHeight) {
                return pathMap[gridX][gridY];
            }
            return 0;
        }

        display(colors) {
            p.noStroke();

            if (this.isExpert) {
                p.fill(...colors.accent2, this.trailOpacity * 0.9);
                p.circle(this.x, this.y, 4);
            } else {
                p.fill(...colors.accent1, this.trailOpacity * 0.7);
                p.circle(this.x, this.y, 5);

                // Beginners have a subtle exploration aura
                p.fill(...colors.accent1, this.trailOpacity * 0.15);
                p.circle(this.x, this.y, 12);
            }
        }
    }

    p.setup = function() {
        p.createCanvas(400, 300);
        p.colorMode(p.RGB);

        // Initialize path map
        mapWidth = p.ceil(400 / gridResolution);
        mapHeight = p.ceil(300 / gridResolution);

        for (let i = 0; i < mapWidth; i++) {
            pathMap[i] = [];
            for (let j = 0; j < mapHeight; j++) {
                pathMap[i][j] = 0;
            }
        }
    };

    p.draw = function() {
        const colors = getThemeColors();

        // Fade background instead of clearing for trail effect
        p.fill(...colors.bg, 25);
        p.noStroke();
        p.rect(0, 0, 400, 300);

        time += 0.01;

        // Draw the accumulated path (the "groove" of expertise)
        p.noStroke();
        for (let i = 0; i < mapWidth; i++) {
            for (let j = 0; j < mapHeight; j++) {
                let weight = pathMap[i][j];
                if (weight > 0) {
                    let alpha = p.map(weight, 0, 100, 0, 120);
                    p.fill(...colors.accent3, alpha);
                    p.rect(i * gridResolution, j * gridResolution, gridResolution, gridResolution);
                }
            }
        }

        // Spawn new walkers
        if (p.frameCount % 30 === 0) {
            // More experts than beginners (showing how expertise dominates)
            walkers.push(new Walker(true)); // Expert
        }

        if (p.frameCount % 90 === 0) {
            walkers.push(new Walker(false)); // Beginner
        }

        // Update and draw all walkers
        for (let i = walkers.length - 1; i >= 0; i--) {
            if (!walkers[i].update()) {
                walkers.splice(i, 1);
            } else {
                walkers[i].display(colors);
            }
        }

        // Draw start and end markers
        p.fill(...colors.accent3, 150);
        p.noStroke();
        p.textFont('Georgia');
        p.textSize(9);
        p.textAlign(p.LEFT, p.CENTER);
        p.text('Start', 10, p.height / 2);

        p.textAlign(p.RIGHT, p.CENTER);
        p.text('Goal', 390, p.height / 2);

        // Labels
        p.textAlign(p.CENTER);
        p.fill(...colors.accent2, 200);
        p.textSize(10);
        p.text('The Expertise Trap', 200, 20);

        p.textSize(8);
        p.fill(...colors.accent2, 180);
        p.textAlign(p.LEFT);
        p.text('Deep groove: efficient but constraining', 10, 280);

        p.fill(...colors.accent1, 180);
        p.textAlign(p.RIGHT);
        p.text('Wandering: inefficient but exploring', 390, 280);
    };
};