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