Where ideas percolate and thoughts brew

The Skill-Signaling Gap

About This Sketch

A generative visualization exploring the tension between building real expertise and signaling competence.

The sketch shows two populations over time: builders who invest in genuine skill development (slow progress, solid cores) and signalers who invest in appearing skilled (fast rise, hollow centers). Despite starting from the same baseline, their trajectories diverge dramatically.

Watch how signalers become larger and rise faster (reflecting their visibility and perceived expertise), while builders remain smaller but develop dense, solid cores (representing actual competence). The visualization captures a central tension in modern professional life: the market rewards signals more than substance in the short term, creating perverse incentives.

This accompanies the blog post "The Skill-Signaling Gap," which explores why performing expertise often has better ROI than developing it, and what this means for individual careers and collective competence.

Algorithm

This sketch visualizes the divergent paths of skill-builders versus skill-signalers over time. Two populations start at the same baseline: - **Builders** (30%, left side): Invest time in actual skill development. Their true competence grows steadily, but their visibility and perceived expertise grow slowly. - **Signalers** (70%, right side): Invest time in appearing skilled. Their perceived expertise grows rapidly, but their actual skill develops minimally. The visualization shows how signalers rise faster and become more visible (larger, more prominent) while builders remain smaller and rise slowly. However, builders have solid, dense cores (representing genuine skill) while signalers are hollow with minimal substance. This demonstrates the perverse incentive structure: the market rewards signaling (fast, visible) more than building (slow, private), even though only building creates real value long-term.

Pseudocode

SETUP:
  Create two populations of people
  - Builders (30%): Start with low visibility
  - Signalers (70%): Start with higher visibility
  All start with zero skill and zero perceived expertise

DRAW (every frame = time passing):
  For each builder:
    - Actual skill increases significantly (+0.15/frame)
    - Perceived skill increases minimally (+0.02/frame)
    - Visibility increases very slowly
    - Display as solid circle with dense core

  For each signaler:
    - Actual skill increases minimally (+0.03/frame)
    - Perceived skill increases rapidly (+0.25/frame)
    - Visibility increases quickly
    - Display as hollow circle with tiny core

  Position vertically based on perceived skill (not actual skill)
  Size based on visibility
  Core density based on actual skill

RESULT:
  Signalers rise faster, become more visible, but remain hollow
  Builders rise slowly, stay smaller, but develop solid competence
  The gap between appearance and substance becomes visible over time

Source Code

let sketch = function(p) {
    let builders = [];
    let signalers = [];
    let numPeople = 40;

    class Person {
        constructor(x, y, type) {
            this.x = x;
            this.y = y;
            this.type = type; // 'builder' or 'signaler'
            this.actualSkill = 0;
            this.perceivedSkill = 0;
            this.timeInvested = 0;
            this.visibility = type === 'signaler' ? 0.8 : 0.1;
            this.size = 4;
            this.targetY = y;
        }

        develop() {
            this.timeInvested += 1;

            if (this.type === 'builder') {
                // Builders: slow skill growth, very slow perception growth
                this.actualSkill += 0.15; // Steady skill gains
                this.perceivedSkill += 0.02; // Slow recognition
                this.visibility = p.min(0.3, this.visibility + 0.001);
            } else {
                // Signalers: fast perception growth, slow skill growth
                this.actualSkill += 0.03; // Minimal skill gains
                this.perceivedSkill += 0.25; // Rapid appearance of competence
                this.visibility = p.min(1.0, this.visibility + 0.015);
            }

            // Position based on perceived skill (what people see)
            this.targetY = p.map(this.perceivedSkill, 0, 50, 280, 50);
            this.y = p.lerp(this.y, this.targetY, 0.05);

            // Size based on visibility
            this.size = p.map(this.visibility, 0, 1, 3, 10);
        }

        display(colors) {
            // Opacity based on visibility
            let alpha = p.map(this.visibility, 0, 1, 50, 255);

            // Color intensity based on actual skill vs perceived skill
            let gap = this.perceivedSkill - this.actualSkill;

            if (this.type === 'builder') {
                // Builders: solid, consistent color (substance)
                p.fill(...colors.accent1, alpha);
                p.noStroke();
                p.circle(this.x, this.y, this.size);

                // Inner core showing true skill
                let coreAlpha = p.map(this.actualSkill, 0, 50, 50, 200);
                p.fill(...colors.accent2, coreAlpha);
                p.circle(this.x, this.y, this.size * 0.5);
            } else {
                // Signalers: hollow, flashy (appearance over substance)
                p.noFill();
                p.stroke(...colors.accent3, alpha);
                p.strokeWeight(2);
                p.circle(this.x, this.y, this.size);

                // Tiny core showing limited actual skill
                p.fill(...colors.accent3, p.map(this.actualSkill, 0, 50, 30, 100));
                p.noStroke();
                p.circle(this.x, this.y, this.size * 0.2);
            }
        }
    }

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

        // Create builders (30% of people)
        for (let i = 0; i < numPeople * 0.3; i++) {
            let x = p.random(30, 180);
            let y = 280;
            builders.push(new Person(x, y, 'builder'));
        }

        // Create signalers (70% of people - reflects reality)
        for (let i = 0; i < numPeople * 0.7; i++) {
            let x = p.random(220, 370);
            let y = 280;
            signalers.push(new Person(x, y, 'signaler'));
        }
    };

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

        // Draw axes
        p.stroke(...colors.accent3, 60);
        p.strokeWeight(1);

        // Horizontal line for starting point
        p.line(20, 280, 380, 280);

        // Vertical line for skill axis
        p.line(200, 40, 200, 285);

        // Labels
        p.noStroke();
        p.fill(...colors.accent3);
        p.textAlign(p.CENTER);
        p.textSize(10);
        p.text('The Skill-Signaling Gap', 200, 20);

        // Axis labels
        p.textSize(8);
        p.textAlign(p.LEFT);
        p.fill(...colors.accent3, 180);
        p.text('BUILDERS', 30, 295);
        p.text('(skill > signal)', 30, 305);

        p.textAlign(p.RIGHT);
        p.text('SIGNALERS', 370, 295);
        p.text('(signal > skill)', 370, 305);

        // Y-axis label
        p.push();
        p.translate(15, 150);
        p.rotate(-p.PI/2);
        p.textAlign(p.CENTER);
        p.text('Perceived Expertise', 0, 0);
        p.pop();

        // Update and display everyone
        for (let person of builders) {
            person.develop();
            person.display(colors);
        }

        for (let person of signalers) {
            person.develop();
            person.display(colors);
        }

        // Legend
        p.textAlign(p.LEFT);
        p.textSize(7);

        // Builder indicator
        p.fill(...colors.accent1, 200);
        p.circle(15, 45, 6);
        p.fill(...colors.accent2, 150);
        p.circle(15, 45, 3);
        p.fill(...colors.accent3);
        p.text('Builders: slow rise, solid skill', 25, 48);

        // Signaler indicator
        p.noFill();
        p.stroke(...colors.accent3, 200);
        p.strokeWeight(1.5);
        p.circle(15, 60, 6);
        p.fill(...colors.accent3, 100);
        p.noStroke();
        p.circle(15, 60, 2);
        p.fill(...colors.accent3);
        p.text('Signalers: fast rise, hollow core', 25, 63);

        // Note at bottom
        p.textSize(7);
        p.fill(...colors.accent3, 150);
        p.textAlign(p.CENTER);
        p.text('Size = visibility. Position = perceived skill. Core density = actual skill.', 200, 260);
    };
};