<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ruhid Ibadli</title>
    <description>The latest articles on DEV Community by Ruhid Ibadli (@ruhidibadli).</description>
    <link>https://dev.to/ruhidibadli</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1036130%2F095a6797-806c-43e4-83b2-1d445133fde9.jpg</url>
      <title>DEV Community: Ruhid Ibadli</title>
      <link>https://dev.to/ruhidibadli</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ruhidibadli"/>
    <language>en</language>
    <item>
      <title>How to Escape Tutorial Hell and Build Something Real (A Practical Roadmap)</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Thu, 05 Mar 2026 13:56:11 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/how-to-escape-tutorial-hell-and-build-something-real-a-practical-roadmap-42b4</link>
      <guid>https://dev.to/ruhidibadli/how-to-escape-tutorial-hell-and-build-something-real-a-practical-roadmap-42b4</guid>
      <description>&lt;p&gt;You've finished 3 Udemy courses. You've followed along with 20 YouTube tutorials. You can build a todo app with your eyes closed — as long as someone is telling you exactly what to type.&lt;/p&gt;

&lt;p&gt;But open a blank VS Code window and try to build something from scratch?&lt;/p&gt;

&lt;p&gt;Nothing. Blank screen. Panic.&lt;/p&gt;

&lt;p&gt;Welcome to Tutorial Hell. Almost every developer has been here. Most never talk about how they got out.&lt;/p&gt;

&lt;p&gt;I'm going to.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Tutorial Hell Happens
&lt;/h2&gt;

&lt;p&gt;It's not a motivation problem. It's a &lt;strong&gt;knowledge type&lt;/strong&gt; problem.&lt;/p&gt;

&lt;p&gt;Tutorials teach &lt;strong&gt;recognition knowledge&lt;/strong&gt; — you see the solution and think "that makes sense." But building requires &lt;strong&gt;recall knowledge&lt;/strong&gt; — you need to produce the solution from nothing.&lt;/p&gt;

&lt;p&gt;These are completely different cognitive processes. Watching someone cook doesn't make you a chef. Following a tutorial doesn't make you a developer.&lt;/p&gt;

&lt;p&gt;The gap between "I understand this code" and "I can write this code" is enormous. Tutorials never close that gap because they never force you to struggle.&lt;/p&gt;

&lt;p&gt;And struggle is where learning actually happens.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5-Stage Escape Plan
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Stage 1: Stop Consuming. Today.
&lt;/h3&gt;

&lt;p&gt;Not tomorrow. Not "after this one last course." Today.&lt;/p&gt;

&lt;p&gt;Close the tutorial. Close the Udemy tab. Close the YouTube playlist. You have enough knowledge to start building. You've had enough knowledge for weeks — you just didn't believe it.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: &lt;strong&gt;you're watching tutorials because it feels productive without the risk of failure.&lt;/strong&gt; Following along successfully gives you a dopamine hit without the pain of debugging, being stuck, or writing bad code.&lt;/p&gt;

&lt;p&gt;That comfort is the trap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Unsubscribe from one coding tutorial channel right now. Not because they're bad — because you need to break the habit of consuming instead of creating.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stage 2: Pick a Laughably Small Project
&lt;/h3&gt;

&lt;p&gt;Your first real project should be embarrassingly simple. Not "build a full-stack e-commerce platform." Not "create a social media clone." Something you can finish in a weekend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good first projects:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A personal bookmark manager (save links with tags, search them)&lt;/li&gt;
&lt;li&gt;A daily expense logger (add amounts, see weekly total)&lt;/li&gt;
&lt;li&gt;A weather dashboard (call one API, display the data)&lt;/li&gt;
&lt;li&gt;A countdown timer to an event you care about&lt;/li&gt;
&lt;li&gt;A recipe box (add recipes, search by ingredient)&lt;/li&gt;
&lt;li&gt;A markdown note-taker (type markdown, see preview)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why small works:&lt;/strong&gt; Finishing a project teaches you more than starting ten projects. The lessons are in the last 20% — deployment, edge cases, error handling, making it actually usable. You only reach the last 20% if the project is small enough to finish.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Pick one project from the list above or invent your own. Write it down. That's your project. No changing it later.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stage 3: The "Stuck Protocol"
&lt;/h3&gt;

&lt;p&gt;You will get stuck. Not "might" — will. Multiple times per day. This is normal. This is where learning happens. But you need a system for getting unstuck, or you'll run back to tutorials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 30-Minute Rule:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you hit a wall, follow this exact sequence:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Minutes 0-10: Try it yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read the error message. Actually read it — most error messages tell you exactly what's wrong.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;console.log&lt;/code&gt; or &lt;code&gt;print()&lt;/code&gt; statements to trace the flow.&lt;/li&gt;
&lt;li&gt;Check if it's a typo. It's almost always a typo.&lt;/li&gt;
&lt;li&gt;Re-read your code line by line, out loud if needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Minutes 10-20: Search specifically.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google the exact error message in quotes.&lt;/li&gt;
&lt;li&gt;Search Stack Overflow with the technology name + what you're trying to do.&lt;/li&gt;
&lt;li&gt;Read the official documentation for the function/method you're using.&lt;/li&gt;
&lt;li&gt;Don't search "how to build X" — search "how to do this specific small thing."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Minutes 20-30: Ask for help.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Post on Stack Overflow, Reddit, or a Discord community.&lt;/li&gt;
&lt;li&gt;Describe what you expected, what happened instead, and what you tried.&lt;/li&gt;
&lt;li&gt;Don't ask "how do I build a bookmark manager" — ask "how do I save a JSON object to localStorage and retrieve it."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After 30 minutes:&lt;/strong&gt; If you're still stuck, move to a different part of the project. Work on the UI. Work on a different feature. Come back to the problem later with fresh eyes.&lt;/p&gt;

&lt;p&gt;The goal isn't to never be stuck. The goal is to &lt;strong&gt;be stuck productively.&lt;/strong&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Stage 4: Build in Public (Even If Nobody's Watching)
&lt;/h3&gt;

&lt;p&gt;This is the accelerator most people skip.&lt;/p&gt;

&lt;p&gt;"Building in public" doesn't mean you need 10K Twitter followers. It means making your process visible — even if the audience is just you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily dev log.&lt;/strong&gt; At the end of each coding session, write 3-5 sentences about what you built, what broke, and what you learned. Keep it in a simple text file or a markdown doc in your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Day 3 - March 4&lt;/span&gt;

Built the "add bookmark" form. Took forever to figure out
controlled components in React. The input wasn't updating
because I forgot to use the setter from useState.
Also learned that localStorage only stores strings,
so I need JSON.stringify/parse. Obvious in hindsight.

Tomorrow: build the search/filter feature.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;It forces reflection.&lt;/strong&gt; Writing about what you learned solidifies it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It shows progress.&lt;/strong&gt; On day 12, you can look back at day 1 and see how far you've come.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It creates content.&lt;/strong&gt; That dev log? It's a blog post draft. Those struggles? They're tweets. Your process is valuable to someone one step behind you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It builds accountability.&lt;/strong&gt; Even if nobody reads it, the act of writing "I didn't code today" is uncomfortable enough to keep you going.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Create a &lt;code&gt;devlog.md&lt;/code&gt; file in your project root. Write your first entry today.&lt;/p&gt;




&lt;h3&gt;
  
  
  Stage 5: Ship It Ugly
&lt;/h3&gt;

&lt;p&gt;Your project won't be perfect. Ship it anyway.&lt;/p&gt;

&lt;p&gt;The CSS will be weird. The error handling will be incomplete. The code structure will make you cringe in 3 months. There will be a bug on mobile that you can't figure out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ship. It. Anyway.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's why: the gap between "working locally" and "deployed on the internet" teaches you more than 5 tutorials combined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to set up a domain&lt;/li&gt;
&lt;li&gt;How to configure environment variables in production&lt;/li&gt;
&lt;li&gt;How to handle CORS&lt;/li&gt;
&lt;li&gt;How HTTPS works&lt;/li&gt;
&lt;li&gt;How to debug something that works locally but breaks in production&lt;/li&gt;
&lt;li&gt;How deployment pipelines work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is taught in tutorials. All of it is essential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free deployment options:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; Vercel, Netlify, GitHub Pages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; Railway, Render, Fly.io&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; Supabase (PostgreSQL), PlanetScale (MySQL)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full-stack:&lt;/strong&gt; A $5 VPS on DigitalOcean or Hetzner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Action:&lt;/strong&gt; Deploy your project before you think it's ready. Get a URL you can share. Put it on your GitHub. The bar isn't "perfect" — the bar is "it exists on the internet."&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happens After Your First Project
&lt;/h2&gt;

&lt;p&gt;Something shifts after you ship your first project without following a tutorial.&lt;/p&gt;

&lt;p&gt;You start seeing code differently. When you encounter a problem, your first instinct is no longer "find a tutorial." It's "I'll figure it out." That instinct is the entire difference between a tutorial consumer and a developer.&lt;/p&gt;

&lt;p&gt;Your second project will be easier. Not because you know more — but because you trust yourself more. You've proven you can be stuck, struggle, and still ship.&lt;/p&gt;

&lt;p&gt;Your third project will be ambitious. You'll pick something that scares you a little. And you'll build it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "What Should I Build?" Framework
&lt;/h2&gt;

&lt;p&gt;If you're stuck on ideas, use this framework:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Solve Your Own Problem
&lt;/h3&gt;

&lt;p&gt;What annoys you? What do you track in a spreadsheet? What do you wish existed?&lt;/p&gt;

&lt;p&gt;The best projects come from personal frustration. You understand the problem deeply, you're the first user, and you actually care about the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Clone Something, Then Change It
&lt;/h3&gt;

&lt;p&gt;Take an app you use and rebuild the core feature. Not all of it — just one feature:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rebuild the tweet composer from Twitter (character count, preview)&lt;/li&gt;
&lt;li&gt;Rebuild the playlist creator from Spotify (drag-and-drop reorder)&lt;/li&gt;
&lt;li&gt;Rebuild the kanban board from Trello (columns, drag cards)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then change one thing. Add a feature the original doesn't have. Now it's not a clone — it's your take on the idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Combine Two Things
&lt;/h3&gt;

