I built a route, called it /tickets, and Fastify heard /tickets/tickets. Like the app got so excited about tickets it started stuttering. Pizza! Pizza!
The stutter, explained
Here’s what’s actually going on. This is a real-time support ticket app built on Fastify inside an NX monorepo, and Fastify’s autoload plugin does something genuinely convenient most of the time: it reads your folder structure and turns it directly into your route structure. Drop a file at routes/tickets/create.ts, and Fastify automatically treats everything inside it as living under /tickets, no manual registration required.
Convenient, until you forget it’s happening. I wrote a route inside that exact file and registered it as /tickets:
typescript
// routes/tickets/create.ts
export default async function (fastify: FastifyInstance) {
fastify.post('/tickets', async (request, reply) => {
// create a new ticket
});
}
I wasn’t asking for /tickets. I already had /tickets, for free, from the folder. What I actually registered was /tickets again, stapled on top of the one Fastify had already given me. The real, final route: /tickets/tickets. Fastify didn’t warn me. It didn’t complain. It just quietly served exactly what I’d asked for, twice, like I’d clearly meant to say the word with extra enthusiasm.
It bit twice, in opposite directions
Weeks later, same underlying rule, opposite mistake. A download route lived one folder deeper than I’d tracked in my head: routes/tickets/attachments/download.ts. This time I remembered the folder was contributing something, so I deliberately wrote the route’s own path as the full thing, /attachments/:id, expecting the folder prefix to only add /tickets on top, the way it had the first time.
Except this file sat one level deeper than the first bug, so the folder was actually contributing /tickets/attachments, not just /tickets. My route path assumed one level of prefix and got two. Final result: /tickets/attachments/attachments/:id. Stuttered again. Different word, same tell, same underlying mistake: writing a route path as if I had perfect, current knowledge of exactly what was already being prepended above it, when I didn’t.
Both times, the actual fix was a single line, either drop the redundant segment or add the missing one. Both times, the bug wasn’t really about Fastify’s autoload behavior being wrong. It’s genuinely useful, once you internalize it. It was about carrying an assumption in my head that had gone stale the moment the folder structure shifted underneath it.
The part that stopped being about code
Turns out, in real life, we want to be repetitiously redundant. An ATS filter and a tired recruiter, roughly six seconds a resume by most published estimates, thousands of applications deep into a single posting, want the exact same thing a route file wants: the keyword, stapled to the top like a fresh cover sheet slapped on a TPS report nobody asked to see twice, then repeated seven, eight, all the way up to a genuinely unlucky thirteen times, because apparently keyword number thirteen is the one that finally convinces the algorithm you meant it.
Every resume template on earth ships with a block that looks roughly like this:
TECHNICAL SKILLS
Languages: Python, PHP, Ruby, Elixir, JavaScript, TypeScript
Frameworks: React, Django, Laravel, Rails, Phoenix
Web Development: HTML, CSS, RESTful APIs, JWT, JSON, AJAX
Database: MySQL, MariaDB, PostgreSQL
And then, if the advice is followed correctly, the exact same words show up again in the summary at the top. Again scattered through the project bullets. Again in the experience section, describing work from four years ago in the present tense because the keyword still needs to be there. Repeated on purpose, deliberately, because that repetition is precisely what survives the first filter, the one that never has a human behind it at all.
A senior hiring manager, the actual person who eventually reads what survives that filter, sees that same stutter and reads it completely differently. Not “thorough.” Not “comprehensive.” Unfocused. Padded. Someone who doesn’t know what actually matters about their own experience, so they listed everything flat, no hierarchy, no sense of priority, hoping volume does the work that judgment was supposed to do. The exact repetition engineered to get past the first, mechanical gate is the same repetition that earns a quiet, silent “next” the moment an actual person is finally the one reading it.
Same stutter. Two completely different judges standing on opposite sides of the same line of text, wanting entirely opposite things from it, and nobody tells you which judge you’re currently writing for until it’s too late to matter.