The Performance of Learning
About This Sketch
A split-screen visualization comparing private, messy learning (which goes deep through confusion and breakthrough) versus public performance of learning (which stays shallow but looks polished and consistent). The left side shows erratic exploration with occasional bursts of insight, while the right side shows steady, linear "progress" that never achieves real depth.
Algorithm
This sketch visualizes the contrast between private learning and public performance of learning.
Two parallel streams of particles represent different approaches to learning:
- Left side (Private Learning): Particles move erratically, wandering and exploring, occasionally experiencing "breakthrough" moments where they suddenly gain depth. The path is messy and non-linear, but the particles grow larger (representing deeper understanding).
- Right side (Public Performance): Particles move smoothly and consistently downward in a neat line, but remain small (shallow understanding). They prioritize visible progress over actual depth.
The sketch accompanies the blog post "The Performance of Learning" and explores how documentation and public sharing can interfere with the messy, private process of genuine learning.
Pseudocode
SETUP:
Initialize canvas (400x300)
Create empty arrays for two particle types
DRAW (every frame):
Get current theme colors
Clear background
Draw labels and dividing line
Spawn new particles periodically for both paths
FOR EACH learning particle (private):
Update position with random wobbling (confusion/exploration)
Gradually increase depth
Randomly trigger breakthrough moments (sudden insight)
Draw with size proportional to depth
Show glow effect during breakthrough
Remove if too old or off screen
FOR EACH performance particle (public):
Update position moving steadily downward (consistent "progress")
Minimally increase depth
Keep movement smooth and predictable
Draw small, polished circles
Connect to previous particle with line
Remove if too old or off screen
Display depth comparison labels
Show insight message after initial period
Source Code
let sketch = function(p) {
// Visualization: The Performance of Learning
// Two parallel paths: one showing messy, deep learning (private)
// and one showing polished, shallow documentation (public)
// The private path goes deeper while public path stays surface-level
let time = 0;
let learningParticles = [];
let performanceParticles = [];
class LearningParticle {
constructor() {
this.x = 50;
this.y = 100;
this.depth = 0;
this.confusion = p.random(0, p.TWO_PI);
this.speed = p.random(0.3, 0.8);
this.wobble = p.random(2, 5);
this.age = 0;
this.breakthrough = false;
this.breakthroughAt = p.random(150, 300);
}
update() {
this.age++;
// Messy, non-linear progress with occasional breakthroughs
if (this.age > this.breakthroughAt && !this.breakthrough) {
this.breakthrough = true;
this.depth += p.random(20, 40); // Sudden insight
}
// Wander and explore (confusion)
this.x += p.cos(this.confusion) * this.wobble;
this.y += p.sin(this.confusion) * 0.5;
this.confusion += p.random(-0.1, 0.1);
// Gradual depth increase (real learning)
this.depth += this.speed * 0.05;
// Stay in bounds but allow exploration
this.x = p.constrain(this.x, 40, 180);
this.y = p.constrain(this.y, 60, 240);
}
display(colors) {
let alpha = p.map(this.age, 0, 400, 255, 50);
// Size represents depth of understanding
let size = p.map(this.depth, 0, 100, 3, 12);
// Trail showing messy exploration
p.noStroke();
p.fill(...colors.accent1, alpha * 0.3);
p.circle(this.x, this.y, size);
// Glow for breakthrough moments
if (this.breakthrough && this.age - this.breakthroughAt < 30) {
let glowSize = size + (30 - (this.age - this.breakthroughAt));
p.fill(...colors.accent2, 100);
p.circle(this.x, this.y, glowSize);
}
}
isDone() {
return this.age > 400 || this.y > 250;
}
}
class PerformanceParticle {
constructor() {
this.x = 250;
this.y = 100;
this.depth = 0;
this.speed = p.random(0.8, 1.2);
this.age = 0;
this.polish = 1.0; // Always polished
}
update() {
this.age++;
// Smooth, linear progress (appearance of learning)
this.y += this.speed;
// Minimal depth increase (shallow understanding)
this.depth += 0.02;
// Stay in lane (performative consistency)
this.x += p.sin(this.age * 0.05) * 0.5;
this.x = p.constrain(this.x, 220, 280);
}
display(colors) {
let alpha = p.map(this.age, 0, 300, 255, 50);
// Always small (shallow understanding)
let size = p.map(this.depth, 0, 100, 3, 6);
// Perfect circles (polished presentation)
p.noStroke();
p.fill(...colors.accent2, alpha * 0.5);
p.circle(this.x, this.y, size);
// Connecting line showing "progress"
if (performanceParticles.length > 1) {
let prev = performanceParticles[performanceParticles.length - 2];
p.stroke(...colors.accent2, alpha * 0.2);
p.strokeWeight(1);
p.line(this.x, this.y, prev.x, prev.y);
}
}
isDone() {
return this.age > 300 || this.y > 250;
}
}
p.setup = function() {
p.createCanvas(400, 300);
};
p.draw = function() {
const colors = getThemeColors();
p.background(...colors.bg);
time++;
// Labels
p.fill(...colors.accent3, 220);
p.noStroke();
p.textAlign(p.CENTER, p.TOP);
p.textSize(11);
p.textFont('Georgia');
p.text('Private Learning', 115, 15);
p.text('Public Performance', 265, 15);
// Dividing line
p.stroke(...colors.accent3, 80);
p.strokeWeight(1);
p.line(200, 40, 200, 260);
// Spawn particles
if (time % 40 === 0) {
learningParticles.push(new LearningParticle());
}
if (time % 35 === 0) {
performanceParticles.push(new PerformanceParticle());
}
// Update and draw learning particles (messy, deep)
for (let i = learningParticles.length - 1; i >= 0; i--) {
learningParticles[i].update();
learningParticles[i].display(colors);
if (learningParticles[i].isDone()) {
learningParticles.splice(i, 1);
}
}
// Update and draw performance particles (polished, shallow)
for (let i = performanceParticles.length - 1; i >= 0; i--) {
performanceParticles[i].update();
performanceParticles[i].display(colors);
if (performanceParticles[i].isDone()) {
performanceParticles.splice(i, 1);
}
}
// Starting points
p.noStroke();
p.fill(...colors.accent1, 200);
p.circle(115, 50, 8);
p.circle(265, 50, 8);
// Depth indicators at bottom
p.textAlign(p.CENTER);
p.textSize(9);
p.fill(...colors.accent3, 180);
// Calculate average depth for each path
let avgPrivateDepth = 0;
if (learningParticles.length > 0) {
for (let particle of learningParticles) {
avgPrivateDepth += particle.depth;
}
avgPrivateDepth /= learningParticles.length;
}
let avgPublicDepth = 0;
if (performanceParticles.length > 0) {
for (let particle of performanceParticles) {
avgPublicDepth += particle.depth;
}
avgPublicDepth /= performanceParticles.length;
}
p.text(`Messy, but deep`, 115, 270);
p.text(`Polished, but shallow`, 265, 270);
// Key insight
if (time > 200) {
p.fill(...colors.accent2, 180);
p.textSize(8);
p.text('Real learning requires private confusion', 200, 285);
}
};
};