&lt;p&gt;Take a concept from one domain and apply it to another:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tinder but for choosing what to cook (swipe recipes)&lt;/li&gt;
&lt;li&gt;GitHub contribution graph but for any habit&lt;/li&gt;
&lt;li&gt;Duolingo's streak system but for reading books&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These often make great portfolio pieces because they're immediately understandable and slightly surprising.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Skills You Actually Need (And The Ones You Don't)
&lt;/h2&gt;

&lt;p&gt;Here's what you need to build a real project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Must have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML/CSS basics (enough to make a form and a list)&lt;/li&gt;
&lt;li&gt;One programming language (JavaScript, Python, whatever you've been learning)&lt;/li&gt;
&lt;li&gt;How to read error messages&lt;/li&gt;
&lt;li&gt;How to use Git (add, commit, push — nothing fancy)&lt;/li&gt;
&lt;li&gt;How to Google effectively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Nice to have:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A frontend framework (React, Vue, Svelte)&lt;/li&gt;
&lt;li&gt;A backend framework (Express, FastAPI, Django)&lt;/li&gt;
&lt;li&gt;Basic database knowledge (SQL or a simple ORM)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;You don't need:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript (learn it after your first project)&lt;/li&gt;
&lt;li&gt;Docker (learn it after your third project)&lt;/li&gt;
&lt;li&gt;CI/CD (learn it when you have something to deploy regularly)&lt;/li&gt;
&lt;li&gt;Testing frameworks (learn it after you've felt the pain of not having tests)&lt;/li&gt;
&lt;li&gt;Kubernetes (you may never need this)&lt;/li&gt;
&lt;li&gt;Microservices (you will definitely never need this for a personal project)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stop learning tools "just in case." Learn them when a real project demands them. The demand creates the motivation, and the context makes the knowledge stick.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Traps (And How to Avoid Them)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Trap 1: "I need to learn X before I can start"
&lt;/h3&gt;

&lt;p&gt;No you don't. Start with what you know. Learn X when you hit a wall that requires it. Just-in-time learning beats just-in-case learning every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 2: "My code is too messy to show anyone"
&lt;/h3&gt;

&lt;p&gt;Everyone's first project is messy. Ship it anyway. Nobody is going to judge your bookmark manager's code structure. And if they do, they've forgotten what it's like to be a beginner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 3: "Someone already built this"
&lt;/h3&gt;

&lt;p&gt;Someone already built everything. That's not the point. You're not building it for the market — you're building it for yourself. The goal is the skill you gain, not the product you ship.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 4: "I'll start after I set up the perfect dev environment"
&lt;/h3&gt;

&lt;p&gt;Spending 3 days configuring ESLint, Prettier, Husky, and a custom VS Code theme is procrastination disguised as productivity. Create React App or &lt;code&gt;npm init&lt;/code&gt; and start writing code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trap 5: Switching projects when it gets hard
&lt;/h3&gt;

&lt;p&gt;The hardest part of every project is the middle. The beginning is exciting. The end is rewarding. The middle is where you're stuck on a bug, the UI looks terrible, and you're questioning why you started.&lt;/p&gt;

&lt;p&gt;Push through the middle. That's where all the learning is.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Realistic Timeline
&lt;/h2&gt;

&lt;p&gt;Here's what the escape from Tutorial Hell actually looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 1:&lt;/strong&gt; Pick a project. Set up the repo. Build the most basic version — one screen, one feature, ugly as hell. Push to GitHub.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2:&lt;/strong&gt; Add one more feature. Hit 3-5 bugs. Solve them. Feel frustrated. Feel proud. Push updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3:&lt;/strong&gt; Try to deploy. Fail. Google the error. Fix it. Deploy again. Get a live URL. Send it to one person.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 4:&lt;/strong&gt; Look at your code from week 1. Cringe. This is growth. Resist the urge to rewrite everything. Add one more feature instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 5+:&lt;/strong&gt; Start your second project. This time, you won't need a roadmap. You'll know what to do.&lt;/p&gt;

&lt;p&gt;Total time: &lt;strong&gt;one month&lt;/strong&gt; from Tutorial Hell to shipped project. Not perfect. Not polished. But real.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Truth Nobody Tells You
&lt;/h2&gt;

&lt;p&gt;Here's what every senior developer knows but most beginners haven't figured out yet:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nobody knows what they're doing at first.&lt;/strong&gt; Every developer you admire went through the same blank-screen panic. The same "I'm not smart enough" thoughts. The same painful debugging sessions. The difference is they pushed through it early enough to build the confidence that compounds over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tutorials are training wheels.&lt;/strong&gt; They're necessary at first. But at some point, you have to take them off and wobble. You'll fall. You'll get back on. And one day you won't even remember what the training wheels felt like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your first project will be bad.&lt;/strong&gt; That's the point. Ship it, learn from it, and build the next one better. The developer who has shipped 5 ugly projects has more real skill than the developer who has completed 50 polished tutorials.&lt;/p&gt;

&lt;p&gt;Stop preparing. Start building. The blank screen is waiting, and it's not as scary as you think.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Are you currently stuck in Tutorial Hell? Or did you escape — and how? Share your story in the comments. Every journey out looks different, and yours might be the one that helps someone else take the first step.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>webdev</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>The Overengineering Trap: You're Building a Spaceship When You Need a Bicycle</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Wed, 04 Mar 2026 19:01:13 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/the-overengineering-trap-youre-building-a-spaceship-when-you-need-a-bicycle-292m</link>
      <guid>https://dev.to/ruhidibadli/the-overengineering-trap-youre-building-a-spaceship-when-you-need-a-bicycle-292m</guid>
      <description>&lt;p&gt;You sit down to build a simple app. A to-do list, maybe. A personal dashboard. A small SaaS.&lt;/p&gt;

&lt;p&gt;Three weeks later, you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Kubernetes cluster&lt;/li&gt;
&lt;li&gt;A microservice architecture with 6 services&lt;/li&gt;
&lt;li&gt;An event-driven message queue&lt;/li&gt;
&lt;li&gt;A custom authentication system with OAuth, SAML, and magic links&lt;/li&gt;
&lt;li&gt;A CI/CD pipeline with 14 stages&lt;/li&gt;
&lt;li&gt;Zero users&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Congratulations. You've built a spaceship to go to the grocery store.&lt;/p&gt;

&lt;p&gt;I've done this. You've done this. Let's talk about why, and how to stop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why We Overengineer
&lt;/h2&gt;

&lt;p&gt;Before the practical stuff, let's be honest about the psychology:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It feels productive.&lt;/strong&gt; Setting up infrastructure &lt;em&gt;feels&lt;/em&gt; like building the product. You're writing code, solving problems, seeing progress. But you're solving imaginary problems for imaginary users at imaginary scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. It's more fun.&lt;/strong&gt; Configuring Kubernetes is more interesting than building a settings page. Writing a custom ORM is more exciting than handling form validation. We gravitate toward interesting problems, not important ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Resume-Driven Development.&lt;/strong&gt; Deep down, we sometimes pick technologies because we want them on our resume, not because the project needs them. "Built a distributed event-sourcing system" looks better in an interview than "used SQLite and a cron job."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Fear of rebuilding.&lt;/strong&gt; "But what if we get 100,000 users and need to scale?" You won't. And if you do, that's the best problem you'll ever have. You can rebuild then.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Tutorial culture.&lt;/strong&gt; Every YouTube tutorial and Medium article shows you the "production-ready" setup. Docker, Redis, Nginx, load balancers, monitoring dashboards. For a project with one user: you.&lt;/p&gt;




&lt;h2&gt;
  
  
  9 Signs You're Overengineering
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Your infrastructure is more complex than your business logic
&lt;/h3&gt;

&lt;p&gt;If your Docker Compose file has more services than your app has features, something is wrong.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This is a red flag for a side project&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;api-gateway&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;auth-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;user-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;notification-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;analytics-service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rabbitmq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;elasticsearch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pgadmin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;12 services. Zero paying customers. Your &lt;code&gt;docker-compose.yml&lt;/code&gt; is doing more work than your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. You're solving scale problems at zero scale
&lt;/h3&gt;

&lt;p&gt;"We need Redis for caching because database queries will be slow at scale."&lt;/p&gt;

&lt;p&gt;How many users do you have? 3? Your database can handle 3 users. PostgreSQL can handle thousands of queries per second on a $5 VPS. You'll run out of motivation before you run out of database capacity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rule of thumb:&lt;/strong&gt; If you can count your users on your fingers, SQLite is probably enough.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. You're abstracting before you have two use cases
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// You have ONE button in your app&lt;/span&gt;
&lt;span class="c1"&gt;// But you built this:&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ButtonProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ButtonVariant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;colorScheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ColorScheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;loadingText&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;spinnerPlacement&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;start&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;iconSpacing&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;leftIcon&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rightIcon&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactElement&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ButtonClickEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onHoverStart&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;onHoverEnd&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;renderAs&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ElementType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 15 more props&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You don't need a design system. You need a &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The rule is simple: &lt;strong&gt;don't abstract until you have at least three concrete use cases.&lt;/strong&gt; Two similar things are a coincidence. Three are a pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. You're writing "reusable" utilities for one-time operations
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# You used this function exactly once
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transform_data_pipeline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;transformers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;validators&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Validator&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;error_handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ErrorHandler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;retry_config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RetryConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;TransformResult&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a function is called in one place, it doesn't need to be configurable. Inline it. Write the specific thing. Three lines of "duplicated" code is better than a premature abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Your setup instructions are longer than your README
&lt;/h3&gt;

&lt;p&gt;If a new developer needs 45 minutes and a PhD in DevOps to run your project locally, you've optimized for the wrong thing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This is all your side project should need:&lt;/span&gt;
git clone repo
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker-compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it takes more than 3 commands, question every additional step.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. You're handling errors that can't happen
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User cannot be None&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Expected User instance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your type system already guarantees most of this. Your ORM won't return a non-User from a User query. Trust your tools. Validate at boundaries (user input, external APIs), not inside your own code.&lt;/p&gt;

&lt;h3&gt;
  
  
  7. You picked a technology stack before understanding the problem
&lt;/h3&gt;

&lt;p&gt;"I'm going to build my next project with Go, gRPC, and Kafka."&lt;/p&gt;

&lt;p&gt;What's the project?&lt;/p&gt;

&lt;p&gt;"I haven't decided yet."&lt;/p&gt;

&lt;p&gt;The stack should serve the problem. If you choose the stack first, you'll bend the problem to fit the stack — and end up overcomplicating simple things because your tools expect complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. You have more config files than source files
&lt;/h3&gt;

&lt;p&gt;Count your root-level config files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.eslintrc.js
.eslintignore
.prettierrc
.prettierignore
.stylelintrc
.editorconfig
.babelrc
tsconfig.json
tsconfig.build.json
tsconfig.node.json
jest.config.ts
vitest.config.ts
postcss.config.js
tailwind.config.js
vite.config.ts
docker-compose.yml
docker-compose.dev.yml
docker-compose.prod.yml
docker-compose.test.yml
Dockerfile
Dockerfile.dev
Dockerfile.prod
.github/workflows/ci.yml
.github/workflows/cd.yml
.github/workflows/codeql.yml
.husky/pre-commit
.husky/pre-push
commitlint.config.js
lint-staged.config.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;28 config files. You haven't written the app yet. Most of these exist because a "best practices" article told you to add them.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. You can't explain your architecture in one sentence
&lt;/h3&gt;

&lt;p&gt;If your architecture requires a 15-minute presentation with a diagram, it's too complex for the problem you're solving.&lt;/p&gt;

&lt;p&gt;Good: "A Python API with a PostgreSQL database and a React frontend."&lt;/p&gt;

&lt;p&gt;Bad: "An event-sourced CQRS architecture with command handlers publishing to a message bus consumed by projection workers that update read models in Elasticsearch, fronted by a BFF gateway layer that aggregates responses for a micro-frontend shell application."&lt;/p&gt;

&lt;p&gt;Both could be solving the same problem. One ships. The other gets presented at a meetup and never launches.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Boring Stack Manifesto
&lt;/h2&gt;

&lt;p&gt;Here's what actually works for 90% of web projects:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Boring Solution&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;One monolithic framework (Django, FastAPI, Rails, Express)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database&lt;/td&gt;
&lt;td&gt;PostgreSQL. That's it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache&lt;/td&gt;
&lt;td&gt;Your database is fast enough. If it's not, add an index.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Background jobs&lt;/td&gt;
&lt;td&gt;Cron. Literally cron.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;JWT + bcrypt. Or use a library.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;A single VPS + Nginx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;GitHub Actions with 2-3 steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring&lt;/td&gt;
&lt;td&gt;Logs. &lt;code&gt;grep&lt;/code&gt; your logs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State management&lt;/td&gt;
&lt;td&gt;React Context or &lt;code&gt;useState&lt;/code&gt;. Not Redux.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Styling&lt;/td&gt;
&lt;td&gt;Tailwind or plain CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No microservices. No Kubernetes. No Redis. No message queues. No service mesh.&lt;/p&gt;

&lt;p&gt;You can serve thousands of concurrent users with a $20/month VPS running a monolith. Basecamp does billions in revenue on a monolith. Stack Overflow serves millions of developers with a handful of servers.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Complexity IS Justified
&lt;/h2&gt;

&lt;p&gt;I'm not saying all complexity is bad. It's justified when:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You have evidence.&lt;/strong&gt; Your database is actually slow (you measured it). Your monolith actually can't handle the load (you profiled it). Your deployment actually needs zero-downtime (users complained).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You have the team.&lt;/strong&gt; Microservices require a team per service. If you're a solo developer or a team of 3, a monolith is not a compromise — it's the correct architecture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The domain is genuinely complex.&lt;/strong&gt; Payment processing, real-time collaboration, distributed systems — some problems require sophisticated solutions. But most CRUD apps are not these problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You'll maintain it.&lt;/strong&gt; The fanciest architecture in the world is technical debt if nobody understands it in 6 months.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Simplicity Checklist
&lt;/h2&gt;

&lt;p&gt;Before adding any technology, library, or architectural pattern, ask:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can I ship without this?&lt;/strong&gt; If yes, don't add it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Am I solving a current problem or a future one?&lt;/strong&gt; If future, don't add it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Will a junior developer understand this?&lt;/strong&gt; If not, simplify it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Am I adding this because I want to learn it?&lt;/strong&gt; That's fine — just be honest about it. Call it a learning project, not a product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can I explain why I need this in one sentence?&lt;/strong&gt; If not, you probably don't need it.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Best Code I Ever Wrote
&lt;/h2&gt;

&lt;p&gt;The best code I ever wrote wasn't clever. It wasn't scalable. It wasn't "production-ready" by any blog post standard.&lt;/p&gt;

&lt;p&gt;It was simple. It worked. It shipped.&lt;/p&gt;

&lt;p&gt;A monolithic Python API. A PostgreSQL database. A React frontend. Deployed on a single VPS with Nginx. Background jobs running on cron. Plain text emails sent with &lt;code&gt;smtplib&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No one has ever complained about the architecture. Users complain about missing features, confusing UI, and slow pages. They never complain about your infrastructure choices.&lt;/p&gt;

&lt;p&gt;Ship the bicycle. You can build the spaceship later — when you actually need to go to space.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's the most overengineered thing you've ever built? I want to hear your confessions in the comments. No judgment — we've all been there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>How I Modeled the Forgetting Curve in 40 Lines of Python</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Wed, 04 Mar 2026 13:04:53 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/how-i-modeled-the-forgetting-curve-in-40-lines-of-python-43mm</link>
      <guid>https://dev.to/ruhidibadli/how-i-modeled-the-forgetting-curve-in-40-lines-of-python-43mm</guid>
      <description>&lt;p&gt;In 1885, Hermann Ebbinghaus sat in a room and memorized nonsense syllables. Then he measured how quickly he forgot them. The result — the &lt;strong&gt;forgetting curve&lt;/strong&gt; — showed that memory decays exponentially. You lose the most in the first few days, then the decline slows.&lt;/p&gt;

&lt;p&gt;140 years later, I used that same curve to build the core algorithm behind &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;SkillFade&lt;/a&gt;, a skill tracking app. Here's exactly how it works, why I made the choices I did, and what I'd change.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Quantifying "Rustiness"
&lt;/h2&gt;

&lt;p&gt;Every developer knows the feeling. You spent two weeks deep in Docker, then three months pass, and suddenly &lt;code&gt;docker-compose&lt;/code&gt; feels like a foreign language. You &lt;em&gt;know&lt;/em&gt; the skill decayed, but by how much? When did it start? How does it compare to your Python knowledge, which you use daily?&lt;/p&gt;

&lt;p&gt;I needed a single number — a &lt;strong&gt;freshness score&lt;/strong&gt; from 0 to 100 — that answers: "How sharp is this skill right now?"&lt;/p&gt;

&lt;p&gt;The constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Must decay automatically over time without user input&lt;/li&gt;
&lt;li&gt;Practice should reset the clock; learning should only slow the decay&lt;/li&gt;
&lt;li&gt;Different skills should decay at different rates&lt;/li&gt;
&lt;li&gt;The math should be simple enough to explain in a sentence&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Core: Exponential Decay
&lt;/h2&gt;

&lt;p&gt;Linear decay (lose 1% per day) doesn't match reality. You don't forget at a constant rate — you forget fast at first, then slower. Exponential decay captures this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;freshness = 100 * (retention_rate ^ days_since_last_practice)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a default retention rate of &lt;code&gt;0.98&lt;/code&gt; (2% daily decay):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days Without Practice&lt;/th&gt;
&lt;th&gt;Freshness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;16%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This feels right. After a week, you're still sharp. After a month, things are fuzzy. After three months, you're starting over.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Practice Resets, But Learning Doesn't
&lt;/h2&gt;

&lt;p&gt;This was the most important design decision in the algorithm.&lt;/p&gt;

&lt;p&gt;When you &lt;strong&gt;practice&lt;/strong&gt; a skill (build a project, solve exercises, write code at work), you're actively retrieving knowledge from memory. Cognitive science calls this the &lt;strong&gt;testing effect&lt;/strong&gt; — retrieval strengthens memory far more than re-exposure.&lt;/p&gt;

&lt;p&gt;When you &lt;strong&gt;learn&lt;/strong&gt; (read an article, watch a tutorial, go through documentation), you're consuming information. It helps, but it doesn't create the same neural pathways as active recall.&lt;/p&gt;

&lt;p&gt;So in the algorithm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Practice resets the decay clock.&lt;/strong&gt; Your freshness restarts from 100%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning adds a small boost.&lt;/strong&gt; Each recent learning event adds 2% freshness, capped at 15%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This means you can read 10 articles about Kubernetes and your freshness might go from 40% to 55%. But one afternoon actually deploying a cluster? Back to 100%.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;Here's the actual Python function, simplified from the production code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_freshness&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;learning_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;skill_created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;decay_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Find the anchor point: last practice, or skill creation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;last_practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;last_practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skill_created&lt;/span&gt;

    &lt;span class="c1"&gt;# Days since last practice
&lt;/span&gt;    &lt;span class="n"&gt;days_elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;last_practice&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;

    &lt;span class="c1"&gt;# Base decay: exponential
&lt;/span&gt;    &lt;span class="n"&gt;retention_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay_rate&lt;/span&gt;
    &lt;span class="n"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retention_rate&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;days_elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Learning boost: recent learning slows the fade
&lt;/span&gt;    &lt;span class="n"&gt;thirty_days_ago&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;recent_learning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;learning_dates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;thirty_days_ago&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;learning_boost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent_learning&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;learning_boost&lt;/span&gt;

    &lt;span class="c1"&gt;# Clamp to 0-100
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. ~30 lines of actual logic. No machine learning. No complex statistical models. Just exponential decay with a learning modifier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making Decay Rates Configurable
&lt;/h2&gt;

&lt;p&gt;Not all skills decay at the same speed. Spoken languages fade fast without immersion. Bicycle-riding-type motor skills barely fade at all. Programming languages sit somewhere in between.&lt;/p&gt;

&lt;p&gt;I added per-skill decay rates with sensible presets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DECAY_PRESETS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 1% per day — motor skills, deep expertise
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 2% per day — most technical skills
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="mf"&gt;0.03&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# 3% per day — languages, frameworks with churn
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;very_fast&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 5% per day — memorization-heavy, trivia
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is dramatic over 30 days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Decay Rate&lt;/th&gt;
&lt;th&gt;Freshness at Day 30&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1% (slow)&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2% (default)&lt;/td&gt;
&lt;td&gt;55%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3% (fast)&lt;/td&gt;
&lt;td&gt;40%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5% (very fast)&lt;/td&gt;
&lt;td&gt;21%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Users can set custom rates per skill. Someone maintaining five programming languages might set Go (used daily) to slow decay and Rust (learning on weekends) to fast decay.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Anchor Point Problem
&lt;/h2&gt;

&lt;p&gt;One subtle decision: what happens when a skill has &lt;strong&gt;never been practiced&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;Option A: Start at 0% freshness. But that feels wrong — you just created the skill, you presumably know &lt;em&gt;something&lt;/em&gt; about it.&lt;/p&gt;

&lt;p&gt;Option B: Start at 100% and decay from the creation date. This is what I chose. When you add "Python" to your tracker, you start fresh. The clock begins ticking immediately. If you don't log a practice event, your freshness will naturally decline.&lt;/p&gt;

&lt;p&gt;This creates a useful pressure: adding a skill is a commitment. You're saying "I want to track this," and the app immediately starts showing you the decay. It's honest from day one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# The anchor is either last practice or skill creation
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;last_practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;last_practice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skill_created&lt;/span&gt;  &lt;span class="c1"&gt;# Decay starts at creation
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why I Capped the Learning Boost at 15%
&lt;/h2&gt;

&lt;p&gt;Early versions had no cap on the learning boost. The problem: a user could watch 20 YouTube tutorials in a week and see their freshness jump from 30% to 70% without ever touching a keyboard.&lt;/p&gt;

&lt;p&gt;That defeats the entire purpose. The app is supposed to show you that &lt;strong&gt;consumption isn't retention&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The 15% cap means learning can slow the bleeding, but it can't substitute for practice. The numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Base freshness (60 days, no practice): 30%
+ 1 learning event in last 30 days:   32%
+ 3 learning events:                  36%
+ 5 learning events:                  40%
+ 8+ learning events:                 45% (capped)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read your way from 30% to 45%, but to get back above 70%, you need to actually practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  Freshness History: Tracking the Curve Over Time
&lt;/h2&gt;

&lt;p&gt;A single freshness number is useful, but the trend is more interesting. I built a history endpoint that reconstructs what the freshness &lt;em&gt;would have been&lt;/em&gt; on each day over the past N days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_freshness_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;practice_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;learning_dates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;skill_created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;decay_rate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.02&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;day_offset&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;target_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;day_offset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Only consider events before or on this date
&lt;/span&gt;        &lt;span class="n"&gt;practices_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;practice_dates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;target_date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;learnings_before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;learning_dates&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;target_date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="c1"&gt;# Calculate what freshness was on that day
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;practices_before&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;practices_before&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;anchor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;skill_created&lt;/span&gt;

        &lt;span class="n"&gt;days_elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;
        &lt;span class="n"&gt;retention&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;decay_rate&lt;/span&gt;
        &lt;span class="n"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;100.0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retention&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;days_elapsed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="c1"&gt;# Learning boost (events within 30 days of target_date)
&lt;/span&gt;        &lt;span class="n"&gt;window_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;target_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;learnings_before&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;window_start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;freshness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;100.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target_date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;freshness&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;freshness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This produces a line chart where you can see the exponential decay curves, the sharp jumps when you practice, and the small bumps when you learn. It tells a story that a single number can't.&lt;/p&gt;




&lt;h2&gt;
  
  
  Edge Cases I Hit in Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Future-dated events
&lt;/h3&gt;

&lt;p&gt;Users can log events in the past (backdating), but what about the future? A practice event dated tomorrow would make &lt;code&gt;days_elapsed&lt;/code&gt; negative, pushing freshness above 100%.&lt;/p&gt;

&lt;p&gt;Fix: &lt;code&gt;max(0, days_elapsed)&lt;/code&gt; — treat future events as "today."&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Same-day multiple events
&lt;/h3&gt;

&lt;p&gt;If you practice and learn on the same day, the order shouldn't matter. Since we only look at dates (not timestamps), this works naturally.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Zero learning, zero practice
&lt;/h3&gt;

&lt;p&gt;A skill with no events at all just decays from 100% starting at creation. No division-by-zero, no special cases needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Very old skills
&lt;/h3&gt;

&lt;p&gt;A skill created 365 days ago with no practice: &lt;code&gt;100 * 0.98^365 = 0.06%&lt;/code&gt;. Effectively zero. The clamp handles this — it shows 0%, not a tiny decimal.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Considered But Didn't Build
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spaced repetition scheduling.&lt;/strong&gt; Systems like Anki calculate optimal review intervals. I explicitly avoided this because SkillFade is a mirror, not a coach. It shows decay; it doesn't prescribe when to practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted event types.&lt;/strong&gt; Should a 3-hour project count more than a 15-minute exercise? Probably. But adding weights adds complexity and subjective tuning. I kept it simple: one practice event = one clock reset, regardless of duration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collaborative decay.&lt;/strong&gt; In teams, you could model shared skill decay — if nobody on the team has touched Terraform in 60 days, flag it. Interesting, but it would require social features, which violates the design principles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ML-based personal decay curves.&lt;/strong&gt; With enough data, you could learn each user's actual forgetting rate per skill. But this violates the "no AI/ML" constraint, and honestly, the exponential model works well enough.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Takeaway for Your Own Projects
&lt;/h2&gt;

&lt;p&gt;If you need to model something that degrades over time — cache staleness, content freshness, user engagement risk — exponential decay is almost always the right starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retention_rate&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;time_elapsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three parameters. One line of math. Adjust the retention rate to match your domain.&lt;/p&gt;

&lt;p&gt;The hard part isn't the algorithm. It's deciding what resets the clock, what slows the decay, and what you refuse to let cheat the system. Those are product decisions disguised as math.&lt;/p&gt;




&lt;p&gt;You can see this algorithm in action at &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;skillfade.website&lt;/a&gt;. Create an account, add a skill, and watch the decay begin.&lt;/p&gt;

&lt;p&gt;Because the first step to retaining knowledge is measuring how fast it disappears.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Python, FastAPI, React, and PostgreSQL. The algorithm runs on every page load — no caching, no background jobs, just real-time math.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>algorithms</category>
      <category>webdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>You're Consuming Too Much and Creating Too Little</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Sat, 31 Jan 2026 18:35:30 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/youre-consuming-too-much-and-creating-too-little-1dfm</link>
      <guid>https://dev.to/ruhidibadli/youre-consuming-too-much-and-creating-too-little-1dfm</guid>
      <description>&lt;p&gt;Last year I tracked my "learning" hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;120+ hours of YouTube tutorials&lt;/li&gt;
&lt;li&gt;40+ hours of online courses&lt;/li&gt;
&lt;li&gt;Countless articles, blog posts, documentation pages&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sounds productive, right?&lt;/p&gt;

&lt;p&gt;Then I tracked my "building" hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 side projects started, 0 finished&lt;/li&gt;
&lt;li&gt;Maybe 30 hours of actual coding outside work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ratio was embarrassing: &lt;strong&gt;4:1 consumption to creation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I was a learning addict who never actually learned anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Input/Output Trap
&lt;/h2&gt;

&lt;p&gt;Developers fall into this trap constantly. We:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watch tutorial after tutorial&lt;/li&gt;
&lt;li&gt;Save hundreds of "read later" articles&lt;/li&gt;
&lt;li&gt;Bookmark GitHub repos we'll "explore someday"&lt;/li&gt;
&lt;li&gt;Buy courses on sale "for when I have time"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It feels like progress. Our brains reward us with dopamine for "discovering" new information. But discovery isn't learning. Consumption isn't skill.&lt;/p&gt;

&lt;p&gt;The uncomfortable truth: &lt;strong&gt;if you're not building, you're not learning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Prefer Input
&lt;/h2&gt;

&lt;p&gt;Input is easy. Output is hard.&lt;/p&gt;

&lt;p&gt;Watching a video is passive. Your brain can half-engage while you feel productive. Building something requires focus, problem-solving, failure, frustration.&lt;/p&gt;

&lt;p&gt;Input has no failure state. You can't "fail" at watching a tutorial. But you can absolutely fail at implementing what it taught. That failure feels bad, so we avoid it.&lt;/p&gt;

&lt;p&gt;Input is infinite. There's always another video, another article, another course. Output has an end point—you have to ship something eventually.&lt;/p&gt;

&lt;p&gt;So we keep consuming. And keep not-learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Ratio That Matters
&lt;/h2&gt;

&lt;p&gt;I started tracking my input/output ratio:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input&lt;/strong&gt;: Tutorials, courses, articles, videos, documentation&lt;br&gt;
&lt;strong&gt;Output&lt;/strong&gt;: Projects, code, writing, teaching, contributing&lt;/p&gt;

&lt;p&gt;A healthy ratio for actual skill building? At least &lt;strong&gt;1:1&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For every hour you consume, spend an hour creating. Watch a React video? Build a component. Read about Docker? Deploy something. Learn a new pattern? Refactor old code to use it.&lt;/p&gt;

&lt;p&gt;Better yet: &lt;strong&gt;lead with output&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Don't watch a tutorial on authentication. Try to build it first. Get stuck. Then watch the tutorial. Now you have context. Now you'll remember.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tutorial Purgatory
&lt;/h2&gt;

&lt;p&gt;You know the pattern:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Want to learn X&lt;/li&gt;
&lt;li&gt;Search "X tutorial for beginners"&lt;/li&gt;
&lt;li&gt;Watch 4-hour video series&lt;/li&gt;
&lt;li&gt;Feel accomplished&lt;/li&gt;
&lt;li&gt;Never use X&lt;/li&gt;
&lt;li&gt;Forget everything&lt;/li&gt;
&lt;li&gt;Months later, repeat from step 1&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is tutorial purgatory. Infinite consumption, zero retention.&lt;/p&gt;

&lt;p&gt;The way out is simple but painful: &lt;strong&gt;build something before you feel ready&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll fail. You'll get stuck. You'll write bad code. That struggle is the learning. The tutorial was just entertainment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Changed
&lt;/h2&gt;

&lt;p&gt;I set rules for myself:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 1:1 rule&lt;/strong&gt; - Match consumption time with creation time. Watched a 2-hour course? Spend 2 hours building with it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 24-hour rule&lt;/strong&gt; - If I learn something, I must use it within 24 hours or it doesn't count as learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The teach rule&lt;/strong&gt; - If I can't explain it simply, I didn't learn it. Writing a blog post or explaining to a colleague forces real understanding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The project-first rule&lt;/strong&gt; - Start the project before the tutorial. Use tutorials as reference, not curriculum.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking the Balance
&lt;/h2&gt;

&lt;p&gt;This input/output problem is why I built balance tracking into &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;SkillFade&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every skill shows two numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Learning events (input)&lt;/li&gt;
&lt;li&gt;Practice events (output)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ratio is visible. When it skews too far toward input, you see it. No judgment—just data showing you've been consuming more than creating.&lt;/p&gt;

&lt;p&gt;It's a mirror. And sometimes the reflection is uncomfortable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;Look at your last month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many tutorials did you watch?&lt;/li&gt;
&lt;li&gt;How many projects did you ship?&lt;/li&gt;
&lt;li&gt;How many articles did you read?&lt;/li&gt;
&lt;li&gt;How many articles did you write?&lt;/li&gt;
&lt;li&gt;How many courses did you complete?&lt;/li&gt;
&lt;li&gt;How many things did you build with that knowledge?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the first number is always bigger than the second, you're not learning. You're just watching.&lt;/p&gt;




&lt;p&gt;What's your input/output ratio? And what are you going to build this week?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>learning</category>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Why You've Already Forgotten Half of What You Learned Last Month</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Fri, 30 Jan 2026 12:37:21 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/why-youve-already-forgotten-half-of-what-you-learned-last-month-icm</link>
      <guid>https://dev.to/ruhidibadli/why-youve-already-forgotten-half-of-what-you-learned-last-month-icm</guid>
      <description>&lt;p&gt;In 1885, German psychologist Hermann Ebbinghaus memorized nonsense syllables and tracked how quickly he forgot them.&lt;/p&gt;

&lt;p&gt;His discovery is now called the &lt;strong&gt;Ebbinghaus Forgetting Curve&lt;/strong&gt;, and it's brutal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;After 1 day: ~70% forgotten&lt;/li&gt;
&lt;li&gt;After 1 week: ~90% forgotten&lt;/li&gt;
&lt;li&gt;After 1 month: almost everything gone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was 140 years ago. The finding has been replicated countless times since. And yet we still act surprised when we can't remember that tutorial we watched last month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Uncomfortable Math
&lt;/h2&gt;

&lt;p&gt;Let's say you completed a Docker course last month. You were excited. You understood containers, images, volumes, networking. You felt competent.&lt;/p&gt;

&lt;p&gt;Today? Without practice, you've retained maybe 10-20% of that knowledge. The rest is gone. Not "rusty"—gone. Your brain literally pruned those neural connections because you didn't use them.&lt;/p&gt;

&lt;p&gt;This isn't a personal failing. It's biology.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Practice Beats Learning
&lt;/h2&gt;

&lt;p&gt;Ebbinghaus also discovered something else: &lt;strong&gt;active recall&lt;/strong&gt; dramatically slows forgetting.&lt;/p&gt;

&lt;p&gt;When you passively watch a video, information enters short-term memory and mostly disappears. When you actively use that information—write code, solve problems, build projects—it moves to long-term memory.&lt;/p&gt;

&lt;p&gt;The difference is massive:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Activity&lt;/th&gt;
&lt;th&gt;Retention after 1 month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Watched a video&lt;/td&gt;
&lt;td&gt;~10%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Took notes&lt;/td&gt;
&lt;td&gt;~20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Did exercises&lt;/td&gt;
&lt;td&gt;~50%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Built a project&lt;/td&gt;
&lt;td&gt;~70%+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is why tutorials feel productive but rarely stick. You're not learning—you're spectating.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Learning Trap
&lt;/h2&gt;

&lt;p&gt;Modern learning platforms accidentally exploit this gap.&lt;/p&gt;

&lt;p&gt;You watch 10 hours of Python tutorials. The platform shows you a completion bar at 100%. You get a certificate. You feel accomplished.&lt;/p&gt;

&lt;p&gt;But completion ≠ competence. You consumed content. You didn't build skills.&lt;/p&gt;

&lt;p&gt;The platform doesn't care. Their metric is "courses completed," not "skills retained." They're optimized for your feeling of progress, not actual progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Actually Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Spaced repetition&lt;/strong&gt; - Review material at increasing intervals (1 day, 3 days, 1 week, 2 weeks). Each review resets the forgetting curve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Active recall&lt;/strong&gt; - Don't re-read notes. Close them and try to remember. The struggle of retrieval strengthens memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project-based learning&lt;/strong&gt; - Build something real. Applied knowledge sticks. Tutorial knowledge evaporates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Interleaving&lt;/strong&gt; - Mix different skills in practice sessions. Harder in the moment, better for retention.&lt;/p&gt;

&lt;p&gt;None of this is new. Cognitive scientists have known it for decades. We just keep ignoring it because passive learning feels easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking Decay
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;SkillFade&lt;/a&gt; to make this forgetting visible.&lt;/p&gt;

&lt;p&gt;Every skill you track has a freshness percentage that decays over time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practice something → freshness resets to 100%&lt;/li&gt;
&lt;li&gt;Learn something (video, article) → small boost, but decay continues&lt;/li&gt;
&lt;li&gt;Do nothing → freshness drops ~2% per day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After a month of no practice, a skill shows ~55% freshness. After two months, ~30%. The red warning color makes the decay impossible to ignore.&lt;/p&gt;

&lt;p&gt;It's not gamification. There's no streak to maintain, no badge to earn. Just honest data: &lt;strong&gt;here's what you're forgetting&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Point
&lt;/h2&gt;

&lt;p&gt;Your skills are decaying right now. The Python you learned six months ago. The SQL from that online course. The framework from your last job.&lt;/p&gt;

&lt;p&gt;Most of it is already gone. Not because you're bad at learning—because that's how human memory works.&lt;/p&gt;

&lt;p&gt;The question isn't whether you're forgetting. You are. The question is: &lt;strong&gt;which skills are worth maintaining?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pick the ones that matter. Practice them regularly. Let the rest go.&lt;/p&gt;

&lt;p&gt;At least now you're making that choice consciously.&lt;/p&gt;




&lt;p&gt;What skills have you let decay? And which ones are you actively maintaining?&lt;/p&gt;

</description>
      <category>learning</category>
      <category>productivity</category>
      <category>career</category>
      <category>psychology</category>
    </item>
    <item>
      <title>I Built a Skill Tracker Because I Was Tired of Lying to Myself</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Thu, 29 Jan 2026 15:40:35 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/i-built-a-skill-tracker-because-i-was-tired-of-lying-to-myself-8m0</link>
      <guid>https://dev.to/ruhidibadli/i-built-a-skill-tracker-because-i-was-tired-of-lying-to-myself-8m0</guid>
      <description>&lt;p&gt;Six months ago I listed "Kubernetes" on my resume.&lt;/p&gt;

&lt;p&gt;The truth? I watched a tutorial series, deployed one pod, and never touched it again. Could I debug a failing cluster in an interview? Absolutely not.&lt;/p&gt;

&lt;p&gt;But it felt good to write "Kubernetes" on that skills list. It felt like progress. I was "learning."&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;We all do this. We consume courses, watch tutorials, read documentation—and call it learning. Our skill lists grow. Our actual abilities don't.&lt;/p&gt;

&lt;p&gt;I call it &lt;strong&gt;skill inflation&lt;/strong&gt;: the gap between what we claim to know and what we can actually do.&lt;/p&gt;

&lt;p&gt;The existing tools didn't help. Duolingo gave me streaks that made me feel accomplished while I forgot basic vocabulary. LinkedIn Learning gave me certificates for videos I half-watched. Every app was designed to make me feel good, not to show me the truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Wanted
&lt;/h2&gt;

&lt;p&gt;A tool that would answer one honest question: &lt;strong&gt;What am I actually forgetting?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not what courses I completed. Not how many days in a row I logged in. Not how I compare to other users.&lt;/p&gt;

&lt;p&gt;Just: here are your skills, here's how fresh they are, here's what's decaying.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I Built It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SkillFade&lt;/strong&gt; tracks your skills and shows their freshness—a percentage that decays over time without practice.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finished a React course? Freshness starts at 50% (learning boost only).&lt;/li&gt;
&lt;li&gt;Built a React project? Now you're at 100%.&lt;/li&gt;
&lt;li&gt;Haven't touched React in 60 days? You're at 30% and dropping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's it. No badges. No streaks. No leaderboards. No AI telling you what to learn next.&lt;/p&gt;

&lt;p&gt;Just data about what you're forgetting.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add skills&lt;/strong&gt; you want to track (Python, Docker, Spanish, whatever)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log events&lt;/strong&gt; when you learn (watched a video) or practice (built something)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check freshness&lt;/strong&gt; to see what's decaying&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The dashboard shows everything at a glance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🟢 Green = Fresh (&amp;gt;70%)&lt;/li&gt;
&lt;li&gt;🟡 Yellow = Aging (40-70%)&lt;/li&gt;
&lt;li&gt;🔴 Red = Decayed (&amp;lt;40%)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optional email alerts (max 1/week) tell you when skills drop below your threshold.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes It Different
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Practice &amp;gt; Learning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most apps treat all activity equally. SkillFade doesn't. Practice resets your freshness. Learning only slows decay.&lt;/p&gt;

&lt;p&gt;This reflects reality: watching a Kubernetes video isn't the same as deploying a cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No Gamification&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No streaks to maintain. No badges to collect. No points to accumulate. Miss a week? Your freshness drops a bit. That's information, not punishment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy First&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No tracking. No analytics. No data sharing. Full export and delete anytime. Your skill decay is nobody's business.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calm Design&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The app doesn't fight for your attention. No push notifications. No "come back!" emails. Log your stuff, check your freshness, leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who It's For
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developers&lt;/strong&gt; maintaining multiple languages and frameworks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Career switchers&lt;/strong&gt; tracking new field knowledge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-learners&lt;/strong&gt; who want honest feedback&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anyone&lt;/strong&gt; tired of apps that manipulate instead of inform&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you've ever felt that gap between what you "know" and what you can actually do—give it a try.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;skillfade.website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's free. No credit card. Takes 30 seconds to add your first skill.&lt;/p&gt;




&lt;p&gt;I'd love feedback from this community. What would make this more useful for you? What's missing?&lt;/p&gt;

&lt;p&gt;And honestly—what skills are you pretending you still have?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>career</category>
      <category>learning</category>
    </item>
    <item>
      <title>The Features I Refused to Build in SkillFade</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Tue, 27 Jan 2026 18:03:46 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/the-features-i-refused-to-build-in-skillfade-3c2f</link>
      <guid>https://dev.to/ruhidibadli/the-features-i-refused-to-build-in-skillfade-3c2f</guid>
      <description>&lt;p&gt;Every product is defined as much by what it &lt;em&gt;doesn't&lt;/em&gt; do as by what it does.&lt;/p&gt;

&lt;p&gt;When I built &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;SkillFade&lt;/a&gt;, a skill tracking app, I kept a list. Not a backlog of features to add—a list of features I actively refused to build.&lt;/p&gt;

&lt;p&gt;Here's that list, and why each "no" made the product better.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. No Streaks
&lt;/h2&gt;

&lt;p&gt;Streaks are the most requested feature I'll never add.&lt;/p&gt;

&lt;p&gt;"Just add a streak counter! It motivates people!"&lt;/p&gt;

&lt;p&gt;Does it? Or does it create anxiety? Miss one day and your 47-day streak resets. Now you feel like a failure. The app that was supposed to help you learn just made you feel worse.&lt;/p&gt;

&lt;p&gt;Streaks optimize for daily opens, not actual learning. They punish life happening—vacations, sick days, busy weeks. They turn a tool into an obligation.&lt;/p&gt;

&lt;p&gt;SkillFade shows freshness decay instead. Miss a week? Your skills dropped a bit. That's just information, not judgment. No streak to "break." No guilt. Just data.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. No Badges or Achievements
&lt;/h2&gt;

&lt;p&gt;"You earned the 'Python Pioneer' badge!"&lt;/p&gt;

&lt;p&gt;Who cares?&lt;/p&gt;

&lt;p&gt;Badges are fake accomplishments that distract from real ones. You didn't become a better developer because an app gave you a virtual medal. You became better by building things.&lt;/p&gt;

&lt;p&gt;Every badge system I've seen eventually becomes the goal itself. People optimize for unlocking achievements instead of actual skill development. The map becomes the territory.&lt;/p&gt;

&lt;p&gt;SkillFade has no badges. The only achievement is the skill you built. The only reward is the work itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. No Leaderboards
&lt;/h2&gt;

&lt;p&gt;"See how you rank against other learners!"&lt;/p&gt;

&lt;p&gt;Why would I want that?&lt;/p&gt;

&lt;p&gt;Learning isn't a competition. Someone else's Python freshness has nothing to do with mine. Leaderboards create winners and losers in a game that shouldn't exist.&lt;/p&gt;

&lt;p&gt;Worse, they encourage gaming the system. Log fake practice sessions to climb the ranks. Optimize for points instead of learning. The metric becomes the target, and once a metric becomes a target, it ceases to be a good metric.&lt;/p&gt;

&lt;p&gt;SkillFade is single-player only. Your data is yours. No comparisons. No rankings. No "you're in the top 10%!" notifications.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. No Push Notifications
&lt;/h2&gt;

&lt;p&gt;My phone buzzes enough.&lt;/p&gt;

&lt;p&gt;Push notifications are an attention tax. Every app fights for your focus. "Come back!" "You haven't logged in today!" "Your streak is at risk!"&lt;/p&gt;

&lt;p&gt;This is hostile design. The app prioritizes its own engagement metrics over your mental peace.&lt;/p&gt;

&lt;p&gt;SkillFade sends email—maximum once per week, plain text, one-click unsubscribe. No push notifications. No red badges. No "you have 3 unread alerts!"&lt;/p&gt;

&lt;p&gt;If you forget the app exists for a month, that's fine. Your data will be there when you return.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. No AI Recommendations
&lt;/h2&gt;

&lt;p&gt;"AI suggests you should learn Kubernetes next!"&lt;/p&gt;

&lt;p&gt;Why? Because it's trending? Because other users learned it? Because an algorithm thinks it knows my career better than I do?&lt;/p&gt;

&lt;p&gt;AI recommendations optimize for engagement, not usefulness. They're based on aggregate patterns that may not apply to you. They create FOMO about skills you don't need.&lt;/p&gt;

&lt;p&gt;SkillFade doesn't tell you what to learn. It shows you what you &lt;em&gt;already&lt;/em&gt; decided to learn and how fresh those skills are. You're the expert on your own goals. The app just holds up a mirror.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. No Social Features
&lt;/h2&gt;

&lt;p&gt;"Share your progress with friends!"&lt;br&gt;
"Follow other learners!"&lt;br&gt;
"Join study groups!"&lt;/p&gt;

&lt;p&gt;No.&lt;/p&gt;

&lt;p&gt;Social features turn personal tools into performance spaces. Suddenly you're not tracking skills for yourself—you're curating an image for others. The private becomes public. The honest becomes performative.&lt;/p&gt;

&lt;p&gt;Some people thrive on social accountability. Great—use a different app. SkillFade is deliberately antisocial. Your skill decay is nobody's business but yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. No "Gamification"
&lt;/h2&gt;

&lt;p&gt;Let's be honest about what gamification usually means: psychological manipulation dressed up as fun.&lt;/p&gt;

&lt;p&gt;Variable rewards. Progress bars that never quite fill. Artificial scarcity. Dark patterns that exploit human psychology to drive engagement.&lt;/p&gt;

&lt;p&gt;Games are fun because they're games. Turning learning into a game doesn't make learning fun—it makes it a worse game. You're not playing because it's enjoyable; you're playing because the app engineered you to.&lt;/p&gt;

&lt;p&gt;SkillFade has no game mechanics. It's a spreadsheet with a nice UI. Boring? Maybe. Honest? Yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. No Onboarding Tutorial
&lt;/h2&gt;

&lt;p&gt;"Let me show you around!"&lt;br&gt;
&lt;em&gt;Click&lt;/em&gt;&lt;br&gt;
"This is your dashboard!"&lt;br&gt;
&lt;em&gt;Click&lt;/em&gt;&lt;br&gt;
"Here's how to add a skill!"&lt;br&gt;
&lt;em&gt;Click click click&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If your app needs a tutorial, your app is too complicated.&lt;/p&gt;

&lt;p&gt;SkillFade drops you on a dashboard with one button: "Add Skill." Click it. Fill in the name. Done. You now understand the entire app.&lt;/p&gt;

&lt;p&gt;The interface should teach itself. If users need a guided tour, that's a design failure, not a feature opportunity.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. No Premium Tier (For Now)
&lt;/h2&gt;

&lt;p&gt;"Free users get 5 skills, premium gets unlimited!"&lt;/p&gt;

&lt;p&gt;Artificial limitations designed to frustrate you into paying. The feature already exists—the code just checks if you've paid before letting you use it.&lt;/p&gt;

&lt;p&gt;SkillFade is currently free with all features. When I add paid tiers, they'll be for genuinely new capabilities—not for removing arbitrary restrictions I invented.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. No "Insights" Dashboard
&lt;/h2&gt;

&lt;p&gt;"You learned 47% more this month!"&lt;br&gt;
"Your most productive day is Tuesday!"&lt;br&gt;
"You're a visual learner!"&lt;/p&gt;

&lt;p&gt;Meaningless metrics presented as wisdom.&lt;/p&gt;

&lt;p&gt;These insights sound smart but rarely change behavior. They exist to make the app feel sophisticated, to give you something to screenshot and share.&lt;/p&gt;

&lt;p&gt;SkillFade shows one insight: your skills are decaying. That's it. One number per skill. No charts about your "learning style." No personality quizzes. No "insights" that are really just flattery.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern
&lt;/h2&gt;

&lt;p&gt;Look at the list again. Every refused feature has something in common:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They optimize for the app, not the user.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Streaks keep you opening the app. Badges keep you engaged. Leaderboards drive competition. Push notifications interrupt your day. Social features create network effects. Gamification increases time-in-app.&lt;/p&gt;

&lt;p&gt;All of these are great for metrics. MAU, DAU, retention, engagement—every startup's favorite dashboard.&lt;/p&gt;

&lt;p&gt;But I don't have investors to impress. I don't need hockey-stick growth charts. I built SkillFade for myself first, and I wanted a tool that respects my time and attention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alternative
&lt;/h2&gt;

&lt;p&gt;What SkillFade does instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shows you the truth&lt;/strong&gt; - Your skills decay. Here's how much.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gets out of your way&lt;/strong&gt; - Log events in 10 seconds, then leave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respects your attention&lt;/strong&gt; - No notifications unless you want them, and even then, rarely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trusts you&lt;/strong&gt; - You know what you need to learn. The app doesn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's less "product" and more "tool." Boring, perhaps. But boring tools get used for years. Exciting apps get deleted after the dopamine fades.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building What You'd Actually Use
&lt;/h2&gt;

&lt;p&gt;My filter for every feature request: "Would I want this in a tool I use daily?"&lt;/p&gt;

&lt;p&gt;Streaks? No, they stress me out.&lt;br&gt;
Badges? No, they feel patronizing.&lt;br&gt;
Push notifications? God no.&lt;br&gt;
AI recommendations? I'll decide what to learn, thanks.&lt;/p&gt;

&lt;p&gt;This isn't for everyone. If you love gamification, SkillFade will feel empty. That's fine—there are dozens of apps that do that well.&lt;/p&gt;

&lt;p&gt;But if you've ever felt exhausted by apps that demand your attention, manipulate your psychology, and treat you like a metric to optimize—maybe you want something quieter.&lt;/p&gt;

&lt;p&gt;SkillFade is that quiet tool. No dopamine hits. No manipulation. Just a simple question: &lt;strong&gt;what are you forgetting?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;What features have you refused to build? I'd love to hear what others have cut from their products.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>ux</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>Technical Deep Dive: Building SkillFade with FastAPI and React</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Mon, 26 Jan 2026 14:35:41 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/technical-deep-dive-building-skillfade-with-fastapi-and-react-3833</link>
      <guid>https://dev.to/ruhidibadli/technical-deep-dive-building-skillfade-with-fastapi-and-react-3833</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fct3nvofbsv9jfdjonq7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fct3nvofbsv9jfdjonq7b.png" alt=" " width="800" height="384"&gt;&lt;/a&gt;Last week I shared &lt;a href="https://dev.to/ruhidibadli/skillfade-a-skill-tracking-app-that-tells-you-the-truth-not-what-you-want-to-hear-3m10"&gt;SkillFade&lt;/a&gt;, a skill tracking app that prioritizes honesty over gamification. Today I want to walk through the architectural decisions and technical choices that shaped it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Backend:  FastAPI + SQLAlchemy 2.0 + PostgreSQL
Frontend: React 18 + TypeScript + TailwindCSS
Auth:     JWT + bcrypt
Deploy:   Docker + Nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Boring? Yes. That's the point. Every piece is battle-tested, well-documented, and easy to hire for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why FastAPI?
&lt;/h2&gt;

&lt;p&gt;I'm primarily a backend engineer with Python experience. When choosing a framework, I wanted something that would let me move fast without fighting the tooling.&lt;/p&gt;

&lt;p&gt;FastAPI checked every box:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Familiar language&lt;/strong&gt; - Python is my comfort zone, so I could focus on product logic instead of learning new syntax&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type hints as documentation&lt;/strong&gt; - Pydantic schemas generate OpenAPI docs automatically, no extra work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Async when needed&lt;/strong&gt; - But sync works perfectly fine for database-bound operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Great ecosystem&lt;/strong&gt; - Need anything? There's probably a Python library for it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Developer experience&lt;/strong&gt; - Hot reload, clear error messages, intuitive routing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Could I have used Django? Sure. Flask? Also fine. But FastAPI hit the sweet spot between simplicity and features. For a solo developer shipping a product, that matters more than benchmarks.&lt;/p&gt;

&lt;p&gt;The honest truth: I picked the tool I knew best so I could ship faster. No regrets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: Monolith by Choice
&lt;/h2&gt;

&lt;p&gt;No microservices. No message queues. No Kubernetes. A single FastAPI process handles everything.&lt;/p&gt;

&lt;p&gt;This wasn't a compromise—it was a deliberate decision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why monolith?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simpler debugging&lt;/strong&gt; - One log stream, one deployment, one place to look when things break&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster development&lt;/strong&gt; - No service orchestration, no network calls between components, no distributed tracing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cheaper hosting&lt;/strong&gt; - Everything runs on a modest VPS, no complex infrastructure costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easier mental model&lt;/strong&gt; - The entire application fits in my head&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What about scale?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;SkillFade is a personal productivity tool. Even with thousands of users, a single well-optimized server handles the load easily. PostgreSQL can manage millions of rows without breaking a sweat.&lt;/p&gt;

&lt;p&gt;The microservices question I ask myself: "Do I have a team of 50 engineers who need to deploy independently?" No. So monolith it is.&lt;/p&gt;

&lt;p&gt;I'll split it when I have the problem. Not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Algorithm: Freshness Decay
&lt;/h2&gt;

&lt;p&gt;The heart of SkillFade is the freshness calculation. Here's the thinking behind it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The concept:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every skill starts at 100% freshness after you practice it&lt;/li&gt;
&lt;li&gt;Each day without practice, freshness decays exponentially&lt;/li&gt;
&lt;li&gt;Recent learning activity adds a small boost (max 15%)&lt;/li&gt;
&lt;li&gt;Result is clamped between 0-100%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The math (simplified):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Default decay rate is 2% per day, compounding. This means:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Days since practice&lt;/th&gt;
&lt;th&gt;Freshness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;~87%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;~75%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;~54%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;~30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;~16%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why exponential decay?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Linear decay (lose 1% per day) doesn't match how memory works. In reality, forgetting is steep at first, then levels off. The exponential curve better models the Ebbinghaus forgetting curve that psychologists have studied for over a century.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does learning only "boost" and not reset?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the key philosophical choice. Watching a tutorial about React doesn't mean you can build a React app. Reading about Kubernetes doesn't mean you can debug a cluster.&lt;/p&gt;

&lt;p&gt;Learning slows decay. Only practice resets the clock.&lt;/p&gt;

&lt;p&gt;This forces users to confront an uncomfortable truth: passive consumption isn't the same as skill building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom decay rates&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not all skills fade equally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Motor skills&lt;/strong&gt; (cycling, typing) - slow decay, muscle memory persists&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conceptual knowledge&lt;/strong&gt; (design patterns, architecture) - medium decay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syntax and tooling&lt;/strong&gt; (specific APIs, CLI flags) - fast decay, details fade quickly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users can adjust decay rate per skill to match reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Schema Philosophy
&lt;/h3&gt;

&lt;p&gt;Six core tables. No more than necessary, no fewer than useful.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;users
categories
skills
learning_events
practice_events
skill_dependencies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every table has clear ownership. Users own categories. Categories organize skills. Skills have events. Simple hierarchy, predictable queries.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separate Tables for Learning vs Practice Events
&lt;/h3&gt;

&lt;p&gt;I debated this one. A single &lt;code&gt;events&lt;/code&gt; table with a &lt;code&gt;type&lt;/code&gt; column would be simpler, right?&lt;/p&gt;

&lt;p&gt;I went with separate tables because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Query patterns differ&lt;/strong&gt; - Dashboard shows practice frequency, detail pages show both. Separate tables = simpler queries for common operations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different subtypes&lt;/strong&gt; - Learning events have types like "video, article, course, book". Practice events have "project, exercise, teaching, code-review". One enum would be confusing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future flexibility&lt;/strong&gt; - Practice events might get "difficulty" or "output_url" fields that don't make sense for learning&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema as documentation&lt;/strong&gt; - Two tables makes the learning/practice distinction explicit in the data model itself&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The tradeoff is some duplication in the codebase. Worth it for clarity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hard Deletes Over Soft Deletes
&lt;/h3&gt;

&lt;p&gt;When a user clicks "Delete my account," everything goes. CASCADE deletes across all tables. No &lt;code&gt;deleted_at&lt;/code&gt; columns, no recovery period, no "we keep your data for 30 days."&lt;/p&gt;

&lt;p&gt;This aligns with the privacy philosophy. If someone wants out, they're out. Fully.&lt;/p&gt;

&lt;p&gt;It also simplifies queries—no &lt;code&gt;WHERE deleted_at IS NULL&lt;/code&gt; everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Categories as First-Class Entities
&lt;/h3&gt;

&lt;p&gt;Early version had category as just a string field on skills. Worked fine until I wanted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Category-level analytics ("How fresh are my Frontend skills overall?")&lt;/li&gt;
&lt;li&gt;Rename a category everywhere at once&lt;/li&gt;
&lt;li&gt;Ensure no duplicate category names per user&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Refactored to proper &lt;code&gt;categories&lt;/code&gt; table with foreign keys. More work upfront, cleaner long-term.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frontend Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why React + Vite (Not Next.js)
&lt;/h3&gt;

&lt;p&gt;Next.js is great. I considered it. Went with plain React SPA instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reasons:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No SEO needed&lt;/strong&gt; - It's a dashboard behind login. Google doesn't need to crawl it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler mental model&lt;/strong&gt; - No SSR vs CSR decisions, no hydration bugs, no server components confusion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear separation&lt;/strong&gt; - API is API (FastAPI), frontend is frontend (React). Clean boundary&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment flexibility&lt;/strong&gt; - Static files served by Nginx, API proxied separately&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a marketing site or blog, I'd pick Next.js. For an authenticated dashboard app, SPA is simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management: Context Over Redux
&lt;/h3&gt;

&lt;p&gt;SkillFade uses React Context for global state. No Redux, no Zustand, no MobX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in global state?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current user (authentication)&lt;/li&gt;
&lt;li&gt;Theme preference (dark/light)&lt;/li&gt;
&lt;li&gt;That's it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Everything else?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fetched fresh per page. Server is the source of truth. When you navigate to the dashboard, it fetches current data. No stale cache problems, no sync issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not Redux?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Redux solves problems I don't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complex state interactions across many components? Nope, state is simple&lt;/li&gt;
&lt;li&gt;Time-travel debugging? Never needed it&lt;/li&gt;
&lt;li&gt;Middleware for side effects? fetch() works fine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this app, Redux would add complexity without adding value. Context + useState + useEffect covers everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  Component Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
  components/     # Reusable UI pieces (Button, Card, Modal)
  pages/          # Route-level components (Dashboard, SkillDetail)
  context/        # Global state (AuthContext, ThemeContext)
  services/       # API calls (skills.ts, events.ts)
  hooks/          # Custom hooks (useSkills, useFreshness)
  types/          # TypeScript interfaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing fancy. Pages fetch data on mount, pass to components via props. Components are mostly presentational.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-time Updates Without WebSockets
&lt;/h2&gt;

&lt;p&gt;When you log a practice event on the skill detail page, the dashboard freshness should update.&lt;/p&gt;

&lt;p&gt;Options considered:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;WebSockets&lt;/strong&gt; - Real-time push from server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling&lt;/strong&gt; - Fetch every N seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Browser events&lt;/strong&gt; - Signal between components in same tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Went with option 3. It's a SPA—dashboard and detail page components exist in the same JavaScript context. When detail page logs an event, it dispatches a custom event. Dashboard listens and refetches.&lt;/p&gt;

&lt;p&gt;No socket server to maintain. No unnecessary network requests. Works perfectly for single-tab usage.&lt;/p&gt;

&lt;p&gt;For multi-device sync (phone updates, desktop sees it), you'd need WebSockets or polling. Not implemented yet. Manual refresh works for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alert System: Calm by Design
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Philosophy
&lt;/h3&gt;

&lt;p&gt;Most apps optimize for engagement. More notifications = more opens = better metrics.&lt;/p&gt;

&lt;p&gt;SkillFade optimizes for calm. Alerts should be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Infrequent&lt;/strong&gt; - Maximum 1 email per week, total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actionable&lt;/strong&gt; - Only alert if user can do something about it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respectful&lt;/strong&gt; - Plain text, no tracking pixels, instant unsubscribe&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Three Alert Types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Decay Alert&lt;/strong&gt;&lt;br&gt;
Triggers when a skill drops below 40% freshness. But not immediately—waits for 14 days since last alert for that skill. Prevents nagging about the same thing repeatedly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Practice Gap Alert&lt;/strong&gt;&lt;br&gt;
Triggers when a skill has learning events but no practice events for 30+ days. The message: "You've been learning X but not practicing. Theory without application fades fast."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Imbalance Alert&lt;/strong&gt;&lt;br&gt;
Triggers when overall learning/practice ratio stays below 0.2 for two consecutive months. The message: "You're consuming a lot but producing little. Consider more hands-on work."&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation Notes
&lt;/h3&gt;

&lt;p&gt;Alerts run via scheduled job (cron), not real-time triggers. Once daily, batch process checks all users who have alerts enabled.&lt;/p&gt;

&lt;p&gt;Each alert type has independent cooldowns. You might get a decay alert and an imbalance alert in the same week, but never two decay alerts for the same skill within 14 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Design Principles
&lt;/h2&gt;

&lt;h3&gt;
  
  
  RESTful, But Practical
&lt;/h3&gt;

&lt;p&gt;Pure REST says everything should be a resource. I follow that mostly, but bend it when convenient:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /analytics/balance&lt;/code&gt; - Not really a "resource," but makes sense as an endpoint&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /auth/login&lt;/code&gt; - "login" isn't a noun, don't care&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pragmatism over purity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Flat URL Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET  /skills
GET  /skills/:id
POST /skills
PUT  /skills/:id

GET  /learning-events
POST /learning-events
PUT  /learning-events/:id
DELETE /learning-events/:id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not nested like &lt;code&gt;/skills/:id/learning-events/:eventId&lt;/code&gt;. Flat is simpler to route, simpler to reason about, simpler to document.&lt;/p&gt;

&lt;p&gt;Events reference skills via &lt;code&gt;skill_id&lt;/code&gt; in the body/params, not URL hierarchy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Consistent Response Patterns
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Success:&lt;/strong&gt; Return the data directly (object or array)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error:&lt;/strong&gt; Always &lt;code&gt;{ "detail": "Human readable message" }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pagination:&lt;/strong&gt; Offset-based with &lt;code&gt;?skip=0&amp;amp;limit=20&lt;/code&gt;, returns &lt;code&gt;{ items: [], total: number }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Frontend code stays simple when API is predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authentication: Keep It Simple
&lt;/h2&gt;

&lt;h3&gt;
  
  
  JWT Tokens
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Access token in Authorization header&lt;/li&gt;
&lt;li&gt;Stored in localStorage (yes, I know about XSS concerns—acceptable for this use case)&lt;/li&gt;
&lt;li&gt;30-minute expiration&lt;/li&gt;
&lt;li&gt;No refresh tokens—expired means re-login&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a personal productivity tool used by one person at a time, this is plenty secure. No need for refresh token rotation, token families, or OAuth complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Password Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;bcrypt with default work factor&lt;/li&gt;
&lt;li&gt;72-byte input limit (bcrypt truncates silently, so I handle it explicitly)&lt;/li&gt;
&lt;li&gt;No password rules beyond minimum length—let users choose their own passwords&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker Compose
&lt;/h3&gt;

&lt;p&gt;Three containers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; - Database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI&lt;/strong&gt; - Backend API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; - Serves frontend static files + proxies API requests&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Single &lt;code&gt;docker-compose up&lt;/code&gt; brings everything online. Environment variables for secrets, volumes for data persistence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Not Serverless?
&lt;/h3&gt;

&lt;p&gt;Lambda/Vercel functions would work for the API. Didn't go that route because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts matter for dashboard responsiveness&lt;/li&gt;
&lt;li&gt;PostgreSQL connection pooling is annoying in serverless&lt;/li&gt;
&lt;li&gt;I wanted full control over the environment&lt;/li&gt;
&lt;li&gt;Monthly cost is predictable with a VPS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a side project, a $10-20/month VPS beats managing serverless complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start Without Auth
&lt;/h3&gt;

&lt;p&gt;First two weeks, I built with a hardcoded user ID. No login, no registration, no password reset flow. Just the core features.&lt;/p&gt;

&lt;p&gt;This prevented the classic trap: spending weeks on auth while the actual product stays unbuilt.&lt;/p&gt;

&lt;p&gt;Added auth last, once everything else worked.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Migrations From Day One
&lt;/h3&gt;

&lt;p&gt;Even solo, even on a side project: use database migrations (Alembic for Python).&lt;/p&gt;

&lt;p&gt;Schema will change. "I'll just modify the table directly" becomes a nightmare when you have production data. Migrations saved me multiple times.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. TypeScript Everywhere
&lt;/h3&gt;

&lt;p&gt;Frontend and backend types should match. I define Pydantic schemas in Python, then manually keep TypeScript interfaces in sync.&lt;/p&gt;

&lt;p&gt;Not ideal (would love auto-generation), but catching type mismatches at compile time is worth the maintenance.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Boring Tech Compounds
&lt;/h3&gt;

&lt;p&gt;Every time I picked the "boring" option (PostgreSQL over MongoDB, REST over GraphQL, React over Svelte), I benefited from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Better documentation&lt;/li&gt;
&lt;li&gt;More Stack Overflow answers&lt;/li&gt;
&lt;li&gt;Easier debugging&lt;/li&gt;
&lt;li&gt;Smoother hiring (if I ever need help)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Novel tech is fun. Shipped products need boring tech.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Design for Deletion
&lt;/h3&gt;

&lt;p&gt;GDPR requires "right to deletion." Even if you're not in the EU, it's good practice.&lt;/p&gt;

&lt;p&gt;Plan for cascade deletes from day one. No orphaned data, no "soft delete" flags that accumulate forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;h3&gt;
  
  
  E2E Tests Earlier
&lt;/h3&gt;

&lt;p&gt;I relied on manual testing too long. Click through the app, check if it works. Tedious and error-prone.&lt;/p&gt;

&lt;p&gt;Should have set up Playwright or Cypress from week two.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Alert System First
&lt;/h3&gt;

&lt;p&gt;Retrofitting notifications into an existing schema was messy. Alert cooldowns, user preferences, email templates—all added later, all awkward.&lt;/p&gt;

&lt;p&gt;If I started over, I'd design the alert data model upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use a Component Library
&lt;/h3&gt;

&lt;p&gt;Built every button, input, modal, and dropdown from scratch with Tailwind. Educational, but slow.&lt;/p&gt;

&lt;p&gt;Next time: Radix UI or Headless UI for primitives, custom styling on top.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anti-Complexity Manifesto
&lt;/h2&gt;

&lt;p&gt;Every feature request, every "nice to have," every shiny technology gets filtered through one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Does the added complexity justify the value?"&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Microservices? No. One process is fine until it isn't.&lt;/li&gt;
&lt;li&gt;GraphQL? No. REST handles every use case here.&lt;/li&gt;
&lt;li&gt;Redis caching? No. PostgreSQL is fast enough.&lt;/li&gt;
&lt;li&gt;Real-time sync? No. Manual refresh works.&lt;/li&gt;
&lt;li&gt;Mobile app? No. PWA covers it.&lt;/li&gt;
&lt;li&gt;AI recommendations? No. That's not what this product is.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best code is code you don't write. The best infrastructure is infrastructure you don't maintain. The best feature is the one that solves the problem without adding moving parts.&lt;/p&gt;




&lt;p&gt;That's the technical foundation of SkillFade. The theme throughout: &lt;strong&gt;simple, boring, maintainable&lt;/strong&gt;. Complexity is easy to add. Simplicity is hard to maintain.&lt;/p&gt;

&lt;p&gt;If you're building a side project, my advice: pick boring tech, ship fast, add complexity only when forced.&lt;/p&gt;

&lt;p&gt;Questions about specific decisions? Drop them in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>fastapi</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The uncomfortable truth: that course you finished 6 months ago? Almost gone.</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Sun, 25 Jan 2026 17:11:03 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/the-uncomfortable-truth-that-course-you-finished-6-months-ago-almost-gone-4jba</link>
      <guid>https://dev.to/ruhidibadli/the-uncomfortable-truth-that-course-you-finished-6-months-ago-almost-gone-4jba</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/ruhidibadli" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1036130%2F095a6797-806c-43e4-83b2-1d445133fde9.jpg" alt="ruhidibadli"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/ruhidibadli/skillfade-a-skill-tracking-app-that-tells-you-the-truth-not-what-you-want-to-hear-3m10" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;SkillFade: A Skill Tracking App That Tells You the Truth (Not What You Want to Hear)&lt;/h2&gt;
      &lt;h3&gt;Ruhid Ibadli ・ Jan 25&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#python&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>react</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Sun, 25 Jan 2026 17:08:22 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/-49n8</link>
      <guid>https://dev.to/ruhidibadli/-49n8</guid>
      <description>&lt;div class="ltag__link"&gt;
  &lt;a href="/ruhidibadli" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1036130%2F095a6797-806c-43e4-83b2-1d445133fde9.jpg" alt="ruhidibadli"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/ruhidibadli/skillfade-a-skill-tracking-app-that-tells-you-the-truth-not-what-you-want-to-hear-3m10" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;SkillFade: A Skill Tracking App That Tells You the Truth (Not What You Want to Hear)&lt;/h2&gt;
      &lt;h3&gt;Ruhid Ibadli ・ Jan 25&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#react&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#python&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#productivity&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>react</category>
      <category>python</category>
      <category>productivity</category>
    </item>
    <item>
      <title>SkillFade: A Skill Tracking App That Tells You the Truth (Not What You Want to Hear)</title>
      <dc:creator>Ruhid Ibadli</dc:creator>
      <pubDate>Sun, 25 Jan 2026 14:46:54 +0000</pubDate>
      <link>https://dev.to/ruhidibadli/skillfade-a-skill-tracking-app-that-tells-you-the-truth-not-what-you-want-to-hear-3m10</link>
      <guid>https://dev.to/ruhidibadli/skillfade-a-skill-tracking-app-that-tells-you-the-truth-not-what-you-want-to-hear-3m10</guid>
      <description>&lt;p&gt;The Problem with Learning Apps&lt;/p&gt;

&lt;p&gt;Most skill tracking and learning apps follow the same playbook: gamification,&lt;br&gt;&lt;br&gt;
  streaks, badges, and dopamine hits. They're designed to make you feel good, not to&lt;br&gt;
   show you reality.&lt;/p&gt;

&lt;p&gt;But here's the uncomfortable truth: skills decay without practice. That Python&lt;br&gt;&lt;br&gt;
  course you finished 6 months ago? It's fading. That framework you learned but&lt;br&gt;&lt;br&gt;
  never used in a project? Almost gone.&lt;/p&gt;

&lt;p&gt;I built SkillFade to be different—a mirror, not a coach.&lt;/p&gt;

&lt;p&gt;What is SkillFade?&lt;/p&gt;

&lt;p&gt;SkillFade is a web application that tracks your skills and exposes three&lt;br&gt;
  uncomfortable realities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Learning Decay&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skills degrade over time without reinforcement. SkillFade calculates "freshness"&lt;br&gt;&lt;br&gt;
  (0-100%) based on when you last practiced, not just when you last read about&lt;br&gt;&lt;br&gt;
  something.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Practice Scarcity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reading tutorials and watching videos isn't the same as doing. SkillFade flags&lt;br&gt;&lt;br&gt;
  skills where you've been learning but not applying.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input/Output Imbalance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Too much consumption, too little production. The app tracks your&lt;br&gt;
  learning-to-practice ratio and gently reminds you when you're all input and no&lt;br&gt;&lt;br&gt;
  output.&lt;/p&gt;

&lt;p&gt;The Anti-Gamification Approach&lt;/p&gt;

&lt;p&gt;What SkillFade deliberately doesn't have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ No points or badges&lt;/li&gt;
&lt;li&gt;❌ No streaks (they cause anxiety, not learning)&lt;/li&gt;
&lt;li&gt;❌ No leaderboards&lt;/li&gt;
&lt;li&gt;❌ No "motivational" push notifications&lt;/li&gt;
&lt;li&gt;❌ No AI recommendations&lt;/li&gt;
&lt;li&gt;❌ No social features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead, it offers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Honest freshness percentages&lt;/li&gt;
&lt;li&gt;✅ Calm, infrequent email alerts (max 1/week)&lt;/li&gt;
&lt;li&gt;✅ Simple charts showing your actual patterns&lt;/li&gt;
&lt;li&gt;✅ Full data export and deletion&lt;/li&gt;
&lt;li&gt;✅ Zero third-party tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How Freshness Works&lt;/p&gt;

&lt;p&gt;The core algorithm is simple but honest:&lt;/p&gt;

&lt;p&gt;freshness = 100% × (0.98 ^ days_since_practice) + learning_boost&lt;/p&gt;

&lt;p&gt;Practice keeps skills fresh. Learning only slows decay—it doesn't reverse it.     &lt;/p&gt;

&lt;p&gt;Visual indicators make it clear at a glance:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🟢 &amp;gt;70% - Fresh&lt;/li&gt;
&lt;li&gt;🟡 40-70% - Aging
&lt;/li&gt;
&lt;li&gt;🔴 &amp;lt;40% - Decayed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Key Features&lt;/p&gt;

&lt;p&gt;Skill Categories &amp;amp; Dependencies&lt;br&gt;
  Organize skills by category. Mark prerequisites between skills and get alerts when&lt;br&gt;
   your foundations are decaying.&lt;/p&gt;

&lt;p&gt;Activity Calendar&lt;br&gt;
  See your learning and practice patterns at a glance. Track what you're actually&lt;br&gt;&lt;br&gt;
  doing, not what you planned to do.&lt;/p&gt;

&lt;p&gt;Balance Ratio&lt;br&gt;
  Visual representation of your input (learning) vs output (practice). Spot&lt;br&gt;
  imbalances before they become problems.&lt;/p&gt;

&lt;p&gt;Custom Decay Rates&lt;br&gt;
  Some skills fade faster than others. Set custom decay rates per skill based on&lt;br&gt;&lt;br&gt;
  complexity.&lt;/p&gt;

&lt;p&gt;Freshness Targets&lt;br&gt;
  Set personal freshness thresholds and get notified when skills drop below your&lt;br&gt;&lt;br&gt;
  standards.&lt;/p&gt;

&lt;p&gt;PWA Support&lt;br&gt;
  Install on mobile or desktop. Works offline.&lt;/p&gt;

&lt;p&gt;Dark Mode&lt;br&gt;
  Because we respect your eyes.&lt;/p&gt;

&lt;p&gt;Privacy First&lt;/p&gt;

&lt;p&gt;This isn't another app harvesting your data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No Google Analytics or third-party tracking&lt;/li&gt;
&lt;li&gt;No tracking pixels&lt;/li&gt;
&lt;li&gt;No data sharing with anyone&lt;/li&gt;
&lt;li&gt;Full JSON export of all your data&lt;/li&gt;
&lt;li&gt;Permanent account deletion (we actually delete everything)&lt;/li&gt;
&lt;li&gt;Email only used for optional alerts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Who Is This For?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Self-directed learners tired of gamification&lt;/li&gt;
&lt;li&gt;Developers maintaining multiple tech skills&lt;/li&gt;
&lt;li&gt;Career switchers tracking new field knowledge&lt;/li&gt;
&lt;li&gt;Anyone who values long-term insight over short-term dopamine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Philosophy&lt;/p&gt;

&lt;p&gt;"This product is a mirror, not a coach. It does not push, judge, or optimize the&lt;br&gt;&lt;br&gt;
  user. It simply tells the truth, kindly and clearly."&lt;/p&gt;

&lt;p&gt;Sometimes what we need isn't another app cheering us on. Sometimes we just need&lt;br&gt;&lt;br&gt;
  honest data about where we actually stand.&lt;/p&gt;

&lt;p&gt;Try SkillFade&lt;/p&gt;

&lt;p&gt;Ready to see where your skills actually stand?&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://skillfade.website" rel="noopener noreferrer"&gt;https://skillfade.website&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's free to get started. You can support using Support button.&lt;/p&gt;




&lt;p&gt;What do you think? Would you use a skill tracker that prioritizes honesty over&lt;br&gt;&lt;br&gt;
  engagement metrics? I'd love to hear your thoughts on the anti-gamification&lt;br&gt;&lt;br&gt;
  approach in the comments.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>python</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
