pcloadletterWhat the **** does that mean?2026-01-23T00:00:00Zhttps://pcloadletter.dev/The Birthday Paradox, simulated2026-01-23T00:00:00Zhttps://pcloadletter.dev/blog/birthday-paradox/<p>I'm a fan of simulating counterintuitive statistics. I recently did this with <a href="https://www.pcloadletter.dev/blog/monty/">the Monty Hall problem</a> and I really enjoyed how it turned out.</p>
<p>A similarly interesting statistical puzzle is the birthday paradox: you only need to get 23 people in a room a room to end up with a 50% chance of at least one birthday match amongst the group.</p>
<p>This can, of course, <a href="https://pi.math.cornell.edu/~mec/2008-2009/TianyiZheng/Birthday.html">be demonstrated using math</a>. But still I think this is a fun simulation problem!</p>
<h2 id="controls" tabindex="-1">Controls <a class="header-anchor" href="https://pcloadletter.dev/blog/birthday-paradox/">#</a></h2>
<p>Start or stop the simulations and adjust the speed to change how fast simulations are run.</p>
<div class="controls">
<button id="play-btn">Start simulation</button>
<label for="speed-control">Speed:</label>
<input id="speed-control" type="range" min="1" max="1000" value="3">
<span id="speed-display">x3</span>
</div>
<h2 id="score" tabindex="-1">Score <a class="header-anchor" href="https://pcloadletter.dev/blog/birthday-paradox/">#</a></h2>
<p>Cumulative results as we run more simulations.</p>
<table>
<thead>
<tr>
<th>Result</th>
<th>Trials</th>
<th>Count</th>
<th>Percentage</th>
</tr>
</thead>
<tbody>
<tr>
<td>Match found</td>
<td><span id="trials">0</span></td>
<td><span id="matches">0</span></td>
<td><span id="match-pct">-</span></td>
</tr>
<tr>
<td>No match</td>
<td><span id="trials-nm">0</span></td>
<td><span id="no-matches">0</span></td>
<td><span id="no-match-pct">-</span></td>
</tr>
</tbody>
</table>
<h2 id="simulation" tabindex="-1">Simulation <a class="header-anchor" href="https://pcloadletter.dev/blog/birthday-paradox/">#</a></h2>
<p>Each simulation creates 23 people below and deals out random birthdays.</p>
<section class="board" id="birthday-board">
<div class="people">
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
<div class="person"></div>
</div>
<div class="status"> </div>
</section>
<style>
.board {
background: rgba(255, 255, 255, 0.06);
padding: 0.75rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.people {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
gap: 8px;
align-items: start;
justify-content: start;
padding: 1rem 0 0.25rem;
}
.person {
width: 40px;
height: 40px;
border: 2px solid rgba(255, 255, 255, 0.25);
border-radius: 4px;
display: grid;
place-items: center;
font-weight: 600;
font-size: 0.75rem;
text-align: center;
padding: 4px;
}
.person.match {
border-color: #4ade80;
background: rgba(74, 222, 128, 0.1);
}
.status {
font-size: 0.9rem;
opacity: 0.8;
margin-top: 0.5rem;
min-height: 1.25rem;
}
.controls {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.controls input[type="range"] {
width: 240px;
}
</style>
<script>
let running = false;
const playBtn = document.querySelector('#play-btn');
const speedControl = document.querySelector('#speed-control');
const speedDisplay = document.querySelector('#speed-display');
let speed = parseInt(speedControl.value);
const randi = (n) => Math.floor(Math.random() * n);
const sleep = (s) => new Promise(res => setTimeout(res, (s * 1000) / Math.max(1, speed)));
const dayToMonthDay = (dayNum) => {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const daysPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let day = dayNum;
for (let m = 0; m < 12; m++) {
if (day < daysPerMonth[m]) {
return months[m] + ' ' + (day + 1);
}
day -= daysPerMonth[m];
}
};
let trialsWithMatch = 0;
let trialsWithoutMatch = 0;
const trialsEl = document.querySelector('#trials');
const matchesEl = document.querySelector('#matches');
const matchPctEl = document.querySelector('#match-pct');
const trialsNmEl = document.querySelector('#trials-nm');
const noMatchesEl = document.querySelector('#no-matches');
const noMatchPctEl = document.querySelector('#no-match-pct');
const pct = (count, total) => total ? ((100 * count) / total).toFixed(1) + '%' : '-';
const updateScoreboard = () => {
const totalTrials = trialsWithMatch + trialsWithoutMatch;
trialsEl.textContent = totalTrials;
matchesEl.textContent = trialsWithMatch;
matchPctEl.textContent = pct(trialsWithMatch, totalTrials);
trialsNmEl.textContent = totalTrials;
noMatchesEl.textContent = trialsWithoutMatch;
noMatchPctEl.textContent = pct(trialsWithoutMatch, totalTrials);
};
class BirthdayBoard {
constructor() {
this.root = document.getElementById('birthday-board');
this.personEls = Array.from(this.root.querySelectorAll('.person'));
this.statusEl = this.root.querySelector('.status');
}
clear() {
this.personEls.forEach((el) => {
el.textContent = '';
el.classList.remove('match');
});
}
status(t) {
this.statusEl.textContent = t;
}
hasMatchingBirthdays(birthdays) {
const seen = new Set();
for (let bd of birthdays) {
if (seen.has(bd)) {
return true;
}
seen.add(bd);
}
return false;
}
async playOnce() {
if (!running) return;
this.status('\u00A0');
this.clear();
// Generate 23 random birthdays
const birthdays = [];
for (let i = 0; i < 23; i++) {
birthdays.push(randi(365));
}
await sleep(0.3);
this.status('Assigning birthdays...');
// Display birthdays one by one
for (let i = 0; i < 23; i++) {
this.personEls[i].textContent = dayToMonthDay(birthdays[i]);
await sleep(0.1);
}
await sleep(0.5);
this.status('Checking for matches...');
await sleep(0.5);
// Check for matching birthdays
const hasMatch = this.hasMatchingBirthdays(birthdays);
if (hasMatch) {
// Find and highlight the matching pair
const seen = new Map();
for (let i = 0; i < 23; i++) {
if (seen.has(birthdays[i])) {
const j = seen.get(birthdays[i]);
this.personEls[i].classList.add('match');
this.personEls[j].classList.add('match');
this.status('✓ Match found! (' + dayToMonthDay(birthdays[i]) + ')');
trialsWithMatch++;
break;
}
seen.set(birthdays[i], i);
}
} else {
this.status('✗ No matching birthdays');
trialsWithoutMatch++;
}
updateScoreboard();
await sleep(2);
}
start() {
const loop = async () => {
while (running) {
await this.playOnce();
}
};
loop();
}
}
const board = new BirthdayBoard();
playBtn.addEventListener('click', () => {
running = !running;
playBtn.textContent = running ? 'Stop simulation' : 'Start simulation';
if (running) board.start();
});
speedControl.addEventListener('input', (e) => {
speed = parseInt(e.target.value) || 1;
speedDisplay.textContent = 'x' + speed;
});
updateScoreboard();
</script>
<h2 id="code" tabindex="-1">Code <a class="header-anchor" href="https://pcloadletter.dev/blog/birthday-paradox/">#</a></h2>
<p>If you'd like to run this simulation or just want to peek at the code, you can find it below:</p>
<p><strong>html</strong></p>
<pre class="language-html" tabindex="0"><code class="language-html">section class="board" id="birthday-board">
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>people<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>person<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span></code></pre>
<p><strong>js</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">let</span> running <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> playBtn <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#play-btn"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> speedControl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#speed-control"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> speedDisplay <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#speed-display"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> speed <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>speedControl<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">randi</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token operator">=></span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">s</span><span class="token punctuation">)</span> <span class="token operator">=></span>
<span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> <span class="token punctuation">(</span>s <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span> <span class="token operator">/</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> speed<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">dayToMonthDay</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">dayNum</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> months <span class="token operator">=</span> <span class="token punctuation">[</span>
<span class="token string">"Jan"</span><span class="token punctuation">,</span>
<span class="token string">"Feb"</span><span class="token punctuation">,</span>
<span class="token string">"Mar"</span><span class="token punctuation">,</span>
<span class="token string">"Apr"</span><span class="token punctuation">,</span>
<span class="token string">"May"</span><span class="token punctuation">,</span>
<span class="token string">"Jun"</span><span class="token punctuation">,</span>
<span class="token string">"Jul"</span><span class="token punctuation">,</span>
<span class="token string">"Aug"</span><span class="token punctuation">,</span>
<span class="token string">"Sep"</span><span class="token punctuation">,</span>
<span class="token string">"Oct"</span><span class="token punctuation">,</span>
<span class="token string">"Nov"</span><span class="token punctuation">,</span>
<span class="token string">"Dec"</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> daysPerMonth <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">28</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">,</span> <span class="token number">30</span><span class="token punctuation">,</span> <span class="token number">31</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> day <span class="token operator">=</span> dayNum<span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> m <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> m <span class="token operator"><</span> <span class="token number">12</span><span class="token punctuation">;</span> m<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>day <span class="token operator"><</span> daysPerMonth<span class="token punctuation">[</span>m<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> months<span class="token punctuation">[</span>m<span class="token punctuation">]</span> <span class="token operator">+</span> <span class="token string">" "</span> <span class="token operator">+</span> <span class="token punctuation">(</span>day <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
day <span class="token operator">-=</span> daysPerMonth<span class="token punctuation">[</span>m<span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> trialsWithMatch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> trialsWithoutMatch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> trialsEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#trials"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> matchesEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#matches"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> matchPctEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#match-pct"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> trialsNmEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#trials-nm"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> noMatchesEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#no-matches"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> noMatchPctEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#no-match-pct"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">pct</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">count<span class="token punctuation">,</span> total</span><span class="token punctuation">)</span> <span class="token operator">=></span>
total <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">*</span> count<span class="token punctuation">)</span> <span class="token operator">/</span> total<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toFixed</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"%"</span> <span class="token operator">:</span> <span class="token string">"-"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">updateScoreboard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> totalTrials <span class="token operator">=</span> trialsWithMatch <span class="token operator">+</span> trialsWithoutMatch<span class="token punctuation">;</span>
trialsEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> totalTrials<span class="token punctuation">;</span>
matchesEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> trialsWithMatch<span class="token punctuation">;</span>
matchPctEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token function">pct</span><span class="token punctuation">(</span>trialsWithMatch<span class="token punctuation">,</span> totalTrials<span class="token punctuation">)</span><span class="token punctuation">;</span>
trialsNmEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> totalTrials<span class="token punctuation">;</span>
noMatchesEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> trialsWithoutMatch<span class="token punctuation">;</span>
noMatchPctEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token function">pct</span><span class="token punctuation">(</span>trialsWithoutMatch<span class="token punctuation">,</span> totalTrials<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">class</span> <span class="token class-name">BirthdayBoard</span> <span class="token punctuation">{</span>
<span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>root <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span><span class="token string">"birthday-board"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>personEls <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>root<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">".person"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>statusEl <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>root<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">".status"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>personEls<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
el<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
el<span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span><span class="token string">"match"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">status</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>statusEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> t<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">hasMatchingBirthdays</span><span class="token punctuation">(</span><span class="token parameter">birthdays</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> seen <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Set</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> bd <span class="token keyword">of</span> birthdays<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>seen<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>bd<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
seen<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>bd<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">async</span> <span class="token function">playOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>running<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"\u00A0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> birthdays <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">23</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
birthdays<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token function">randi</span><span class="token punctuation">(</span><span class="token number">365</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"Assigning birthdays..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">23</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>personEls<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token function">dayToMonthDay</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"Checking for matches..."</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> hasMatch <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">hasMatchingBirthdays</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>hasMatch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> seen <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator"><</span> <span class="token number">23</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>seen<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> j <span class="token operator">=</span> seen<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>personEls<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"match"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>personEls<span class="token punctuation">[</span>j<span class="token punctuation">]</span><span class="token punctuation">.</span>classList<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token string">"match"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"✓ Match found! ("</span> <span class="token operator">+</span> <span class="token function">dayToMonthDay</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">")"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
trialsWithMatch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token keyword">break</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
seen<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>birthdays<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"✗ No matching birthdays"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
trialsWithoutMatch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">updateScoreboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token function-variable function">loop</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>running<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">playOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token function">loop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">const</span> board <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">BirthdayBoard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
playBtn<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
running <span class="token operator">=</span> <span class="token operator">!</span>running<span class="token punctuation">;</span>
playBtn<span class="token punctuation">.</span>textContent <span class="token operator">=</span> running <span class="token operator">?</span> <span class="token string">"Stop simulation"</span> <span class="token operator">:</span> <span class="token string">"Start simulation"</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>running<span class="token punctuation">)</span> board<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
speedControl<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"input"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
speed <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token number">1</span><span class="token punctuation">;</span>
speedDisplay<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">"x"</span> <span class="token operator">+</span> speed<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">updateScoreboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
Pre-agent nostalgia2026-01-18T00:00:00Zhttps://pcloadletter.dev/blog/pre-coding-agent-nostalgia/<p>Everything seems to move fast these days and apparently nostalgia is no exception.</p>
<p>I'm not an agentic coding evangelist but I'm also a realist—coding agents are really impressive and have changed the way most of us write software. Agents can help you plan, write, and ship code more efficiently and effectively if you use them correctly.</p>
<p>But dammit, I miss the old way.</p>
<p>I miss the ceremony of searching the Internet for solutions. I miss the ceremony of reading the docs. I miss the ceremony of asking question on Stack Overflow (okay, maybe not that part).</p>
<p>I miss the craft I used to put into every line of code.</p>
<p>A reasonable suggestion might be to just ignore the new tech and not use it. But that's hard to do when you know the tech exists and makes your work better. The cat's already out of the bag, so to speak.</p>
<p>I feel what has historically been referred to as a <em>nostalgia for friction</em>. I miss dearly those processes that are being taken over by automation. For me, the friction was the fun. Without it, I'm feeling a bit lost.</p>
Features for no one (AI edition)2026-01-10T00:00:00Zhttps://pcloadletter.dev/blog/ai-features-for-no-one/<p>One of the worst aspects of tech hype is the onslaught of useless features. The current AI hype train is clearly no exception.</p>
<p>Whenever hyped tech comes up, the tech industry seems to get collective amnesia about how to validate product and feature ideas and that <a href="https://www.interaction-design.org/literature/topics/ux-research">UX research is a thing</a>. Instead, we just throw everything at the wall and see what sticks.</p>
<p>They are features for no one.</p>
<p>I recall one of the earlier examples: LinkedIn's <a href="https://news.linkedin.com/2023/november/linkedIn-introduces-new-aI-powered-premium-experience">AI-powered premium experience</a>, which promised "a higher level of personalization to each interaction" by, ahem, generating AI follow-up questions to responses on your posts. How personalized!</p>
<p>Or who could forget <a href="https://support.microsoft.com/en-us/windows/retrace-your-steps-with-recall-aa03f8a0-a78b-4b3e-b0a1-2eb8ac48701c">Microsoft Windows "Recall"</a>, the dystopian privacy nightmare that continually takes screenshots of your activity. (By the way, this still exists!).</p>
<p>There are also entire products, like the AI Friend Bracelet, <a href="https://www.techbuzz.ai/articles/friend-ai-necklace-review-the-129-wearable-that-bullies-you">that are horrendous</a>. These are devices that purport to offer companionship or connection by pairing you with a hallucinating, unfeeling robot.</p>
<p>This post isn't intended to target AI in particular: it's just the latest example of what seems to always happen. I can't wait for it to be over so we can move on to the next one.</p>
We might have been slower to abandon Stack Overflow if it wasn't a toxic hellhole2026-01-07T00:00:00Zhttps://pcloadletter.dev/blog/abandoning-stackoverflow/<p>If you were a software developer prior to 2024, you probably used Stack Overflow. It was a reliable place to find good answers to many technical questions. If you asked any questions there, you probably also know that it was a toxic hellhole—you often got criticized for not having basic knowledge or not understanding error messages.</p>
<p>This isn't exactly a secret. In 2018, Stack Overflow published a blog post entitled <a href="https://stackoverflow.blog/2018/04/26/stack-overflow-isnt-very-welcoming-its-time-for-that-to-change/?cb=1">Stack Overflow Isn't Very Welcoming. It's Time for That to Change</a>.</p>
<p>I don't know about you, but I never felt like behavior on the site got any better. Any time I asked a question I braced for impact. Was I about to get smacked down?</p>
<p>Recently, a <a href="https://data.stackexchange.com/stackoverflow/query/1926661#graph">graph of the total number of Stack Overflow questions over time</a> started making rounds on the internet:</p>
<p><img src="https://pcloadletter.dev/img/stackoverflow.png" alt="Total number of Stack Overflow question over time"></p>
<p>This graph shows a steady decline in usage from about 2017 to 2023 and then usage falls off a cliff. In a Reddit posts asking why developers are moving away from Stack Overflow, one user <a href="https://www.reddit.com/r/programming/comments/1q6e9ml/comment/ny6xz3q/">put it pretty succinctly</a>:</p>
<p><em>"Because I can get an answer from an LLM (which does need to be verified) in less than a minute versus the hours or days I would have to wait to get a toxic and potentially useless reply on stackoverflow. They should really downsize or just kill the company it’s a relic of the past and most developers won’t miss it."</em></p>
<p>This is how a lot of us feel. Developer sentiment on generative AI can be mixed, but we know that at least it won't be an asshole to us, unlike many "helpers" on Stack Overflow.</p>
<p>One question I have is if we would have been slower to abandon Stack Overflow if it was a welcoming community. I don't know. Getting answers from generative AI would <em>still</em> have been faster.</p>
<p>I suspect we may have fought a little harder to preserve Stack Overflow in some capacity if it was a positive place. And I think this should be a lesson to other communities out there: instead of relying solely on being necessary, you should also be a positive place. Because when the next thing comes along and makes you less necessary, people won't hesitate to abandon you.</p>
Cool project, will you actually maintain it?2026-01-04T00:00:00Zhttps://pcloadletter.dev/blog/cool-project/<p>I have seen a lot of cool, open source projects posted online. Helpful libraries, starter templates, frameworks. What I often don't see, however, is a plan for maintaining them.</p>
<p>Long after the dopamine wears off from the upvotes and karma from the initial post about your project, you'll be left with a bunch of code and possibly some people now using that code in their applications. This is a responsibility that I don't think many consider fully.</p>
<p>If you have a full stack starter template, will you actually make sure to keep dependencies up-to-date so new users don't start out with vulnerabilities? If your project is an npm library, will you keep up with dependencies to make sure existing users aren't stuck with your vulnerable package?</p>
<p>If you're thinking of open sourcing something, I ask you to please think beyond the initial dopamine hit and ask yourself: will I really maintain this?</p>
Software craftsmanship is dead2026-01-03T00:00:00Zhttps://pcloadletter.dev/blog/craftsmanship-is-dead/<p>"Ship it!"</p>
<p>"We're agile now, baby. Move fast and break things!""</p>
<p>"We measure our engineers by the <em>impact</em> they have!"</p>
<p>Somewhere along the way, in the midst of the agilification of software, or the software engineer salary gold rush, we forgot about <em>craftsmanship</em>.</p>
<p>I have been in big tech, startups, consultancies, and even government. These are all different environments with one key similarity: code quality is <em>low</em>, especially as of late.</p>
<p>Don't get me wrong, there are pockets of good code quality. Isolated instances of true care and craftsmanship. But, by and large, what I see now is people trying to ship products as fast as possible without regard for the maintenance burden 1, 2, 5, 10 years down the road.</p>
<p>So, what's going on? I don't truly know, but here are my top contending theories.</p>
<h2 id="perverse-incentives-surrounding-impact" tabindex="-1">Perverse incentives surrounding "impact" <a class="header-anchor" href="https://pcloadletter.dev/blog/craftsmanship-is-dead/">#</a></h2>
<p>Big tech is all about considering "impact" when evaluating engineer performance and making promotion decisions. Unfortunately, "impact" is almost always measured by what features you shipped rather than considering the <em>impact</em> your code had on the long-term maintainability of the codebase.</p>
<p>If you're looking to advance in your career and you want to get paid more money (nothing wrong with either of these things) then it only makes sense that you ship <em>more</em> features—even if the code you're committing is lower quality.</p>
<h2 id="backlog-pressure" tabindex="-1">Backlog pressure <a class="header-anchor" href="https://pcloadletter.dev/blog/craftsmanship-is-dead/">#</a></h2>
<p>I don't blame agile. But I do kind of blame Agile™. Now, more than ever, you're likely to be in an Agile™ environment in which you get overloaded with tasks. Should you focus on improving the quality of the feature you're working on? Or should you get on to the other 17 tasks you have this sprint?</p>
<p>More senior engineers may be able to push back and say, "I need to spend more time to get this right." But junior and mid-level engineers? They need to keep up to make sure they're not considered "slow."</p>
<h2 id="lower-stakes" tabindex="-1">Lower stakes <a class="header-anchor" href="https://pcloadletter.dev/blog/craftsmanship-is-dead/">#</a></h2>
<p>Many of us are now shipping our products over the internet. In some cases, they're fully web-based and in other cases we can ship patches over the web. Regardless, we're hardly ever shipping out a physical CD where we have to be damn well sure the software works. We <em>can</em> afford to "move fast and break things."</p>
<p>But this also means we more readily let shortcuts and untested code into our codebases as well—things that slowly erode at the codebase's quality until we wake up one day and the whole thing feels like a mess.</p>
<h2 id="no-one-is-focusing-on-craftsmanship" tabindex="-1">No one is focusing on craftsmanship <a class="header-anchor" href="https://pcloadletter.dev/blog/craftsmanship-is-dead/">#</a></h2>
<p>The last point I want to make is I feel like it's been forever since I had a conversation about craftsmanship on the job. It's possible I have just found myself in the wrong places time and time again.</p>
<p>Or maybe, and this is something I really fear, maybe the people who truly focus on the craft of software engineering are retiring or getting run off.</p>
The Monty Hall Problem, a side-by-side simulation2026-01-01T00:00:00Zhttps://pcloadletter.dev/blog/monty/<p>I saw a cool version of the Monty Hall game here recently: <a href="https://monty.donk.systems">https://monty.donk.systems</a>.</p>
<p>This is really cool! But I had an itch I wanted to scratch: rather than manually test out the probabilities, I wanted to run two games side-by-side: one where a switch happened and one where a switch didn't happen. Then, running those over and over, we should see the overall win percentages converge to 66.7% for switching and 33.3% for not switching.</p>
<p>So I coded up a simulation here to scratch my itch!</p>
<h2 id="controls" tabindex="-1">Controls <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<p>To control the simulation, you can start or stop here. You can also adjust the speed.</p>
<div class="controls">
<button id="play-btn">Start simulation</button>
<label for="speed-control">Speed:</label>
<input id="speed-control" type="range" min="1" max="1000" value="3">
<span id="speed-display">x3</span>
</div>
<h2 id="score" tabindex="-1">Score <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<p>Cumulative stats as the games go on.</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Plays</th>
<th>Wins</th>
<th>Win Percent</th>
</tr>
</thead>
<tbody>
<tr>
<td>Switch</td>
<td><span id="plays">0</span></td>
<td><span id="wins">0</span></td>
<td><span id="win-pct">-</span></td>
</tr>
<tr>
<td>No switch</td>
<td><span id="ns-plays">0</span></td>
<td><span id="ns-wins">0</span></td>
<td><span id="ns-win-pct">-</span></td>
</tr>
</tbody>
</table>
<h2 id="switch-simulation" tabindex="-1">Switch simulation <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<p>If we switch cards after the first goat reveal.</p>
<section class="board" id="switch-board">
<div class="doors">
<div class="door"><div class="guess"></div><div class="card"></div></div>
<div class="door"><div class="guess"></div><div class="card"></div></div>
<div class="door"><div class="guess"></div><div class="card"></div></div>
</div>
<div class="status"> </div>
</section>
<h2 id="no-switch-simulation" tabindex="-1">No switch simulation <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<p>If we don't switch.</p>
<section class="board" id="no-switch-board">
<div class="doors">
<div class="door"><div class="guess"></div><div class="card"></div></div>
<div class="door"><div class="guess"></div><div class="card"></div></div>
<div class="door"><div class="guess"></div><div class="card"></div></div>
</div>
<div class="status"> </div>
</section>
<h1 id="code" tabindex="-1">Code <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h1>
<p>If interested, the code for this simulation can be viewed here:</p>
<h2 id="html" tabindex="-1">HTML <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<pre class="language-html" tabindex="0"><code class="language-html"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>board<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>switch-board<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>doors<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>section</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>board<span class="token punctuation">"</span></span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>switch-board<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>doors<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>door<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>guess<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>card<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>status<span class="token punctuation">"</span></span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>div</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>section</span><span class="token punctuation">></span></span></code></pre>
<h2 id="javascript" tabindex="-1">JavaScript <a class="header-anchor" href="https://pcloadletter.dev/blog/monty/">#</a></h2>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">let</span> running <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> playBtn <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#play-btn"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> speedControl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#speed-control"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> speedDisplay <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#speed-display"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> speed <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>speedControl<span class="token punctuation">.</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">randi</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token operator">=></span> Math<span class="token punctuation">.</span><span class="token function">floor</span><span class="token punctuation">(</span>Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> n<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">sleep</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">s</span><span class="token punctuation">)</span> <span class="token operator">=></span>
<span class="token keyword">new</span> <span class="token class-name">Promise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">res</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">setTimeout</span><span class="token punctuation">(</span>res<span class="token punctuation">,</span> <span class="token punctuation">(</span>s <span class="token operator">*</span> <span class="token number">1000</span><span class="token punctuation">)</span> <span class="token operator">/</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> speed<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> playsSwitch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> winsSwitch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> playsNoSwitch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> winsNoSwitch <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> playsEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#plays"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> winsEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#wins"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> winPctEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#win-pct"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> nsPlaysEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#ns-plays"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> nsWinsEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#ns-wins"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> nsWinPctEl <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">"#ns-win-pct"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">pct</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">w<span class="token punctuation">,</span> p</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>p <span class="token operator">?</span> <span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">*</span> w<span class="token punctuation">)</span> <span class="token operator">/</span> p<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toFixed</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"%"</span> <span class="token operator">:</span> <span class="token string">"-"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">updateScoreboard</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
playsEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> playsSwitch<span class="token punctuation">;</span>
winsEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> winsSwitch<span class="token punctuation">;</span>
winPctEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token function">pct</span><span class="token punctuation">(</span>winsSwitch<span class="token punctuation">,</span> playsSwitch<span class="token punctuation">)</span><span class="token punctuation">;</span>
nsPlaysEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> playsNoSwitch<span class="token punctuation">;</span>
nsWinsEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> winsNoSwitch<span class="token punctuation">;</span>
nsWinPctEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token function">pct</span><span class="token punctuation">(</span>winsNoSwitch<span class="token punctuation">,</span> playsNoSwitch<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token keyword">class</span> <span class="token class-name">MontyBoard</span> <span class="token punctuation">{</span>
<span class="token function">constructor</span><span class="token punctuation">(</span><span class="token parameter">rootId<span class="token punctuation">,</span> isSwitch</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>root <span class="token operator">=</span> document<span class="token punctuation">.</span><span class="token function">getElementById</span><span class="token punctuation">(</span>rootId<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>isSwitch <span class="token operator">=</span> isSwitch<span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>guessEls <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>root<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">".guess"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>cardEls <span class="token operator">=</span> Array<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>root<span class="token punctuation">.</span><span class="token function">querySelectorAll</span><span class="token punctuation">(</span><span class="token string">".card"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>statusEl <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>root<span class="token punctuation">.</span><span class="token function">querySelector</span><span class="token punctuation">(</span><span class="token string">".status"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>_looping <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>guessEls<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>cardEls<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">el</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">status</span><span class="token punctuation">(</span><span class="token parameter">t</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>statusEl<span class="token punctuation">.</span>textContent <span class="token operator">=</span> t<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">async</span> <span class="token function">playOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>running<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"\u00A0"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> doors <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token string">"G"</span><span class="token punctuation">,</span> <span class="token string">"G"</span><span class="token punctuation">,</span> <span class="token string">"G"</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> winIndex <span class="token operator">=</span> <span class="token function">randi</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
doors<span class="token punctuation">[</span>winIndex<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"C"</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"Initial guess"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">let</span> guessIndex <span class="token operator">=</span> <span class="token function">randi</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>guessEls<span class="token punctuation">[</span>guessIndex<span class="token punctuation">]</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">"✔"</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"Revealing a goat"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> revealChoices <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">i</span><span class="token punctuation">)</span> <span class="token operator">=></span> i <span class="token operator">!==</span> guessIndex <span class="token operator">&&</span> i <span class="token operator">!==</span> winIndex
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> doorToReveal <span class="token operator">=</span> revealChoices<span class="token punctuation">[</span><span class="token function">randi</span><span class="token punctuation">(</span>revealChoices<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>cardEls<span class="token punctuation">[</span>doorToReveal<span class="token punctuation">]</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">"G"</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isSwitch <span class="token operator">?</span> <span class="token string">"Switching choice"</span> <span class="token operator">:</span> <span class="token string">"Not switching choice"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isSwitch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>guessEls<span class="token punctuation">[</span>guessIndex<span class="token punctuation">]</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">""</span><span class="token punctuation">;</span>
guessIndex <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">i</span><span class="token punctuation">)</span> <span class="token operator">=></span> i <span class="token operator">!==</span> guessIndex <span class="token operator">&&</span> i <span class="token operator">!==</span> doorToReveal
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>guessEls<span class="token punctuation">[</span>guessIndex<span class="token punctuation">]</span><span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">"✔"</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token string">"Reveal"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">0.5</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>cardEls<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">el<span class="token punctuation">,</span> i</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>el<span class="token punctuation">.</span>textContent <span class="token operator">=</span> doors<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> win <span class="token operator">=</span> guessIndex <span class="token operator">===</span> winIndex<span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>isSwitch<span class="token punctuation">)</span> <span class="token punctuation">{</span>
playsSwitch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>win<span class="token punctuation">)</span> winsSwitch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
playsNoSwitch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>win<span class="token punctuation">)</span> winsNoSwitch<span class="token operator">++</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span>win <span class="token operator">?</span> <span class="token string">"Reveal (WIN!)"</span> <span class="token operator">:</span> <span class="token string">"Reveal (LOSE!)"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">updateScoreboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>_looping<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>_looping <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token function-variable function">loop</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token keyword">while</span> <span class="token punctuation">(</span>running<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">playOnce</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token keyword">this</span><span class="token punctuation">.</span>_looping <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token function">loop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span class="token keyword">const</span> boards <span class="token operator">=</span> <span class="token punctuation">[</span>
<span class="token keyword">new</span> <span class="token class-name">MontyBoard</span><span class="token punctuation">(</span><span class="token string">"switch-board"</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token keyword">new</span> <span class="token class-name">MontyBoard</span><span class="token punctuation">(</span><span class="token string">"no-switch-board"</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token punctuation">]</span><span class="token punctuation">;</span>
playBtn<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"click"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
running <span class="token operator">=</span> <span class="token operator">!</span>running<span class="token punctuation">;</span>
playBtn<span class="token punctuation">.</span>textContent <span class="token operator">=</span> running <span class="token operator">?</span> <span class="token string">"Stop simulation"</span> <span class="token operator">:</span> <span class="token string">"Start simulation"</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>running<span class="token punctuation">)</span> boards<span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">b</span><span class="token punctuation">)</span> <span class="token operator">=></span> b<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
speedControl<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"input"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">e</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
speed <span class="token operator">=</span> <span class="token function">parseInt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token number">1</span><span class="token punctuation">;</span>
speedDisplay<span class="token punctuation">.</span>textContent <span class="token operator">=</span> <span class="token string">"x"</span> <span class="token operator">+</span> speed<span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">updateScoreboard</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<style>
.board { background: rgba(255, 255, 255, 0.06); padding: 0.75rem; border-radius: 8px; margin-bottom: 1rem; }
.doors { display: grid; grid-template-columns: repeat(3, 72px); gap: 16px; align-items: start; justify-content: start; padding: 1.25rem 0 0.25rem; }
.door { width: 72px; height: 90px; border: 2px solid rgba(255, 255, 255, 0.25); border-radius: 6px; display: grid; place-items: center; position: relative; font-weight: 600; }
.door .card { font-size: 1.25rem; }
.door .guess { position: absolute; top: -1.25rem; left: 50%; transform: translateX(-50%); font-size: 0.95rem; }
.status { font-size: 0.9rem; opacity: 0.8; margin-top: 0.25rem; min-height: 1.25rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.controls { display: flex; gap: 1rem; align-items: center; flex-wrap: wrap; }
.controls input[type="range"] { width: 240px; }
.stat-table td, .stat-table th { padding: 0.25rem 0.5rem; }
</style>
<script>
let running = false;
const playBtn = document.querySelector('#play-btn');
const speedControl = document.querySelector('#speed-control');
const speedDisplay = document.querySelector('#speed-display');
let speed = parseInt(speedControl.value);
const randi = (n) => Math.floor(Math.random() * n);
const sleep = (s) => new Promise(res => setTimeout(res, (s * 1000) / Math.max(1, speed)));
let playsSwitch = 0;
let winsSwitch = 0;
let playsNoSwitch = 0;
let winsNoSwitch = 0;
const playsEl = document.querySelector('#plays');
const winsEl = document.querySelector('#wins');
const winPctEl = document.querySelector('#win-pct');
const nsPlaysEl = document.querySelector('#ns-plays');
const nsWinsEl = document.querySelector('#ns-wins');
const nsWinPctEl = document.querySelector('#ns-win-pct');
const pct = (w, p) => p ? (100 * w / p).toFixed(1) + '%' : '-';
const updateScoreboard = () => {
playsEl.textContent = playsSwitch;
winsEl.textContent = winsSwitch;
winPctEl.textContent = pct(winsSwitch, playsSwitch);
nsPlaysEl.textContent = playsNoSwitch;
nsWinsEl.textContent = winsNoSwitch;
nsWinPctEl.textContent = pct(winsNoSwitch, playsNoSwitch);
};
class MontyBoard {
constructor(rootId, isSwitch) {
this.root = document.getElementById(rootId);
this.isSwitch = isSwitch;
this.guessEls = Array.from(this.root.querySelectorAll('.guess'));
this.cardEls = Array.from(this.root.querySelectorAll('.card'));
this.statusEl = this.root.querySelector('.status');
this._looping = false;
}
clear() {
this.guessEls.forEach(el => el.textContent = '');
this.cardEls.forEach(el => el.textContent = '');
}
status(t) { this.statusEl.textContent = t; }
async playOnce() {
if (!running) return;
this.status('\u00A0');
this.clear();
const doors = ['G','G','G'];
const winIndex = randi(3);
doors[winIndex] = 'C';
await sleep(0.5);
this.status('Initial guess');
await sleep(0.5);
let guessIndex = randi(3);
this.guessEls[guessIndex].textContent = '✔';
await sleep(0.5);
this.status('Revealing a goat');
await sleep(0.5);
const revealChoices = [0,1,2].filter(i => i !== guessIndex && i !== winIndex);
const doorToReveal = revealChoices[randi(revealChoices.length)];
this.cardEls[doorToReveal].textContent = 'G';
await sleep(0.5);
this.status(this.isSwitch ? 'Switching choice' : 'Not switching choice');
await sleep(0.5);
if (this.isSwitch) {
this.guessEls[guessIndex].textContent = '';
guessIndex = [0,1,2].find(i => i !== guessIndex && i !== doorToReveal);
this.guessEls[guessIndex].textContent = '✔';
}
await sleep(0.5);
this.status('Reveal');
await sleep(0.5);
this.cardEls.forEach((el, i) => el.textContent = doors[i]);
const win = guessIndex === winIndex;
if (this.isSwitch) {
playsSwitch++;
if (win) winsSwitch++;
} else {
playsNoSwitch++;
if (win) winsNoSwitch++;
}
this.status(win ? 'Reveal (WIN!)' : 'Reveal (LOSE!)');
updateScoreboard();
await sleep(2);
}
start() {
if (this._looping) return;
this._looping = true;
const loop = async () => {
while (running) {
await this.playOnce();
}
this._looping = false;
};
loop();
}
}
const boards = [
new MontyBoard('switch-board', true),
new MontyBoard('no-switch-board', false)
];
playBtn.addEventListener('click', () => {
running = !running;
playBtn.textContent = running ? 'Stop simulation' : 'Start simulation';
if (running) boards.forEach(b => b.start());
});
speedControl.addEventListener('input', (e) => {
speed = parseInt(e.target.value) || 1;
speedDisplay.textContent = 'x' + speed;
});
updateScoreboard();
</script>
I hope generative AI does away with SEO2025-12-30T00:00:00Zhttps://pcloadletter.dev/blog/ai-and-seo/<p>Have you ever searched for something on Google and found the first one, two, or three blog posts to be utter nonsense? That's because these blog posts have been optimized not for human consumption, but rather to entertain the search engine ranking algorithms. People have figured out the right buzzwords to include in headings, how to game backlinks, and research keywords to write up blog posts about things they know nothing about. Pleasing these bots means raking in the views—and ad revenue (or product referrals, sales leads, etc.).</p>
<p>Search Engine Optimization (SEO) may have been the single worst thing that happened to the web. Every year it seems like search results get worse than the previous. The streets of the internet are littered with SEO junk.</p>
<p>But now, we may have an escape from this SEO hellscape: generative AI!</p>
<p>Think about it: if AI-generated search results (or even direct use of AI chat interfaces) subsumes web search as a primary way to look up information, there will be no more motivation to crank out SEO-driven content. These kinds of articles will fade into obscurity as the only purpose for their existence (monetization) is gone.</p>
<p>Perhaps we will be left with the blogosphere of old with webrings and RSS (not that these things went away but they're certainly not mainstream anymore).</p>
<p>This, anyways, is my hope. No more blogging to entertain the robots. Just writing stuff you want to write and share with other like-minded folks online.</p>
npm needs an analog to pnpm's minimumReleaseAge and yarn's npmMinimalAgeGate2025-12-28T00:00:00Zhttps://pcloadletter.dev/blog/npm-min-release-age/<p>With the growing number of supply chain attacks in the npm ecosystem (for example, <a href="https://securitylabs.datadoghq.com/articles/shai-hulud-2.0-npm-worm/">Shai-Hulud</a>), package managers like pnpm and yarn have added a very handy configuration option that prevents the install of new packages: <code>minimumReleaseAge</code> and <code>npmMinimalAgeGate</code>, respectively.</p>
<p>These options allow developers to specify the minimum age of a dependency before it gets installed. For example, a development team could decide that if a package version has been in the wild for three days, then it's probably safe. This would be set up as follows:</p>
<p>pnpm:</p>
<pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span>
<span class="token property">"minimumReleaseAge"</span><span class="token operator">:</span> <span class="token number">1440</span> <span class="token comment">// in minutes</span>
<span class="token punctuation">}</span></code></pre>
<p>yarn:</p>
<pre class="language-json" tabindex="0"><code class="language-json"><span class="token punctuation">{</span>
<span class="token property">"npmMinimalAgeGate"</span><span class="token operator">:</span> <span class="token string">"3d"</span>
<span class="token punctuation">}</span></code></pre>
<p>Both package managers have a way to allowlist packages to bypass these rules as necessary.</p>
<p>This kind of safeguard is nice because the same thing that makes npm supply chain attacks valuable is what makes age gates effective: npm's popularity. Packages that receive a lot of use should receive a lot of scrutiny and there's a good chance any attempted attacks will be sniffed out fairly quickly.</p>
<p>npm really doesn't have this feature, which seems bad. There has been <a href="https://github.com/npm/cli/pull/8825">at least one attempt to add such a feature</a>, but it was closed in favor of <a href="https://github.com/npm/cli/pull/8802">an existing PR to enhance the --before flag</a> to parse relative dates. The way this would work is:</p>
<pre class="language-sh" tabindex="0"><code class="language-sh"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">--before</span><span class="token operator">=</span>3d</code></pre>
<p>Or the <code>before=3d</code> can be added to <code>.npmrc</code>.</p>
<p>But this is insufficient because there is no escape hatch for trusted packages, which is important for internal dependencies.</p>
<p>Also, there is no mention of using the <code>--before</code> flag as a security mechanism <a href="https://docs.npmjs.com/cli/11/using-npm/config#before">in the npm docs</a>. Perhaps this is pedantic, but I would like to see the npm docs focus explicitly on security and if this <code>--before</code> flag is intended to be a security mechanism it should be stated as such.</p>
Make your PR process resilient to AI slop2025-12-24T00:00:00Zhttps://pcloadletter.dev/blog/pr-review-ai-slop-resilience/<p>One concern I have heard from AI naysayers is that AI slop will make code reviews nearly impossible. AI churns out so much code, documentation, etc. that it's just impossible for any reviewer to keep up with it... right?</p>
<p>Wrong!</p>
<p>If you have good PR review processes, then reviewing AI-assisted code shouldn't be any more onerous than reviewing any other code.</p>
<p>Here are some concerns I have heard and my response.</p>
<h1 id="ai-generates-tons-of-code" tabindex="-1">AI generates tons of code <a class="header-anchor" href="https://pcloadletter.dev/blog/pr-review-ai-slop-resilience/">#</a></h1>
<p>It's true that AI can sometimes generate tons of code—but your PR process shouldn't allow for massive PRs in the first place! If I get a 100 file PR today, I wouldn't review that. I'd respectfully ask the author to break the PR down into smaller, atomic pieces of work that can be reviewed more carefully. I'd ask this whether or not AI helped generate the code.</p>
<p>As an aside, AI can actually generate small, digestible diffs! You just need to prompt in a way to do so. I have found being more methodical in walking AI through the problem step-by-step not only results in more digestible diffs, but also results in higher-quality code.</p>
<h1 id="ai-generates-low-quality-code" tabindex="-1">AI generates low quality code <a class="header-anchor" href="https://pcloadletter.dev/blog/pr-review-ai-slop-resilience/">#</a></h1>
<p>I don't quite know what to say to this one! If you're not reviewing PRs for quality in the first place, then that's a problem. Just apply your regular level of vetting to AI-assisted code as you would regular code. If you don't currently review PRs closely, then the problem isn't the quality of the code—it's that you're phoning it in during PR reviews.</p>
<h1 id="people-just-accept-whatever-ai-outputs-without-understanding-it" tabindex="-1">People just accept whatever AI outputs without understanding it <a class="header-anchor" href="https://pcloadletter.dev/blog/pr-review-ai-slop-resilience/">#</a></h1>
<p>I tend to review PRs pretty closely and ask questions about anything I don't understand or think may be wrong. If you author a PR and are unable to answer the questions I have about your code, then it's not making it into the codebase. Again, this is as true today as it was 10 years ago. AI or not, I am going to make sure your code makes sense!</p>
<h1 id="ai-can-use-outdated-vulnerable-dependencies" tabindex="-1">AI can use outdated/vulnerable dependencies <a class="header-anchor" href="https://pcloadletter.dev/blog/pr-review-ai-slop-resilience/">#</a></h1>
<p>If you add third-party dependencies in a PR, that should be considered a "bigger deal" than some folks treat it today (I'm looking at you, node ecosystem). Your PR review process should include evaluating what new dependencies are being added and a review of the installed version. Ideally, there should also be some consideration of whether an external dependency is even needed.</p>
<p>Outside of the PR review process, you should ideally have automated dependency scanning (sonarqube, dependabot, etc.) that will detect vulnerable dependencies.</p>
<h1 id="conclusion" tabindex="-1">Conclusion <a class="header-anchor" href="https://pcloadletter.dev/blog/pr-review-ai-slop-resilience/">#</a></h1>
<p>If you're worried about AI "slop" making its way into your codebase, consider how your prevent human "slop" from making its way into your codebase. PR reviews are a critical tool for this—and should remain one as we explore this new AI-assisted world.</p>
AI hype is excessive, but its productivity gains are real2025-10-19T00:00:00Zhttps://pcloadletter.dev/blog/ai-hype-and-productivity/<p>I'm kind of writing this to "past" me, who I assume is "current" you for a number of folks out there. For the rest of you, this might just sound like ramblings of an old fogey super late to the party.</p>
<p>Yes, AI is over-hyped. LLMs will not solve every problem under the sun but, like with any hot new tech, companies are going to say it will solve every problem out there, especially problems in the domain space of the company.</p>
<p>Startups who used to be "uber for farmers" are now "AI-powered uber for farmers." You can't get away from it. It's exhausting.</p>
<p>I let the hype exhaustion get the best of me for a while and eschewed the tech entirely.</p>
<p>Well, I was wrong to do so. This became clear when my company bought Cursor licenses for all software developers in the company and strongly encouraged us to use it. I reluctantly started experimenting.</p>
<p>The first thing I noticed is that LLM-powered autocomplete was wildly accurate. It seemed like it "knew" what I wanted to do next at every turn. Due to my discomfort with AI, I just stuck with autocomplete for a while. And, honestly, if I stuck with just using autocomplete it would <em>still</em> have been a massive level up.</p>
<p>I remember having a few false starts with the agent panel in Cursor. I felt totally out of control when it was making changes to all sorts of files when I asked it a simple question. I have since figured out how to ask more directed questions, provide constraints, and supply markdown files in the codebase with general instructions.</p>
<p>I now find the agent panel really helpful. I use it to help understand parts of a codebase, scaffold entirely new services or unit tests, and track down bugs.</p>
<p>As a former skeptic, I am a wildly more productive developer with AI tooling. I let my aversion to the hype train cause me to miss out on those productivity gains for too long. I hope you don't make the same mistake.</p>
Write code that you can understand when you get paged at 2am2024-05-25T00:00:00Zhttps://pcloadletter.dev/blog/clever-code/<p>The older I get, the more I dislike clever code. This is not a controversial take; it is pretty-well agreed upon that clever code is bad.</p>
<p>But I particularly like the on-call responsiblity framing: <em>write code that you can understand when you get paged at 2am</em>.</p>
<p>If you have never been lucky enough to get paged a 2am, I'll paint the picture for you:</p>
<p>A critical part of the app is down. Your phone starts dinging on your nightstand next to you. You wake up with a start, not quite sure who you are or where you are. You put on your glasses and squint at the way-too-bright screen of your phone. It's PagerDuty. "Oh shit," you think. You pop open your laptop, open the PagerDuty web app, and read the alert. You go to your telemetry and logging systems and figure out approximate whereabouts in the codebase the issue is.</p>
<p>You open your IDE and start sweating: "I have no idea what the hell any of this code means." The git blame shows you wrote the code 2 years ago. You thought that abstraction was pretty clever at the time, but now you're paying a price: your code is inscrutable to an exhausted, stressed version of yourself who just wants to get the app back online.</p>
<h2 id="reasons-for-clever-code" tabindex="-1">Reasons for clever code <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h2>
<p>There are a few reasons for clever code that I have seen over my career.</p>
<h3 id="thinking-clever-code-is-inherently-good" tabindex="-1">Thinking clever code is inherently good <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h3>
<p>I think at some point a lot of engineers end up in a place where they become very skilled in a language before they understand the importance of writing clean, readable code. Consider the following two javascript snippets:</p>
<p><strong>snippet 1</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">const</span> sum <span class="token operator">=</span> items<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>
<span class="token punctuation">(</span><span class="token parameter">acc<span class="token punctuation">,</span> el</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> el <span class="token operator">===</span> <span class="token string">"number"</span> <span class="token operator">?</span> acc <span class="token operator">+</span> el <span class="token operator">:</span> acc<span class="token punctuation">)</span><span class="token punctuation">,</span>
<span class="token number">0</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>snippet 2</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">let</span> sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
<span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> item <span class="token keyword">of</span> items<span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> item <span class="token operator">===</span> <span class="token string">"number"</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
sum <span class="token operator">=</span> sum <span class="token operator">+</span> item<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span></code></pre>
<p>At one point in my career, I would have assumed the first snippet was superior: fewer lines and uses the <code>reduce</code> method! But I promise far more engineers can very quickly and easily understand what's going on in the second snippet. I would much rather the second snippet in my codebase any day.</p>
<h3 id="premature-abstraction" tabindex="-1">Premature abstraction <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h3>
<p>Premature abstractions tend to be pretty common in object-oriented languages. <a href="https://softwareengineering.stackexchange.com/a/386573">This stackexchange answer</a> made me laugh quite a bit, so I'll use it as an example. Let's say you have a system with employee information. Well perhaps you decide employees are types of humans, so we'd better have a human class, and humans are a type of mammal, so we'd better have a mammal class, and so on. All of a sudden, you might have to navigate several layers up to the animal class to see an employee's properties and methods.</p>
<p>As the stackexchange answer succinctly put it:</p>
<blockquote>
<p>As a result, we ended up with code that really only needed to deal with, say, records of employees, but were carefully written to be ready if you ever hired an arachnid or maybe a crustacean.</p>
</blockquote>
<h3 id="dry-dogma" tabindex="-1">DRY dogma <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h3>
<p>Don't Repeat Yourself (DRY) is a coding philosophy where you try to <em>minimize</em> the amount of code repeated in your software. In theory, even repeating code once results in an increased chance that you'll miss updating the code in both places or having inconsistent behavior when you have to implement the code somewhere else.</p>
<p>In practice, DRYing up code can sometimes be complex. Perhaps there is a little repeated code shared between client and server. Do we need to create a way to share this logic? If it's only one small instance, it simply may not be worth the complexity of sharing logic. If this is going to be a common issue in the codebase, then perhaps centralizing the logic <em>is</em> worth it. But importantly we can't just assume that one instance of repeated code means we <em>must</em> eliminate the redundancy.</p>
<h2 id="what-should-we-aim-for-instead" tabindex="-1">What should we aim for instead? <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h2>
<p>There's definitely a balance to be struck. We can't have <em>purely dumb</em> code with no abstractions: that ends up being pretty error prone. Imagine you're working with an API that has some set of required headers. Forcing all engineers to remember to include those headers with every API call is error-prone.</p>
<p><strong>file1</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/users"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">Authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token literal-property property">AppVersion</span><span class="token operator">:</span> version<span class="token punctuation">,</span>
<span class="token literal-property property">XsrfToken</span><span class="token operator">:</span> xsrfToken<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">fetch</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>userId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">Authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token literal-property property">AppVersion</span><span class="token operator">:</span> version<span class="token punctuation">,</span>
<span class="token literal-property property">XsrfToken</span><span class="token operator">:</span> xsrfToken<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>file2</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/transactions"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">Authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token literal-property property">AppVersion</span><span class="token operator">:</span> version<span class="token punctuation">,</span>
<span class="token literal-property property">XsrfToken</span><span class="token operator">:</span> xsrfToken<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>file3</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">fetch</span><span class="token punctuation">(</span><span class="token string">"/api/settings"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
<span class="token literal-property property">headers</span><span class="token operator">:</span> <span class="token punctuation">{</span>
<span class="token literal-property property">Authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token literal-property property">AppVersion</span><span class="token operator">:</span> version<span class="token punctuation">,</span>
<span class="token literal-property property">XsrfToken</span><span class="token operator">:</span> xsrfToken<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>Furthermore, having to track down every instance of that API call to update the headers (or any other required info) could be challenging. In this instance, it makes a lot of sense to create some kind of API service that encapsulates the header logic:</p>
<p><strong>service</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">function</span> <span class="token function">apiRequest</span><span class="token punctuation">(</span><span class="token parameter"><span class="token operator">...</span>args</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">const</span> <span class="token punctuation">[</span>url<span class="token punctuation">,</span> headers<span class="token punctuation">,</span> <span class="token operator">...</span>rest<span class="token punctuation">]</span> <span class="token operator">=</span> args<span class="token punctuation">;</span>
<span class="token keyword">return</span> <span class="token function">fetch</span><span class="token punctuation">(</span>
url<span class="token punctuation">,</span>
<span class="token punctuation">{</span>
<span class="token operator">...</span>headers<span class="token punctuation">,</span>
<span class="token literal-property property">Authorization</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Bearer </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>token<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span>
<span class="token literal-property property">AppVersion</span><span class="token operator">:</span> version<span class="token punctuation">,</span>
<span class="token literal-property property">XsrfToken</span><span class="token operator">:</span> xsrfToken<span class="token punctuation">,</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token operator">...</span>rest
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p><strong>file1</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">apiRequest</span><span class="token punctuation">(</span><span class="token string">"/api/users"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">apiRequest</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/api/users/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>userId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>file2</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">apiRequest</span><span class="token punctuation">(</span><span class="token string">"/api/transactions"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p><strong>file3</strong></p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token function">apiRequest</span><span class="token punctuation">(</span><span class="token string">"/api/settings"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The <code>apiRequest</code> function is a pretty helpful abstraction. It helps that it is a very minimal abstraction: just enough to prevent future engineers from making mistakes but not so much that it's confusing.</p>
<p>These kinds of abstractions, however, can get out of hand. I have see code where making a request looks something like this:</p>
<pre class="language-js" tabindex="0"><code class="language-js"><span class="token keyword">const</span> <span class="token constant">API_PATH</span> <span class="token operator">=</span> <span class="token string">"api"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">USER_PATH</span> <span class="token operator">=</span> <span class="token string">"user"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">TRANSACTIONS_PATH</span> <span class="token operator">=</span> <span class="token string">"transactions"</span><span class="token punctuation">;</span>
<span class="token keyword">const</span> <span class="token constant">SETTINGS_PATH</span> <span class="token operator">=</span> <span class="token string">"settings"</span><span class="token punctuation">;</span>
<span class="token function">createRequest</span><span class="token punctuation">(</span>
endpointGenerationFn<span class="token punctuation">,</span>
<span class="token punctuation">[</span><span class="token constant">API_PATH</span><span class="token punctuation">,</span> <span class="token constant">USER_PATH</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token function">getHeaderOverrides</span><span class="token punctuation">(</span><span class="token string">"authenticated"</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">createRequest</span><span class="token punctuation">(</span>
endpointGenerationFn<span class="token punctuation">,</span>
<span class="token punctuation">[</span><span class="token constant">API_PATH</span><span class="token punctuation">,</span> <span class="token constant">USER_PATH</span><span class="token punctuation">,</span> userId<span class="token punctuation">]</span><span class="token punctuation">,</span>
<span class="token function">getHeaderOverrides</span><span class="token punctuation">(</span><span class="token string">"authenticated"</span><span class="token punctuation">)</span>
<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>There's really no need for this. You're not saving all that much for making variables instead of using strings for paths. In fact, this ends up making it <em>really hard</em> for someone debugging the code to search! Typically, I'd lok for the string <code>"api/user"</code> in my IDE to try to find the location of the request. Would I be able to find it with this abstraction? Would I be able to find it <em>at 2am</em>?</p>
<p>Furthermore, passing an endpoint-generation function that consumes the path parts seems like overkill and may be inscrutable to more junior engineers (or, again, 2am you).</p>
<h1 id="keep-it-as-simple-as-possible" tabindex="-1">Keep it as simple as possible <a class="header-anchor" href="https://pcloadletter.dev/blog/clever-code/">#</a></h1>
<p>So I think in the end my message is to keep your code as simple <em>as possible</em>. Don't create some abstraction that may or may not be needed eventually. Weigh the maintenance value of DRYing up parts of your codebase versus readability.</p>
The ChatGPT wrapper product boom is an uncanny valley hellscape2024-05-20T00:00:00Zhttps://pcloadletter.dev/blog/llms/<p><img src="https://pcloadletter.dev/img/cocomelon-dad.png" alt="dad from Cocomelon"></p>
<p>Here we go again: I'm so tired of <s>crypto</s> <s>web3</s> LLMs.</p>
<p>I'm positive there are wonderful applications for LLMs. The ChatGPT web UI seems great for summarizing information from various online sources (as long as you're willing to verify the things that you learn).</p>
<p>But a lot fo the "AI businesses" coming out right now are just lightweight wrappers around ChatGPT. It's lazy and unhelpful.</p>
<p>Probably the worst offenders are in the <em>content marketing</em> space.</p>
<p>We didn't know how lucky we were back in the "This one weird trick for saving money" days. Now, rather than a human writing that junk, we have every article sounding like the writing voice equivalent of the dad from Cocomelon.</p>
<p>Here's an approximate technical diagram of how these businesses work:</p>
<p><a href="https://pcloadletter.dev/img/llm-business-model.png"><img src="https://pcloadletter.dev/img/llm-business-model.png" alt="ChatGPT wrapper business model"></a></p>
<p>Part 1 is what I like to call the "bilking process." Basically, you put up a flashy landing page promising content generation in exchange for a monthly subscription fee (or discounted annual fee, of course!). No more paying pesky writers!</p>
<p>Once the husk of a company has secured the bag, part 2, the "bullshit process," kicks in. Customers provide their niches and the service happily passes queries over to the ChatGPT (or similar) API. Customers are rewarded with stinky garbage articles that sound like they're being narrated by HAL on Prozac in return. Success!</p>
<p>I suppose we should have expected as much. With every new tech trend comes a deluge of tech investors trying to find the next great thing. And when this happens, it's a gold rush every time.</p>
<p>I will say I'm more optimistic about "AI" (aka machine learning, aka <em>statistics</em>).</p>
<p>There are going to be some pretty cool applications of this tech eventually—but your ChatGPT wrapper ain't it.</p>
Quality is a hard sell in big tech2024-02-23T00:00:00Zhttps://pcloadletter.dev/blog/big-tech-quality/<p>I have noticed a trend in a handful of products I've worked on at big tech companies. I have friends at other big tech companies that have noticed a similar trend: The products are kind of crummy.</p>
<p>Here are some experiences that I have often encountered:</p>
<ul>
<li>the UI is flakey and/or unintuitive</li>
<li>there is a lot of cruft in the codebase that has never been cleaned up</li>
<li>bugs that have "acceptable" workarounds that never get fixed</li>
<li>packages/dependencies are badly out of date</li>
<li>the developer experience is crummy (bad build times, easily breakable processes)</li>
</ul>
<p>One of the reasons I have found for these issues is that we simply aren't investing enough time to increase product quality: we have poorly or nonexistent quality metrics, invest minimally in testing infrastructure (and actually writing tests), and don't invest in improving the inner loop. But why is this?</p>
<p>My experience has been that quality is simply a hard sell in bigh tech.</p>
<p>Let's first talk about something that's an <em>easy</em> sell right now: AI everything. Why is this an easy sell? Well, Microsoft could announce they put ChatGPT in a toaster and their stock price would jump $5/share. The sad truth is that big tech is hyper-focused on doing the things that make their stock prices go up in the short-term.</p>
<p>It's hard to make this connection with quality initiatives. If your software is slightly less shitty, the stock price won't jump next week. So instead of being able to sell the obvious benefit of shiny new features, you need to have an Engineering Manager willing to risk having lower <a href="https://pcloadletter.dev/blog/impact-based-performance-evaluation">impact</a> for the sake of having a better product. Even if there is broad consensus in your team, group, org that these quality improvements are necessary, there's a point up the corporate hierarchy where it simply doesn't matter to them. Certainly not as much as shipping some feature to great fanfare.</p>
<h2 id="part-of-a-bigger-strategy" tabindex="-1">Part of a bigger strategy? <a class="header-anchor" href="https://pcloadletter.dev/blog/big-tech-quality/">#</a></h2>
<p>Cory Doctorow has <a href="https://pluralistic.net/2023/11/22/who-wins-the-argument/#corporations-are-people-my-friend">said some interesting things</a> about <em>enshittification</em> in big tech:</p>
<p><em>"enshittification is a three-stage process: first, surpluses are allocated to users until they are locked in. Then they are withdrawn and given to business-customers until they are locked in. Then all the value is harvested for the company's shareholders, leaving just enough residual value in the service to keep both end-users and business-customers glued to the platform."</em></p>
<p>At a macro level, it's possible this is the strategy: hook users initially, make them dependent on your product, and then cram in superficial features that make the stock go up but don't offer real value, and keep the customers simply because they really have no choice but to use your product (an enterprise Office 365 customer probably isn't switching anytime soon).</p>
<p>This does seem to have been a good strategy in the <em>short-term</em>: look at Microsoft's stock ever since they started cranking out AI everything. But how can the quality corner-cutting work long-term?</p>
<h2 id="i-hope-the-hubris-will-backfire" tabindex="-1">I hope the hubris will backfire <a class="header-anchor" href="https://pcloadletter.dev/blog/big-tech-quality/">#</a></h2>
<p>Something will have to give. Big tech products can't just keep getting shittier—can they? I'd like to think some smaller competitors will come eat their lunch, but I'm not sure. Hopefully we're not all too entrenched in the big tech ecosystem for this to happen.</p>
Agile is a tainted term2024-02-16T00:00:00Zhttps://pcloadletter.dev/blog/agile/<p>Oh no, not another agile article.</p>
<p>But at least this one isn't attempting to teach or reconcile. I'm not going to talk about the difference between agile and Agile™ nor will I try to convince you of my favorite flavor of Agile™.</p>
<p>Instead, I'm here to assert that agile is a tainted term: it has become a hollow buzzword, stripped of all meaning and drowned in a sea of convoluted processes and certifications. It has more baggage than benefit.</p>
<p>Don't get me wrong, the underlying principles of the Agile Manifesto are great. The ideals help minimize the amount of time you spend building the wrong thing. Requirements are hard and open communication is good. Delivering value and working software is also good.</p>
<p>But here's the problem with using the word "agile" anymore: everyone has their own understanding of what it means, and that understanding is usually closer to some process like Scrum or Kanban or (god help you) SAFe. When you say the word "agile," you'll see one developer break out into a cold sweat because it was used to make her work at 110% capacity until she burned out. Another developer will weep at the thought of an overbearing scrum master or pointless ceremonies.</p>
<p><img src="https://pcloadletter.dev/img/SAFe.jpeg" alt="SAFe diagram">
<small style="display: block; text-align: center">Figure 1: SAFe, what the fuck</small></p>
<p>To reset expecations with anyone who has been traumatized by some shitty process masquerading as "doing agile," you essentially have to spell out the principles of the Agile Manifesto... and therefore you didn't need the word "agile" in the first place.</p>
<p>There are some takes out there that agile is dead. I don't think that's quite true, but the <em>word</em> "agile" is certainly dead dead. It's devoid of any meaning because it means everything and nothing all at once. The principles of agile development are still wonderful and useful, but there is no longer a singular word that can helpfully convey them. Maybe there really <em>never was</em>, but for sure none exists today.</p>
Surely dark UX doesn't work in the long run2024-02-14T00:00:00Zhttps://pcloadletter.dev/blog/dark-ux/<p>I was just feeling pretty good—I published <a href="https://pcloadletter.dev/blog/rss">my article about RSS</a> and it's being pretty well-received.</p>
<p>I decided a fitting way to celebrate was to head on over to Feedly and catch up on some reading! I clicked on an engineers blog feed to check out here latest couple posts. I notied an ad in the middle of the feed. That's fair: I am using a free version of Feedly so it only makes sense they'd show me an ad.</p>
<p>I clicked "X" in the top corner of the ad. This is where things went sideways.</p>
<p><img src="https://pcloadletter.dev/img/dark-ux.gif" alt="when clicking the X, I got a message telling me the only way to hide the add was a paid Feedly membership"></p>
<p>Instead of dismissing the ad, Feedly shows me a popup/tooltip informing me that the only way to remove "this module" (read: advertisment) is to "Back Feedly directly via Feedly Pro."</p>
<p>Again, I have no problem that Feedly advertises on its free tier, but providing a dismiss button that doesn't work is dark UX. I feel deceived. I'm not going to pay for your service, I'm not going to click on the ad.</p>
<h2 id="dark-ux-warning-signs" tabindex="-1">Dark UX warning signs <a class="header-anchor" href="https://pcloadletter.dev/blog/dark-ux/">#</a></h2>
<p>I'm going to assume the best here: Feedly didn't know they had implemented dark UX and would readily fix it once pointed out. To help prevent such issues in the future, I'm going to list some tips here to help recognize when you may be implementing dark UX. I'm a web engineer, so that's where my focus will be. I'm sure the principles apply to other engineering disciplines as well.</p>
<h3 id="the-action-doesn-t-match-the-event-handler" tabindex="-1">The action doesn't match the event handler <a class="header-anchor" href="https://pcloadletter.dev/blog/dark-ux/">#</a></h3>
<p>I can tell Feedly uses React so let's pretend they're using a React component library and this ad is being shown in a <code>FeedCard</code> component.</p>
<pre class="language-jsx" tabindex="0"><code class="language-jsx"><span class="token keyword">function</span> <span class="token function">Feed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> <span class="token punctuation">(</span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span></span><span class="token punctuation">></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FeedCard</span></span><span class="token punctuation">></span></span><span class="token plain-text">First post</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">FeedCard</span></span><span class="token punctuation">></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FeedCard</span></span>
<span class="token attr-name">onDismiss</span><span class="token script language-javascript"><span class="token script-punctuation punctuation">=</span><span class="token punctuation">{</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token function">showSubscribePopup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">}</span></span>
<span class="token punctuation">></span></span><span class="token plain-text">
Ad content
</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">FeedCard</span></span><span class="token punctuation">></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation"><</span><span class="token class-name">FeedCard</span></span><span class="token punctuation">></span></span><span class="token plain-text">Second post</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span><span class="token class-name">FeedCard</span></span><span class="token punctuation">></span></span><span class="token plain-text">
</span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span></span><span class="token punctuation">></span></span>
<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span></code></pre>
<p>Notice how our event handler is called <code>onDismiss</code>, yet when we call it, we're not dismissing the card. Instead, we're showing a popup asking people to subscribe. It should be clear from the <code>onDismiss</code> property name that the intended action here is dismissing the card, not showing some other UI.</p>
<h3 id="you-re-intercepting-events-and-doing-something-user-facing-solely-for-your-benefit" tabindex="-1">You're intercepting events and doing something user-facing solely for your benefit <a class="header-anchor" href="https://pcloadletter.dev/blog/dark-ux/">#</a></h3>
<p>There are plenty of legitimate reasons to listen to events (it's why they exist!). But if you're intercepting a user's events and showing them something solely for your benefit, you're probably implementing a dark UX pattern.</p>
<p>A good example of this is listening to the scroll event and showing a popup when someone gets a certain distance down the page:</p>
<pre class="language-js" tabindex="0"><code class="language-js">window<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"scroll"</span><span class="token punctuation">,</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>window<span class="token punctuation">.</span>scrollY <span class="token operator">>=</span> <span class="token number">1000</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token function">showSubscribePopup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>The user is just trying to scroll your website and <em>bam</em>, a popup to the face. I'm actually surprised at how prevalent this pattern is these days given how unpleasant it is.</p>
<h3 id="someone-is-trying-to-leave-and-you-try-to-stop-them" tabindex="-1">Someone is trying to leave and you try to stop them <a class="header-anchor" href="https://pcloadletter.dev/blog/dark-ux/">#</a></h3>
<p>This one is really rough. Browsers have done a decent job at tamping down <a href="https://javascript.info/onload-ondomcontentloaded#window.onbeforeunload"><code>unload</code> and <code>beforeunload</code> event abuse</a>, but there are still some ways to be obnoxious when someone is trying to leave your page. One common dark pattern right now is to show a popup when someone's mouse leaves the document.</p>
<pre class="language-js" tabindex="0"><code class="language-js">document<span class="token punctuation">.</span><span class="token function">addEventListener</span><span class="token punctuation">(</span><span class="token string">"mouseleave"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
<span class="token function">showSubscribePopup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
<p>When I find this happening, I'm usually about to type something in the URL bar or close the tab. The "Wait don't go yet" popup is visually jarring.</p>
<p>This, by the way, is very different than if a user is editing a document on your web app and you prevent them from leaving because they may lose their work. As always, context is everything.</p>
<h2 id="the-rule-of-thumb-don-t-subvert-user-expectations" tabindex="-1">The rule of thumb: don't subvert user expectations <a class="header-anchor" href="https://pcloadletter.dev/blog/dark-ux/">#</a></h2>
<p>The rule of thumb when you're designing or engineering systems that users interact with is to not subvert their expectations. If there's a UI element (e.g., an "X" button) that has a common behavior, don't make your "X" button do something different. If someone is trying to use native browser functionality, don't override it (without really good reason that benefits the user).</p>
<p>Perhaps some of these dark UX patterns work in the short term. Maybe you get a couple extra email signups or some paid memberships. But longer term, I have to imagine this catches up with you: your website or brand's reputation takes a hit. Or at least that's what I'll choose to believe to make myself feel better.</p>
RSS is still pretty great2024-02-11T00:00:00Zhttps://pcloadletter.dev/blog/rss/<p>I think a lot about information and information consumption. The way the Internet made information readily available is phenomenal. Sadly, the signal-to-noise ratio here is pretty low. For me, consuming RSS feeds<a href="https://pcloadletter.dev/blog/rss/">[1]</a> offers the best way to read the kind of high-quality information that I want with very little noise.</p>
<h2 id="what-rss-is-and-how-it-works" tabindex="-1">What RSS is and how it works <a class="header-anchor" href="https://pcloadletter.dev/blog/rss/">#</a></h2>
<p>RSS stands for Really Simple Syndication. Any site that publishes content may choose to additionally publish an RSS feed file based on a specification (e.g., <a href="https://www.rssboard.org/rss-specification">RSS</a> or <a href="https://www.ietf.org/rfc/rfc4287.txt">Atom syndication</a>). This feed will contains enough information to show its content elsewhere (that's the syndication part!). These specifications are really stable, with RSS last being updated in 2009 and Atom syndication last being updated in 2005.</p>
<p>Here's an example of what an RSS feed using the Atom spec looks like:</p>
<pre class="language-xml" tabindex="0"><code class="language-xml"><span class="token tag"><span class="token tag"><span class="token punctuation"><</span>feed</span> <span class="token attr-name">xmlns</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>http://www.w3.org/2005/Atom<span class="token punctuation">"</span></span> <span class="token attr-name"><span class="token namespace">xml:</span>base</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>en<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>pcloadletter<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>subtitle</span><span class="token punctuation">></span></span>What the **** does that mean?<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>subtitle</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://pcloadletter.dev/feed/feed.xml<span class="token punctuation">"</span></span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>self<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://pcloadletter.dev/<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>updated</span><span class="token punctuation">></span></span>2024-02-11T00:00:00Z<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>updated</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span><span class="token punctuation">></span></span>https://pcloadletter.dev/<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>id</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>entry</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>title</span><span class="token punctuation">></span></span>RSS is still pretty great<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>title</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://pcloadletter.dev/blog/rss/<span class="token punctuation">"</span></span><span class="token punctuation">/></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>updated</span><span class="token punctuation">></span></span>2024-02-12T00:00:00Z<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>updated</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>id</span><span class="token punctuation">></span></span>https://pcloadletter.dev/blog/rss/<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>id</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>content</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>html<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>I think a lot about information and information consumption. The way the Internet made information readily available is phenomenal. Sadly, the signal-to-noise ratio here is pretty low. For me, consuming RSS feeds<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://pcloadletter.dev/blog/rss/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>[1]<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span> offers the best way to read the kind of high-quality information that I want with very little noise.<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>p</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>h2</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>what-rss-is-and-how-it-works<span class="token punctuation">"</span></span> <span class="token attr-name">tabindex</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>-1<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>What RSS is and how it works <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>a</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>header-anchor<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">"</span>https://pcloadletter.dev/blog/rss/<span class="token punctuation">"</span></span><span class="token punctuation">></span></span>#<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>a</span><span class="token punctuation">></span></span><span class="token tag"><span class="token tag"><span class="token punctuation"></</span>h2</span><span class="token punctuation">></span></span> <span class="token tag"><span class="token tag"><span class="token punctuation"><</span>p</span><span class="token punctuation">></span></span>RSS stands for Really Simple Syndication. Any site that publishes content may choose
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>content</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>entry</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"><</span>entry</span><span class="token punctuation">></span></span>
...
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>entry</span><span class="token punctuation">></span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation"></</span>feed</span><span class="token punctuation">></span></span></code></pre>
<p>That's actually the feed for this blog, and you can <a href="https://www.pcloadletter.dev/feed/feed.xml">see the rest of it here</a> if you want.</p>
<p>Now that a website has a published feed, anyone can consume it. While a reader could theoretically parse your RSS feed themselves, it's much more likely they're using an RSS feed aggregator (e.g., Feedly, NewsBlur, Inoreader) to subscribe to, and consume, multiple RSS feeds at once.</p>
<p>In the end, user interaction with your content looks something like this:</p>
<p><img src="https://pcloadletter.dev/img/rss-diagram.png" alt="rss block diagram"></p>
<p>While all RSS aggregators can act differently, they generally poll feeds based on update frequency and content popularity and cache the results.</p>
<p>If you're a content publisher, it's usually pretty simple to get RSS set up on your website. There are surely some RSS plugins for popular authoring platforms like Wordpress. For this site, I use the 11ty static site generator and it also has an RSS feed generation plugin.</p>
<p>If you need to generate your own feed, there are plenty of open source options. For example, <a href="https://github.com/lkiesow/python-feedgen">python-feedgen</a> will let you get up and running with a feed in just a few lines:</p>
<pre class="language-python" tabindex="0"><code class="language-python"><span class="token keyword">from</span> feedgen<span class="token punctuation">.</span>feed <span class="token keyword">import</span> FeedGenerator
fg <span class="token operator">=</span> FeedGenerator<span class="token punctuation">(</span><span class="token punctuation">)</span>
fg<span class="token punctuation">.</span><span class="token builtin">id</span><span class="token punctuation">(</span><span class="token string">'https://pcloadletter.dev'</span><span class="token punctuation">)</span>
fg<span class="token punctuation">.</span>title<span class="token punctuation">(</span><span class="token string">'pcloadletter'</span><span class="token punctuation">)</span>
fg<span class="token punctuation">.</span>subtitle<span class="token punctuation">(</span><span class="token string">'What the **** does that mean?'</span><span class="token punctuation">)</span>
fg<span class="token punctuation">.</span>language<span class="token punctuation">(</span><span class="token string">'en'</span><span class="token punctuation">)</span>
<span class="token comment"># etc</span></code></pre>
<h2 id="why-this-model-is-really-good-for-consuming-high-quality-content" tabindex="-1">Why this model is really good for consuming high-quality content <a class="header-anchor" href="https://pcloadletter.dev/blog/rss/">#</a></h2>
<p>There are some attributes of RSS that make it innately good for consuming high-quality content.</p>
<p>The biggest pro of RSS is choice: I decide what blogs or sites to include in my RSS aggreator feed. There is no algorithm engineered to milk the maximum amount of interaction out of me. If I start disliking what an author is publishing, I simple unsubscribe from their feed. I am in total control of the content I consume.</p>
<p>Another selling point, for me, is that RSS feeds bias towards long-term content. This is usually content published to some person or organization's blog and the author has put in a good amount of time and effort into the post. They've had time to think, "Should I really write this on the Internet?" (a thought pattern I've found is sorely missing from social media).</p>
<p>Along the same lines, RSS reduces low-value content. It tends to greatly reduce the amount of content that exists sole for the sake of advertising. Often, when you're googling something and find a promising site, you'll visit it and be dismayed that the content is exceedingly shallow but there are plenty of ads to be found on the site. This doesn't really happen on RSS feeds because a lot of ads (especially the obnoxious ones) won't render in RSS feed aggregators.</p>
<p>There are no flame wars and no internet points on RSS. Social media has its value, but I really can't stand the flame wars, trolling, and people generally performing for Internet points. If there's an author I like, I just want to read what they wrote without having to wade through a sea of contrarian comments.</p>
<p>One final reason I really like RSS is that it's just-in-time: I can get to my content when I need it. I totally understand it's convenient for some to receive an email in their inbox with a website's latest article, but I don't love it. I'm almost never ready to ready the article when it hits my inbox, so now I have to decide what to do with the email. For me, it's a much better workflow to just browse to Feedly when I have the time and read.</p>
<h2 id="unfair-criticisms-of-rss" tabindex="-1">Unfair criticisms of RSS <a class="header-anchor" href="https://pcloadletter.dev/blog/rss/">#</a></h2>
<p>RSS isn't without its detractors, but I think a lot of arguments against it are unfair.</p>
<p>One criticism is that publishers don't get analytics on who is reading their content. I don't think everything needs analytics. I also think analytics are closely related to one of the bigger content-corrupting forces on the Internet: ads.</p>
<p>RSS makes it hard to advertise and therefore publishers looking to make ad revenue off their content may be unwilling to use it. I don't think it's entirely wrong to want to monetize content, but I do see it as a big motivator for a lot of the bad content out there. When you make it hard to monetize, you reduce the number of "get rich quick" schemes involved.</p>
<p>Another criticism is that RSS isn't social and doesn't innately enable interaction. You know what else isn't social or interactive? Books. But they're still great. Sometimes you just want to read something without telling the world your opinion on it, or without some random stranger telling you why your opinion is wrong.</p>
<p>There's a class of criticisms about RSS due to fact that you effectively need to poll an RSS feed to get updates to it, so you don't get near real-time updates. Furthermore, some of the criticisms involve the fact that you could end up getting a lot of traffic to your RSS feed.</p>
<p>On the former point, I think it's a good thing that you don't get near real-time updates on RSS feeds. I mentioned before that social media has value: I think more real-time content is one of those. There was a time when Twitter was the go-to place to find out what was going on with developing situations. That's not the purpose of RSS feeds. If anything, the fact that we really can't get real-time updates from RSS helps keep the content focuses towards deliberate works.</p>
<p>With respect to traffic: I can't imagine this is actually a problem. If you're a large site maybe you have a number of aggregators hitting your RSS feed endpoint every couple minutes. That's not really that much, and is probably small in comparison to traffic to your actual website.</p>
<p>One final unfair criticism of RSS is that it's dying or dead. Every so often I see a post or <a href="https://www.wired.com/story/rss-readers-feedly-inoreader-old-reader/">article</a> asserting that RSS is dead or dying. It's true RSS isn't as prevalent as it used to be, probably because it's hard to monetize as a publisher, but that's fine. It doesn't need to be <em>wildly</em> popular for it to be great. Why does everything need a billion upvotes and maximum popularity to be worthy? I think we should enjoy RSS for filling a somewhat niche use case really well.</p>
<h2 id="fair-criticisms" tabindex="-1">Fair criticisms <a class="header-anchor" href="https://pcloadletter.dev/blog/rss/">#</a></h2>
<p>There are definitely some fair criticisms of RSS.</p>
<p>First, aggregators don't render content perfectly. It does for the type of content I write, but if you publish content with interesting visuals (e.g., svg animation, canvas, page scroll interaction) I don't think any RSS aggregator will display it correctly. This means there's potentially a whole class of interesting content that's not RSS-friendly. One approach here is to use RSS as a "preview" of sorts and then visit the actual content if you're interested in the associated visuals.</p>
<p>Another fair criticism is lack of discoverability. No matter how great RSS is for reading blogs I know about, it has no inherent capability to discover content that you don't know about. Some feed aggregators may have this kind of functionality as an add-on, but it's simply something that's not possible with how underlying RSS technology works.</p>
<p>Given that you just publish your feed and allow aggregators to interpret that feed, you also give up a bit of control about the publishing process. For example, there is really no way to tell aggregators that you changed an article's path. I did this a couple times on this blog, and you can <a href="https://feedly.com/i/subscription/feed%2Fhttps%3A%2F%2Fwww.pcloadletter.dev%2Ffeed%2Ffeed.xml">see the duplicates in Feedly here</a>.</p>
<p>Finally, and one of the biggest issues with RSS, is that it's not very intuitive to non-tech savvy individuals. If someone non-technical clicks the RSS link on the bottom of my website, their eyes might glaze over at the sight of a bunch of XML. Their best chance of subscribing to my RSS feed may be to go to an aggregator and use the search feature in its UI to find this blog.</p>
<h2 id="final-thoughts" tabindex="-1">Final thoughts <a class="header-anchor" href="https://pcloadletter.dev/blog/rss/">#</a></h2>
<p>As long as you're using RSS for its purpose (consuming content) and not trying to use it for something it's not designed for (social media, content discovery), it's still a really solid technology. If you've ditched RSS in favor of social media in the past decades, consider adding RSS back into your life!</p>
<small>
<a name="1">
[1] Note: I'm lazily using the term RSS to refer to all syndication feeds. This site uses Atom, for example. Kind of like how we use Kleenex to refer to any kind of facial tissue.
</a>
</small>
Impact-based performance evaluation in big tech is terrible2024-02-07T00:00:00Zhttps://pcloadletter.dev/blog/impact-based-performance-evaluation/<p><img src="https://pcloadletter.dev/img/impact.jpg" alt="Office Space scene with printer"></p>
<p>My theory is that some performance consultants got paid a lot of money one day for a single word: "impact."</p>
<p>If you have worked in big tech, you're probably all too familiar with this word because your annual performance evaluations are based on your impact.</p>
<p>As an employee, impact-based performance evaluation is a disaster. For companies, I think it still may end up being pretty bad.</p>
<h2 id="impact-what-the-f-ck-does-that-mean" tabindex="-1">Impact? What the f*ck does that mean? <a class="header-anchor" href="https://pcloadletter.dev/blog/impact-based-performance-evaluation/">#</a></h2>
<p>We all kind of understand what it means to "have impact." Your presence helps move the company in a positive direction in some way (tied to profit, of course).</p>
<p>But from the perspective of "am I good at my job" or "how do I get a raise" or "how do I get a promotion," being told to "have more impact" means nothing. Do you need to ship more features? Better quality? (<a href="https://pcloadletter.dev/blog/craftsmanship">just kidding</a>). Perhaps you need to lead a new initiative to increase test coverage or improve quality.</p>
<p>If you're lucky, your manager helps you define the things you need to accomplish your next career step. But in my experience they don't have great answers either.</p>
<p>So we thrash.</p>
<p>I have seen this play out in a few different ways:</p>
<ul>
<li>Some people work really hard. Way too hard. This is probably what the company wants.</li>
<li>Some people become excellent self-promoters. You'll get emails about all the incredible work they and their team have done. Kudos to everyone on my team, especially me!</li>
<li>Some people just kind of give up trying to understand impact. The promotion will come if it comes. This is probably healthiest, but not what career-minded people want to hear.</li>
</ul>
<p>None of this is healthy. Some of it benefits the company, at least in the short term.</p>
<h2 id="impact-above-all-else" tabindex="-1">Impact above all else <a class="header-anchor" href="https://pcloadletter.dev/blog/impact-based-performance-evaluation/">#</a></h2>
<p>When impact is the goal, we optimize for impact (or whatever we perceive to be impact—see previous section).</p>
<p>Often, people believe impact to be about shipping features and new initiatives. So we'll crank out a feature or some new innitiaitve with reckless disregard for (1) it's quality and (2) if it's even needed.</p>
<p>Are you in big tech and wonder why there are twelve nearly identical logging services? Impact, baby! Each of those initiatives was led by an engineer looking to make impact.</p>
<p>Is this really beneficial for big tech companies? To have redundancies all over the place in the name of impact? To ship low-quality features so we can spike the football in an organization-wide email?</p>
<h2 id="no-one-is-measuring-the-negative-impact-of-all-this" tabindex="-1">No one is measuring the negative impact of all this <a class="header-anchor" href="https://pcloadletter.dev/blog/impact-based-performance-evaluation/">#</a></h2>
<p>Engineers are leaving massive amounts of tech debt in their wake as they cram high-impact features into their codebases. This isn't always (or often) evident when the feature ships, but will become clear within the next few years. Development speed slows down as the team battles bugs from the slipshod feature implementations.</p>
<p>The engineers who wrote the code already cashed in for that sweet impact and have been promoted or they've fled the scene of the crime to another organization or company.</p>
<p>As the next engineer onboards to organization they can't help but think to themselves, "How can I possibly make an impact in this nightmare codebase?"</p>