<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Samuel Ochaba</title>
    <description>The latest articles on DEV Community by Samuel Ochaba (@samuel_ochaba_eb9c875fa89).</description>
    <link>https://dev.to/samuel_ochaba_eb9c875fa89</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3602341%2F962160fe-c301-4314-bc6b-9e98da6f00c8.png</url>
      <title>DEV Community: Samuel Ochaba</title>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/samuel_ochaba_eb9c875fa89"/>
    <language>en</language>
    <item>
      <title>Garbage Collection Isn't Magic: Why Your App Still Leaks Memory</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Sun, 21 Jun 2026 09:34:08 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/garbage-collection-isnt-magic-why-your-app-still-leaks-memory-1jig</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/garbage-collection-isnt-magic-why-your-app-still-leaks-memory-1jig</guid>
      <description>&lt;p&gt;You picked JavaScript (or Python, or Go, or Java) partly so you'd never have to think about memory. No &lt;code&gt;malloc&lt;/code&gt;, no &lt;code&gt;free&lt;/code&gt;, no dangling pointers. The runtime cleans up after you.&lt;/p&gt;

&lt;p&gt;So why does your app get slower the longer it runs? Why does the process climb from 80 MB to 1.5 GB overnight, then feel instant again the moment you restart, only to climb right back up?&lt;/p&gt;

&lt;p&gt;Here is the uncomfortable truth: the garbage collector is working perfectly. It is reclaiming exactly what it was designed to reclaim. The leak is not a failure of the language. It is a data structure you wrote that holds a reference forever.&lt;/p&gt;

&lt;p&gt;In this post I'll show how reachability actually works, why two styles of collector differ, and the one-line mental model that stops the most common memory leak in modern code. There's a working example at the end. ([I wrote more on profiling this here][add link].)&lt;/p&gt;

&lt;h2&gt;
  
  
  Garbage collection decides one question: is it reachable?
&lt;/h2&gt;

&lt;p&gt;Modern collectors do not count how many times you use a value. They ask a single, sharp question: &lt;strong&gt;can I still get to this object from somewhere that matters?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The "somewhere that matters" set is called the &lt;strong&gt;roots&lt;/strong&gt;. Roots are the live, in-scope things: local variables on the call stack, globals, event handlers the runtime still tracks. From those roots, the collector walks every reference, then every reference those references hold, and so on, marking everything it touches as alive. Everything it never reaches is garbage, and it gets freed in bulk.&lt;/p&gt;

&lt;p&gt;That is the whole model. If you can draw a line of pointers from a root to your object, the object survives. If you can't, it dies.&lt;/p&gt;

&lt;p&gt;This is beautiful and generous, and it has one sharp edge it never warns you about: &lt;strong&gt;the collector has no opinions about your data structures.&lt;/strong&gt; If your cache holds a pointer to a task you looked at an hour ago, the collector traces that pointer, reaches the task, and concludes "alive." It is not leaking. It is obeying you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reachability is not reference counting
&lt;/h2&gt;

&lt;p&gt;People often picture garbage collection as a tally: every object counts how many things point at it, and when the count hits zero, free it on the spot. That strategy is real, it's called &lt;strong&gt;reference counting&lt;/strong&gt;, and some languages use it (Python's CPython is the famous example, with a tracing collector as backup).&lt;/p&gt;

&lt;p&gt;It has one fatal flaw: &lt;strong&gt;cycles.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;partner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="c1"&gt;# a and b now reference each other.
# Each has a reference count of at least 1, forever,
# even if nothing else in the program can reach them.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two objects pointing at each other never hit zero. A pure reference counter would hold them until the process exits.&lt;/p&gt;

&lt;p&gt;The collectors in JavaScript, Go, and Java don't have this problem, because they don't count references at all. They &lt;strong&gt;trace&lt;/strong&gt; from roots, periodically, and anything unreachable dies regardless of how many internal pointers it has. Cycles are free.&lt;/p&gt;

&lt;p&gt;Which means: if you are debugging a memory leak in a tracing-collected language, the cycle explanation is a dead end. The real cause is almost always simpler.&lt;/p&gt;

&lt;h2&gt;
  
  
  The modern leak, with a real example
&lt;/h2&gt;

&lt;p&gt;A reader was sketching a "recently viewed" menu for a task app. Clean, tiny feature. This was the heart of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// every task we ever look at lands here&lt;/span&gt;
  &lt;span class="nf"&gt;renderRecent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It felt great for the first fifty views. Then the UI slowed. Then it crawled. Restart, instant again, fifty more views, slow again.&lt;/p&gt;

&lt;p&gt;Nothing is wrong with that &lt;code&gt;push&lt;/code&gt;. Nothing is wrong with the language. The problem is that &lt;code&gt;recent&lt;/code&gt; is a root-reachable array, and it holds a pointer to &lt;strong&gt;every task the user has ever opened.&lt;/strong&gt; The collector traces the chain &lt;code&gt;recent -&amp;gt; task&lt;/code&gt;, marks each task alive, and correctly refuses to free it. The array grows without bound. So does memory.&lt;/p&gt;

&lt;p&gt;The fix is not smarter collection. It is to stop holding the reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RECENT_LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;recent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;trackView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;RECENT_LIMIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// drop the oldest entry&lt;/span&gt;
    &lt;span class="c1"&gt;// nothing else points to it now -&amp;gt; unreachable -&amp;gt; collected&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;renderRecent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment &lt;code&gt;shift()&lt;/code&gt; removes the last reference to an old task, the collector can no longer reach it from any root. Next collection cycle, that memory comes back. No &lt;code&gt;free()&lt;/code&gt;, no &lt;code&gt;delete&lt;/code&gt;. Just a data structure that finally agrees to let go.&lt;/p&gt;

&lt;h2&gt;
  
  
  A mental model that catches these before they ship
&lt;/h2&gt;

&lt;p&gt;After the tenth leak of this shape, I wrote myself a one-line rule and taped it to the mental wall:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;If a collection can grow without bound, treat it as a leak until proven otherwise.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you review a PR, scan for these patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caches and "recent" lists&lt;/strong&gt; with no eviction policy or size cap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event listeners or observers&lt;/strong&gt; added but never removed, holding their context alive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closures&lt;/strong&gt; that capture large objects they never actually need.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Global accumulators&lt;/strong&gt; (logs, metrics, registries) that append forever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maps keyed by long-lived objects&lt;/strong&gt; where stale keys are never deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these is a root-reachable structure quietly pinning memory in place. The collector is doing its job. The structure is doing yours, badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Automatic memory management is not automatic memory &lt;em&gt;forgetting&lt;/em&gt;. The collector will reclaim everything you stop reaching for, and it will dutifully keep everything you keep reaching for, even if you forgot you still were.&lt;/p&gt;

&lt;p&gt;So the next time your app slows and stays slow, before you blame the runtime, ask the only question that matters: &lt;strong&gt;what am I still pointing at?&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write about performance and clean architecture every week. Follow along if that's your kind of thing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over to you:&lt;/strong&gt; what's the dumbest memory leak you've ever shipped, and how long did it take you to find it? Drop it below, I'm collecting war stories. 👇&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>beginners</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Stop Hitting Yourself: The Quiet Lie in Function Signatures</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Sun, 21 Jun 2026 09:25:41 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/stop-hitting-yourself-the-quiet-lie-in-function-signatures-21eh</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/stop-hitting-yourself-the-quiet-lie-in-function-signatures-21eh</guid>
      <description>&lt;p&gt;Every codebase has a function like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... fetch from DB ...&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks innocent. It is not. That signature makes a &lt;strong&gt;promise&lt;/strong&gt;: give me an ID, I will hand you back a &lt;code&gt;User&lt;/code&gt;. Except it will not. Sometimes there is no user. Sometimes the database is on fire. Sometimes the caller passed an ID that was never valid. The signature does not tell you which, and that silence is one of the most expensive bugs in software.&lt;/p&gt;

&lt;p&gt;This post is about the difference between &lt;strong&gt;code that runs&lt;/strong&gt; and &lt;strong&gt;code that is honest&lt;/strong&gt;, and the three failure modes that separate them. It is short, it has working TypeScript, and by the end you will know exactly what to put in your type signatures the next time you are tempted to write &lt;code&gt;return null&lt;/code&gt; or &lt;code&gt;throw new Error(...)&lt;/code&gt; and move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lie Hiding in Plain Sight
&lt;/h2&gt;

&lt;p&gt;A type signature is a contract. When you write &lt;code&gt;getUser(id: string): User&lt;/code&gt;, you are telling every caller, every reviewer, and the compiler itself: &lt;em&gt;this returns a &lt;code&gt;User&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Then you do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 🤥&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature still says &lt;code&gt;User&lt;/code&gt;. The body returns &lt;code&gt;null&lt;/code&gt;. The caller has no way to know without reading the implementation, and once you are reading the implementation, the type system has stopped doing its job. This is the family of bug Tony Hoare famously called his &lt;strong&gt;billion-dollar mistake&lt;/strong&gt;: letting types promise a value they might not deliver.&lt;/p&gt;

&lt;p&gt;So how do you fix it? First you have to realize there is not one kind of "something went wrong." There are three.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exceptions, Errors, and Panics: Know the Difference
&lt;/h2&gt;

&lt;p&gt;Most codebases treat every failure as an exception, and that is the root of the confusion. Let me untangle them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exceptions: for things &lt;em&gt;you&lt;/em&gt; cannot handle
&lt;/h3&gt;

&lt;p&gt;An exception is for &lt;strong&gt;exceptional conditions&lt;/strong&gt;, things outside this function's control or knowledge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the database is unreachable&lt;/li&gt;
&lt;li&gt;a required config file is missing&lt;/li&gt;
&lt;li&gt;a network call timed out mid-request&lt;/li&gt;
&lt;li&gt;a programming bug, like calling a method on the wrong shape of object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The defining property: &lt;strong&gt;the current function does not know what to do&lt;/strong&gt;, so it unwinds the stack and asks someone above it to figure it out. If nobody can, the request dies. That is the correct outcome. You genuinely did not know.&lt;/p&gt;

&lt;h3&gt;
  
  
  Errors: for things the &lt;em&gt;caller&lt;/em&gt; must handle
&lt;/h3&gt;

&lt;p&gt;An error is an &lt;strong&gt;expected, recoverable failure&lt;/strong&gt; that belongs to the normal operation of the program:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the record was not found&lt;/li&gt;
&lt;li&gt;the input failed validation&lt;/li&gt;
&lt;li&gt;the user lacks permission&lt;/li&gt;
&lt;li&gt;a file already exists&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not exceptional. They are Tuesday. The function knows they are possible, and the right move is to &lt;strong&gt;return&lt;/strong&gt; the failure so the caller is &lt;em&gt;forced&lt;/em&gt; to deal with it, not throw it and hope.&lt;/p&gt;

&lt;h3&gt;
  
  
  Panics: for the impossible
&lt;/h3&gt;

&lt;p&gt;A panic is for an &lt;strong&gt;unrecoverable invariant violation&lt;/strong&gt;, a state the author believed could not happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a switch statement hits a case that should be unreachable&lt;/li&gt;
&lt;li&gt;an array that was just checked is somehow empty&lt;/li&gt;
&lt;li&gt;an internal counter is negative&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You do not catch a panic. It means the program is in a state nobody designed for, and continuing would be worse than stopping. Crash loudly, with a stack trace, so somebody fixes it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the Possibility Visible in the Type
&lt;/h2&gt;

&lt;p&gt;Now the fix writes itself. "Record not found" is an &lt;strong&gt;error&lt;/strong&gt;, not an exception. So do not throw, and do not sneakily return &lt;code&gt;null&lt;/code&gt;. Make the absence part of the type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the signature tells the truth: &lt;em&gt;a &lt;code&gt;User&lt;/code&gt;, or nothing.&lt;/em&gt; And TypeScript will not let the caller pretend otherwise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ❌ Error: 'user' is possibly 'null'&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ✅ narrowed to User&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The compiler became your ally instead of your adversary. The bug that would have shipped at runtime now fails at &lt;code&gt;tsc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Want to carry a reason along with the absence? Reach for the richer version of the same idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;FindResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not_found&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;forbidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;FindResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same principle, more information. Either way, the signature stops lying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three Rules to Take With You
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Throw for the exceptional.&lt;/strong&gt; Return for the expected. Crash for the impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If the caller needs to handle it, it goes in the type.&lt;/strong&gt; &lt;code&gt;null&lt;/code&gt;, a discriminated union, a &lt;code&gt;Result&lt;/code&gt;. Never a silent lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every signature is a promise.&lt;/strong&gt; Make ones you can keep, and let the compiler enforce them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Over to You
&lt;/h2&gt;

&lt;p&gt;The next time you reach for &lt;code&gt;throw new Error("not found")&lt;/code&gt; or &lt;code&gt;return null&lt;/code&gt; on a typed &lt;code&gt;User&lt;/code&gt;, ask the question that matters: &lt;em&gt;what is my signature actually promising, and is it true?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I am genuinely curious how other teams draw the exception/error/panic line. Do you throw on "not found" and catch it later, return an optional, or use a full &lt;code&gt;Result&lt;/code&gt; type? Tell me in the comments, I read all of them.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If this was useful, I write about TypeScript, system design, and the small decisions that compound in a codebase. Follow for the next one, and check out my earlier post on the same idea applied to optional fields.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>typescript</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Refactoring is not a bag of tricks. It is a professional discipline.</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Thu, 09 Apr 2026 12:17:44 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/refactoring-is-not-a-bag-of-tricks-it-is-a-professional-discipline-5agp</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/refactoring-is-not-a-bag-of-tricks-it-is-a-professional-discipline-5agp</guid>
      <description>&lt;p&gt;We built &lt;strong&gt;TekBreed&lt;/strong&gt; because we think many developers are not struggling from a lack of information.&lt;/p&gt;

&lt;p&gt;They are struggling from a lack of the right kind of training.&lt;/p&gt;

&lt;p&gt;There is a difference.&lt;/p&gt;

&lt;p&gt;Information tells you what something is.&lt;br&gt;&lt;br&gt;
Training helps you operate when the situation is unclear, messy, or high-stakes.&lt;/p&gt;

&lt;p&gt;That difference is especially visible in refactoring.&lt;/p&gt;

&lt;p&gt;That is why one of the sprints we are most excited about on TekBreed is:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Refactoring Mastery: Thinking &amp;amp; Building Like a Senior Engineer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Right now, during early access, the &lt;strong&gt;first milestone of that sprint is open for free until April 13, 2026&lt;/strong&gt;, along with the first milestone of every premium sprint on the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why refactoring was one of the first topics we wanted to teach this way
&lt;/h2&gt;

&lt;p&gt;Refactoring looks simple when you only see the polished version.&lt;/p&gt;

&lt;p&gt;A blog post shows ugly code.&lt;br&gt;&lt;br&gt;
Then it shows cleaner code.&lt;br&gt;&lt;br&gt;
Then it explains the technique.&lt;/p&gt;

&lt;p&gt;That is useful, but it does not fully train the real skill.&lt;/p&gt;

&lt;p&gt;Behind the scenes, the real challenge in refactoring is not just knowing a technique.&lt;br&gt;&lt;br&gt;
It is knowing how to evaluate structure under real constraints.&lt;/p&gt;

&lt;p&gt;That includes questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is safe to change right now?&lt;/li&gt;
&lt;li&gt;What behavior must be preserved?&lt;/li&gt;
&lt;li&gt;What part of this code is actually causing pain?&lt;/li&gt;
&lt;li&gt;Should this be refactored now, later, or not at all?&lt;/li&gt;
&lt;li&gt;Is this change improving the system or just making it look nicer?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is closer to real engineering work.&lt;/p&gt;

&lt;p&gt;And that is the kind of thinking we want TekBreed to support.&lt;/p&gt;

&lt;h2&gt;
  
  
  What TekBreed is trying to do differently
&lt;/h2&gt;

&lt;p&gt;TekBreed is a software engineering learning platform built around deeper understanding, practical thinking, and more realistic skill development.&lt;/p&gt;

&lt;p&gt;We are not trying to be just another place to consume tutorials.&lt;/p&gt;

&lt;p&gt;We want the platform to help learners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build stronger mental models&lt;/li&gt;
&lt;li&gt;Move beyond surface-level code familiarity&lt;/li&gt;
&lt;li&gt;Interact with ideas more actively&lt;/li&gt;
&lt;li&gt;Get help when they are stuck&lt;/li&gt;
&lt;li&gt;Think more like practicing engineers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is also where &lt;strong&gt;TekChat&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why TekChat matters
&lt;/h2&gt;

&lt;p&gt;A learner usually does not need help only before or after a lesson.&lt;/p&gt;

&lt;p&gt;They need help in the middle of it.&lt;/p&gt;

&lt;p&gt;Inside the moment where something almost makes sense, but not quite.&lt;br&gt;&lt;br&gt;
Inside the moment where a concept becomes slippery.&lt;br&gt;&lt;br&gt;
Inside the moment where they need one more explanation, one more example, or one more reframing.&lt;/p&gt;

&lt;p&gt;That is where &lt;strong&gt;TekChat&lt;/strong&gt; is meant to be useful.&lt;/p&gt;

&lt;p&gt;Behind the scenes, the goal is not to remove effort.&lt;br&gt;&lt;br&gt;
The goal is to support effort.&lt;/p&gt;

&lt;p&gt;We want TekChat to help learners keep moving when they hit friction, instead of quietly dropping off.&lt;/p&gt;

&lt;p&gt;And because we are in early access, we want people to use TekChat however they want and tell us where it is genuinely helpful and where it still needs work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the refactoring sprint is a strong place to start
&lt;/h2&gt;

&lt;p&gt;The refactoring sprint represents a lot of what we care about.&lt;/p&gt;

&lt;p&gt;It is not only about code cleanup.&lt;br&gt;&lt;br&gt;
It is about engineering judgment.&lt;/p&gt;

&lt;p&gt;It is about learning to think carefully about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Structure&lt;/li&gt;
&lt;li&gt;Safety&lt;/li&gt;
&lt;li&gt;Behavior preservation&lt;/li&gt;
&lt;li&gt;Tradeoffs&lt;/li&gt;
&lt;li&gt;Decision-making under constraints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why the sprint is called:&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Refactoring Mastery: Thinking &amp;amp; Building Like a Senior Engineer&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;We wanted the framing to be honest.&lt;/p&gt;

&lt;p&gt;This is not “10 refactoring tricks.”&lt;br&gt;&lt;br&gt;
It is a more serious attempt to teach what better engineering judgment looks like in practice.&lt;/p&gt;

&lt;p&gt;And because the &lt;strong&gt;first milestone is open&lt;/strong&gt;, people can experience the direction of that sprint right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What early access looks like
&lt;/h2&gt;

&lt;p&gt;From now until &lt;strong&gt;April 13, 2026&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TekBreed is open for early access&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;first milestone of every premium sprint&lt;/strong&gt; is free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TekChat&lt;/strong&gt; can be used freely&lt;/li&gt;
&lt;li&gt;Feedback is strongly encouraged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then, from &lt;strong&gt;April 13 through April 30, 2026&lt;/strong&gt;, subscribers get &lt;strong&gt;30% off&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not meant to be a soft, polite preview.&lt;/p&gt;

&lt;p&gt;We want real usage.&lt;/p&gt;

&lt;p&gt;We want people to test the product properly.&lt;br&gt;&lt;br&gt;
We want them to try the refactoring sprint.&lt;br&gt;&lt;br&gt;
We want them to use TekChat heavily.&lt;br&gt;&lt;br&gt;
We want them to tell us what feels sharp, what feels unclear, and what breaks.&lt;/p&gt;

&lt;p&gt;Because that is how the platform gets better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the first milestone
&lt;/h2&gt;

&lt;p&gt;If you are interested in refactoring, engineering judgment, or learning environments that go beyond passive tutorials, this is a good time to try TekBreed.&lt;/p&gt;

&lt;p&gt;Start with the first milestone of the refactoring sprint.&lt;br&gt;&lt;br&gt;
Use TekChat while you go through it.&lt;br&gt;&lt;br&gt;
Then leave feedback.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tekbreed.com/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=soft_launch_2026" rel="noopener noreferrer"&gt;TekBreed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tekbreed.com/sprints/refactoring-mastery-thinking-and-building-like-a-senior-engineer?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=soft_launch_2026" rel="noopener noreferrer"&gt;Refactoring sprint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tekbreed.com/about?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=soft_launch_2026" rel="noopener noreferrer"&gt;About TekBreed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tekbreed.com/sprints/refactoring-mastery-thinking-and-building-like-a-senior-engineer?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=soft_launch_2026" rel="noopener noreferrer"&gt;Early access announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>refactoring</category>
      <category>softwareengineering</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>The Python Bug That Bites Every Developer (Exactly Once)</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Fri, 23 Jan 2026 13:59:05 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/the-python-bug-that-bites-every-developer-exactly-once-4m8j</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/the-python-bug-that-bites-every-developer-exactly-once-4m8j</guid>
      <description>&lt;p&gt;I stared at my screen for 20 minutes, convinced Python was broken.&lt;/p&gt;

&lt;p&gt;My function was supposed to return a fresh list every time. Instead, it was accumulating items like it had a memory. The third call somehow contained results from the first two.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# ['apple'] ✓
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# ['apple', 'banana'] ???
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# ['apple', 'banana', 'cherry'] !!!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I expected &lt;code&gt;['banana']&lt;/code&gt;. I got &lt;code&gt;['apple', 'banana']&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is Python's &lt;strong&gt;mutable default argument bug&lt;/strong&gt; — and every Python developer gets bitten by it exactly once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Mental Model That's Lying to You
&lt;/h2&gt;

&lt;p&gt;When you see &lt;code&gt;items=[]&lt;/code&gt;, your brain reads: &lt;em&gt;"If no list is provided, create an empty one."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That's wrong.&lt;/p&gt;

&lt;p&gt;What Python actually does: &lt;em&gt;"Create one empty list **right now, when the function is defined&lt;/em&gt;&lt;em&gt;, and reuse that same object for every call."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The default value isn't &lt;em&gt;"an empty list"&lt;/em&gt; — it's a &lt;strong&gt;reference to one specific list object&lt;/strong&gt; that was created when Python first read your function.&lt;/p&gt;

&lt;p&gt;Let me prove it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;List id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# List id: 4399504832
&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# List id: 4399504832  ← Same object!
&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# List id: 4399504832  ← Still same!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every call uses the &lt;strong&gt;exact same list object&lt;/strong&gt;. When you append to it, that change persists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix: Use None as a Sentinel
&lt;/h2&gt;

&lt;p&gt;The solution is elegant once you understand the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# Create a NEW list each call
&lt;/span&gt;    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apple&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# ['apple']
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# ['banana'] ✓
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now each call that doesn't provide &lt;code&gt;items&lt;/code&gt; gets a &lt;strong&gt;fresh list&lt;/strong&gt;, created at call time, not definition time.&lt;/p&gt;

&lt;p&gt;This pattern should become automatic for any mutable default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Lists
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Dictionaries
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Sets
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Actually Matters
&lt;/h2&gt;

&lt;p&gt;This isn't just a gotcha for interviews. It causes real bugs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching gone wrong&lt;/strong&gt;: Your memoization function "remembers" too much&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API handlers accumulating state&lt;/strong&gt;: Each request sees data from previous requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test pollution&lt;/strong&gt;: Tests pass individually but fail when run together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've seen this bug in production code from senior engineers. It's subtle because &lt;strong&gt;the function works perfectly the first time&lt;/strong&gt; — the bug only appears on subsequent calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Deeper Lesson
&lt;/h2&gt;

&lt;p&gt;Python's object model is consistent: variables are names pointing to objects. When you write &lt;code&gt;items=[]&lt;/code&gt;, you're creating an object and storing a reference to it. That reference is evaluated &lt;strong&gt;once&lt;/strong&gt; — when the &lt;code&gt;def&lt;/code&gt; statement runs.&lt;/p&gt;

&lt;p&gt;Understanding this doesn't just help you avoid one bug. It unlocks how Python actually works under the hood.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article is adapted from my upcoming book, **Zero to AI Engineer: Python Foundations&lt;/em&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I share excerpts like this on &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;Substack&lt;/a&gt; — follow along for more!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>beginners</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Why You Freeze When Starting a New Project (And 5 Questions to Fix It)</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Sun, 18 Jan 2026 15:32:00 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/why-you-freeze-when-starting-a-new-project-and-5-questions-to-fix-it-57mm</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/why-you-freeze-when-starting-a-new-project-and-5-questions-to-fix-it-57mm</guid>
      <description>&lt;p&gt;&lt;strong&gt;You know the feeling.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You have a great idea for a project. You open your code editor. You create &lt;code&gt;main.py&lt;/code&gt;. The blinking cursor waits.&lt;/p&gt;

&lt;p&gt;And you freeze.&lt;/p&gt;

&lt;p&gt;What do you type first? Do you need a class? A function? A variable? Ten minutes later, you're checking Reddit because the paralyzed feeling is too uncomfortable.&lt;/p&gt;

&lt;p&gt;This happens not because you can't code, but because you're trying to do two things at once: &lt;strong&gt;designing&lt;/strong&gt; the software and &lt;strong&gt;building&lt;/strong&gt; it.&lt;/p&gt;

&lt;p&gt;Professional developers don't start by writing code. They start by asking questions.&lt;/p&gt;

&lt;p&gt;Here is the &lt;strong&gt;Five Questions Framework&lt;/strong&gt; I teach my students to turn any vague idea into a concrete blueprint before writing a single line of Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. WHAT does it do? (The Menu)
&lt;/h2&gt;

&lt;p&gt;Imagine you're opening a restaurant. Before you buy stoves or hire chefs, you need a menu.&lt;/p&gt;

&lt;p&gt;Your "menu" is your feature list. Don't worry about &lt;em&gt;how&lt;/em&gt; to build it yet. Just list &lt;em&gt;what&lt;/em&gt; it does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: A Simple Contact Book&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new contacts&lt;/li&gt;
&lt;li&gt;View all contacts&lt;/li&gt;
&lt;li&gt;Search by name&lt;/li&gt;
&lt;li&gt;Save to file&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. WHO does the work? (The Staff)
&lt;/h2&gt;

&lt;p&gt;In a restaurant, the host greets you, the chef cooks, and the dishwasher cleans. They have distinct &lt;strong&gt;responsibilities&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Group your features into "staff members" or layers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User Interface (UI):&lt;/strong&gt; The "Host". Displays menus, asks for input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Business Logic:&lt;/strong&gt; The "Chef". Adds, searches, and deletes contacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; The "Archive Manager". Saves and loads JSON files.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. HOW are they connected? (The Kitchen Flow)
&lt;/h2&gt;

&lt;p&gt;The host doesn't cook the burger. The host hands an order to the chef.&lt;/p&gt;

&lt;p&gt;Draw a simple dependency line:&lt;br&gt;
&lt;code&gt;UI -&amp;gt; Logic -&amp;gt; Storage&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This tells you something critical: &lt;strong&gt;The Storage layer doesn't know the UI exists.&lt;/strong&gt; This is "Separation of Concerns," and it prevents your code from becoming spaghetti.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. WHAT flows between them? (The Dish)
&lt;/h2&gt;

&lt;p&gt;When the host hands an order to the chef, what is written on the ticket?&lt;/p&gt;

&lt;p&gt;Decide your data structure now.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is a "Contact" a tuple? &lt;code&gt;("Alice", "555-1234")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Or a dictionary? &lt;code&gt;{"name": "Alice", "phone": "555-1234"}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choosing a &lt;strong&gt;list of dictionaries&lt;/strong&gt; now prevents painful refactors later.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. WHAT operations exist? (The Recipes)
&lt;/h2&gt;

&lt;p&gt;Now, listing the functions becomes easy. You just map the work to the "staff":&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;load_contacts()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;save_contacts()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Logic Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;add_contact()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;search_contacts()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UI Layer:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;display_menu()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_user_input()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The "Aha" Moment
&lt;/h2&gt;

&lt;p&gt;By answering these five questions, you didn't write code, but you just designed a full architecture.&lt;/p&gt;

&lt;p&gt;When you finally return to &lt;code&gt;main.py&lt;/code&gt;, the paralysis is gone. You aren't "building a contact book." You're just implementing &lt;code&gt;load_contacts()&lt;/code&gt;. Then &lt;code&gt;display_menu()&lt;/code&gt;. Small, manageable tasks.&lt;/p&gt;

&lt;p&gt;Stop staring at the cursor. Start asking questions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This framework is adapted from my upcoming book, &lt;strong&gt;Zero to AI Engineer: Python Foundations&lt;/strong&gt;, where we use it to design everything from CLI tools to AI pipelines.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>productivity</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Your Python Code Takes Hours Instead of Seconds (A 3-Line Fix)</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Mon, 12 Jan 2026 12:48:44 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/why-your-python-code-takes-hours-instead-of-seconds-a-3-line-fix-i0l</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/why-your-python-code-takes-hours-instead-of-seconds-a-3-line-fix-i0l</guid>
      <description>&lt;p&gt;Your code works. It passes every test. Then you run it on real data and go make coffee. Then lunch. Then you start questioning your career choices.&lt;/p&gt;

&lt;p&gt;Here's the pattern that's probably killing your performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This checks for duplicates by comparing every pair. Perfectly logical. Perfectly correct. Perfectly &lt;strong&gt;devastating&lt;/strong&gt; at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Math That Ruins Your Day
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;List Size&lt;/th&gt;
&lt;th&gt;Comparisons&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;4,950&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;499,500&lt;/td&gt;
&lt;td&gt;Instant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;49,995,000&lt;/td&gt;
&lt;td&gt;Noticeable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;~5 billion&lt;/td&gt;
&lt;td&gt;Many seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1,000,000&lt;/td&gt;
&lt;td&gt;~500 billion&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Hours&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That nested loop means comparisons grow with the &lt;em&gt;square&lt;/em&gt; of your data. Double the input, quadruple the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3-Line Fix
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;has_duplicates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This version is essentially &lt;strong&gt;instant&lt;/strong&gt; even for a million items.&lt;/p&gt;

&lt;p&gt;Why? Converting to a set is O(n) — you touch each element once. Checking length is O(1). No nested loops, no quadratic explosion.&lt;/p&gt;

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

&lt;p&gt;Whenever you see nested loops over the same collection, alarm bells should ring:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 🚨 O(n²) — danger zone
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;other_list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# This is a hidden loop!
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ O(n) — convert first
&lt;/span&gt;&lt;span class="n"&gt;other_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;other_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# O(1) lookup
&lt;/span&gt;        &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That innocent &lt;code&gt;if item in other_list&lt;/code&gt; is doing a linear scan each time. With a set, it's a hash lookup — constant time regardless of size.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real-World Impact
&lt;/h2&gt;

&lt;p&gt;This isn't academic. In AI engineering, you're constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deduplicating embeddings&lt;/li&gt;
&lt;li&gt;Checking if items exist in large collections&lt;/li&gt;
&lt;li&gt;Processing batches where nested loops sneak in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difference between O(n²) and O(n) is the difference between "works on my machine" and "works in production."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>performance</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python Sets: remove() vs discard() — When Silence Is Golden</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Sun, 11 Jan 2026 13:16:22 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/python-sets-remove-vs-discard-when-silence-is-golden-1j1k</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/python-sets-remove-vs-discard-when-silence-is-golden-1j1k</guid>
      <description>&lt;p&gt;Python sets have two methods for removing elements: &lt;code&gt;remove()&lt;/code&gt; and &lt;code&gt;discard()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They look identical:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Works
&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Also works
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But watch what happens when the element doesn't exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;red&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;blue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yellow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# KeyError: 'yellow'
&lt;/span&gt;&lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yellow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Nothing happens. No error.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;remove()&lt;/code&gt; raises an exception. &lt;code&gt;discard()&lt;/code&gt; fails silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Use Each
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;remove()&lt;/code&gt; when missing elements are bugs:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deactivate_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;active_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;active_users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Should exist!
&lt;/span&gt;    &lt;span class="c1"&gt;# If it doesn't, something's wrong upstream
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;user_id&lt;/code&gt; isn't in &lt;code&gt;active_users&lt;/code&gt;, that's a logic error in your code. You &lt;em&gt;want&lt;/em&gt; the exception to surface it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;discard()&lt;/code&gt; when missing elements are expected:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# May already be gone
&lt;/span&gt;    &lt;span class="c1"&gt;# User might have logged out twice, that's fine
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, the session might have already been cleaned up. No need to crash over it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI Engineering Context
&lt;/h2&gt;

&lt;p&gt;This pattern appears constantly in data pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Processing documents for RAG
&lt;/span&gt;&lt;span class="n"&gt;processed_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;document_stream&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;processed_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="n"&gt;processed_ids&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Cleanup: remove from pending queue
&lt;/span&gt;    &lt;span class="n"&gt;pending_queue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Might not be there
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;discard()&lt;/code&gt; here prevents crashes when the same document appears in multiple streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Framework
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Because&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Element &lt;em&gt;should&lt;/em&gt; exist&lt;/td&gt;
&lt;td&gt;&lt;code&gt;remove()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing = bug, surface it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Element &lt;em&gt;might&lt;/em&gt; exist&lt;/td&gt;
&lt;td&gt;&lt;code&gt;discard()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Missing = expected, ignore it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Idempotent operations&lt;/td&gt;
&lt;td&gt;&lt;code&gt;discard()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Safe to call multiple times&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Strict state management&lt;/td&gt;
&lt;td&gt;&lt;code&gt;remove()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Catch invalid transitions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  One More Option
&lt;/h2&gt;

&lt;p&gt;If you want to know &lt;em&gt;whether&lt;/em&gt; removal happened without raising an error, combine &lt;code&gt;in&lt;/code&gt; with &lt;code&gt;discard()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;my_set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;my_set&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Removed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Wasn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t there&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Though this is rarely necessary—usually you either care (use &lt;code&gt;remove()&lt;/code&gt;) or you don't (use &lt;code&gt;discard()&lt;/code&gt; and move on).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which do you default to?&lt;/strong&gt; Do you prefer loud failures or silent forgiveness?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Python Dictionary Views Are Live (And It Might Break Your Code)</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Sat, 10 Jan 2026 19:16:04 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/python-dictionary-views-are-live-and-it-might-break-your-code-56in</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/python-dictionary-views-are-live-and-it-might-break-your-code-56in</guid>
      <description>&lt;p&gt;Quick quiz. What does this print?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you said:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dict_keys(['a', 'b'])
dict_keys(['a', 'b'])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're wrong.&lt;/p&gt;

&lt;p&gt;It prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dict_keys(['a', 'b'])
dict_keys(['a', 'b', 'c'])  # ← Updated automatically!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Dictionary Views Are Live
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;.keys()&lt;/code&gt;, &lt;code&gt;.values()&lt;/code&gt;, and &lt;code&gt;.items()&lt;/code&gt; don't return copies.&lt;/p&gt;

&lt;p&gt;They return &lt;strong&gt;view objects&lt;/strong&gt;—live windows into the dictionary that update automatically when the dictionary changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Alice&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
&lt;span class="nf"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NYC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Add a key
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;                       &lt;span class="c1"&gt;# View updated automatically
&lt;/span&gt;&lt;span class="nf"&gt;dict_keys&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;age&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;city&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is by design. Views are memory-efficient—they don't copy data.&lt;/p&gt;

&lt;p&gt;But it can cause bugs if you're not expecting it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bug This Causes
&lt;/h2&gt;

&lt;p&gt;Imagine you're iterating over dictionary keys while modifying the dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verbose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temp_flag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Remove all flags that are True
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# 💥 RuntimeError!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RuntimeError: dictionary changed size during iteration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view is live. When you delete a key, the view changes mid-iteration. Python catches this and throws an error.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;If you need to modify while iterating, convert to a list first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;debug&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;verbose&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temp_flag&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Convert to list - creates a snapshot
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# {'verbose': False}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;list(config.keys())&lt;/code&gt; creates a &lt;strong&gt;static copy&lt;/strong&gt; at that moment. Now you can modify the original dictionary freely.&lt;/p&gt;




&lt;h2&gt;
  
  
  When Live Views Are Useful
&lt;/h2&gt;

&lt;p&gt;Live views aren't just a gotcha—they're actually powerful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Monitor changes in real-time
&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;cache_keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# ... elsewhere in your code ...
&lt;/span&gt;&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_456&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;more_data&lt;/span&gt;

&lt;span class="c1"&gt;# cache_keys automatically reflects all additions
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cached: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cache_keys&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Always current
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to re-fetch &lt;code&gt;.keys()&lt;/code&gt; every time. The view stays synchronized.&lt;/p&gt;

&lt;p&gt;Did you know dictionary views were live? Has this ever caught you off guard?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Why Your Python Tuple Can't Be a Dictionary Key</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Fri, 09 Jan 2026 09:02:43 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/why-your-python-tuple-cant-be-a-dictionary-key-5307</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/why-your-python-tuple-cant-be-a-dictionary-key-5307</guid>
      <description>&lt;p&gt;"Tuples are immutable" is one of the first things you learn about Python tuples.&lt;/p&gt;

&lt;p&gt;But check this out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# Tuple of lists
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# Modify the list inside
&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;      &lt;span class="c1"&gt;# It changed!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait—didn't we say tuples can't be modified?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tuple itself didn't change.&lt;/strong&gt; It still contains exactly two references to the same two list objects. What changed is the &lt;em&gt;content&lt;/em&gt; of one of those list objects.&lt;/p&gt;

&lt;p&gt;Think of it like a display case with two fish tanks inside. You can't add or remove tanks from the case (the tuple is immutable), but the fish inside each tank can still swim around (the lists are mutable).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This has real consequences:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;Traceback &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;unhashable&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tuples containing mutable objects &lt;strong&gt;cannot be hashed&lt;/strong&gt;—which means they can't be dictionary keys or set members.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters for AI engineering:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you're working with embeddings, model outputs, or cached results, you might think "I'll use a tuple as a cache key." But if that tuple contains any mutable objects (lists, dicts, sets), you'll get a &lt;code&gt;TypeError&lt;/code&gt; at runtime.&lt;/p&gt;

&lt;p&gt;The fix: ensure all elements are also immutable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This works as a cache key
&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# This fails
&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input_ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# if input_ids is a list
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>computerscience</category>
      <category>ai</category>
    </item>
    <item>
      <title>You Know Python Basics—Now Let's Build Something Real</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Thu, 08 Jan 2026 13:56:26 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/you-know-python-basics-now-lets-build-something-real-1pco</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/you-know-python-basics-now-lets-build-something-real-1pco</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Who this is for:&lt;/strong&gt; You've completed a Python basics course or tutorial. You understand variables, loops, conditionals, and strings. But you haven't built anything real yet. This project is specifically for that stage.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You've learned variables, loops, conditionals, and string methods. Each concept made sense in isolation. But when you stared at a blank file and tried to build something... nothing came together.&lt;/p&gt;

&lt;p&gt;This is the gap between knowing syntax and &lt;em&gt;actually programming&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I just open-sourced a project specifically designed to bridge that gap: a text adventure game that combines all those foundational concepts into one playable program.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/samuel-ochaba-dev/zero-to-ai-engineer-projects/tree/main/dungeon-escape-text-adventure-game" rel="noopener noreferrer"&gt;github.com/samuel-ochaba-dev/zero-to-ai-engineer-projects&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What You'll Practice
&lt;/h2&gt;

&lt;p&gt;This isn't just another tutorial—it's a consolidation project. Every Python basic you've learned has a job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;How It's Used&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Variables &amp;amp; Data Types&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Game state, rooms, player info&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dictionaries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Nested data for the game world&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operators&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Health checks, item membership&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;String Methods&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.strip()&lt;/code&gt;, &lt;code&gt;.lower()&lt;/code&gt;, &lt;code&gt;.split()&lt;/code&gt;, &lt;code&gt;.join()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;User Input&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Interactive &lt;code&gt;input()&lt;/code&gt; game loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conditionals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;if-elif-else&lt;/code&gt; and &lt;code&gt;match/case&lt;/code&gt; for commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;While Loops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Main game loop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;For Loops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Iterating inventory with &lt;code&gt;enumerate()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type Hints&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Self-documenting function signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The point isn't to learn new syntax. The point is to &lt;em&gt;combine&lt;/em&gt; concepts you already know.&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Get the Most from This
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Don't just run the code.&lt;/strong&gt; That's the mistake most people make with learning projects.&lt;/p&gt;

&lt;p&gt;Instead:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Read the code without running it first
&lt;/h3&gt;

&lt;p&gt;Trace through the main loop. Predict what happens when you type &lt;code&gt;go north&lt;/code&gt; or &lt;code&gt;take torch&lt;/code&gt;. &lt;em&gt;Then&lt;/em&gt; run it to check your mental model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Break something intentionally
&lt;/h3&gt;

&lt;p&gt;Remove a line. Change a condition. See what error you get. This teaches you what each piece is actually doing.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Extend it
&lt;/h3&gt;

&lt;p&gt;The repo includes practice exercises:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a new room with items&lt;/li&gt;
&lt;li&gt;Implement a locked door puzzle (requires a key)&lt;/li&gt;
&lt;li&gt;Add a scoring system&lt;/li&gt;
&lt;li&gt;Create new random events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building on existing code is how real projects work.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pattern You'll Keep Using
&lt;/h2&gt;

&lt;p&gt;The main game loop looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;game_running&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Check win/lose conditions
&lt;/span&gt;    &lt;span class="c1"&gt;# Display current state
&lt;/span&gt;    &lt;span class="c1"&gt;# Get player input
&lt;/span&gt;    &lt;span class="c1"&gt;# Process command
&lt;/span&gt;    &lt;span class="c1"&gt;# Trigger random events
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern—a loop that reads input, processes it, updates state, and displays results—appears &lt;em&gt;everywhere&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI tools&lt;/li&gt;
&lt;li&gt;Chat applications&lt;/li&gt;
&lt;li&gt;AI chatbots (read user message → send to LLM → display response → repeat)&lt;/li&gt;
&lt;li&gt;Game engines&lt;/li&gt;
&lt;li&gt;REPLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you understand this pattern deeply, you'll recognize it in every interactive program you encounter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.10+&lt;/strong&gt; (required for &lt;code&gt;match/case&lt;/code&gt; syntax)&lt;/li&gt;
&lt;li&gt;No external dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/samuel-ochaba-dev/zero-to-ai-engineer-projects.git
&lt;span class="nb"&gt;cd &lt;/span&gt;zero-to-ai-engineer-projects/dungeon-escape-text-adventure-game
python3 adventure_game.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Sample Gameplay
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;========================================
       DUNGEON ESCAPE
========================================

You wake up in a dark dungeon.
Find the exit to escape!
Type 'help' for available commands.

You are in the Entrance Hall.
A dusty entrance with cobwebs covering the walls.
A faint light flickers from the north.

Exits: north, east
Items here: torch

Health: 100 | Inventory: empty

What do you do? &amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate through rooms, collect items, survive random events, and find the exit.&lt;/p&gt;




&lt;p&gt;The mental shift from "I know what a while loop is" to "I can use a while loop to build a game loop" is significant. This project is designed to make that shift concrete.&lt;/p&gt;

&lt;p&gt;Clone it. Break it. Extend it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>gamedev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Type Hints Make AI Code Generation Significantly Better</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Wed, 07 Jan 2026 07:54:47 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/type-hints-make-ai-code-generation-significantly-better-2286</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/type-hints-make-ai-code-generation-significantly-better-2286</guid>
      <description>&lt;p&gt;If you're using AI coding assistants without type hints, you're leaving performance on the table.&lt;/p&gt;

&lt;p&gt;When you ask an AI to complete this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: split by comma and return uppercase words
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI has to guess what &lt;code&gt;data&lt;/code&gt; is. A string? A file? A list?&lt;/p&gt;

&lt;p&gt;But with type hints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: split by comma and return uppercase words
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the AI knows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;data&lt;/code&gt; is definitely a string&lt;/li&gt;
&lt;li&gt;It should return a list of strings&lt;/li&gt;
&lt;li&gt;Methods like &lt;code&gt;.split()&lt;/code&gt; and &lt;code&gt;.upper()&lt;/code&gt; are appropriate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The result:&lt;/strong&gt; More accurate completions, fewer hallucinations, less back-and-forth.&lt;/p&gt;

&lt;p&gt;This extends to entire codebases. When your functions have type hints, AI tools can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate code that matches your existing types&lt;/li&gt;
&lt;li&gt;Suggest appropriate methods for the given types&lt;/li&gt;
&lt;li&gt;Catch inconsistencies in their own output&lt;/li&gt;
&lt;li&gt;Understand relationships between modules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where this matters most:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API handlers (FastAPI uses type hints for automatic validation)&lt;/li&gt;
&lt;li&gt;Data processing pipelines&lt;/li&gt;
&lt;li&gt;Any code that interfaces with AI-generated components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Type hints are documentation for both humans &lt;em&gt;and&lt;/em&gt; machines. In 2026, that dual purpose matters more than ever.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>typing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Python Comprehensions Are Declarative (And Why That Matters)</title>
      <dc:creator>Samuel Ochaba</dc:creator>
      <pubDate>Tue, 06 Jan 2026 00:48:33 +0000</pubDate>
      <link>https://dev.to/samuel_ochaba_eb9c875fa89/python-comprehensions-are-declarative-and-why-that-matters-17ba</link>
      <guid>https://dev.to/samuel_ochaba_eb9c875fa89/python-comprehensions-are-declarative-and-why-that-matters-17ba</guid>
      <description>&lt;p&gt;Two ways to square every number in a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Version A
&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Version B
&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version A tells Python &lt;strong&gt;how&lt;/strong&gt; to build the list:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create empty list&lt;/li&gt;
&lt;li&gt;Loop through numbers&lt;/li&gt;
&lt;li&gt;Compute square&lt;/li&gt;
&lt;li&gt;Append to list&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Version B tells Python &lt;strong&gt;what&lt;/strong&gt; you want:&lt;br&gt;
"A list of squared numbers."&lt;/p&gt;

&lt;p&gt;This is the difference between &lt;strong&gt;imperative&lt;/strong&gt; and &lt;strong&gt;declarative&lt;/strong&gt; code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Declarative Wins
&lt;/h2&gt;

&lt;p&gt;Consider reading code you didn't write. With Version A, you have to trace the loop logic to understand the result. With Version B, the intent is immediate.&lt;/p&gt;

&lt;p&gt;More importantly, declarative code is &lt;strong&gt;harder to mess up&lt;/strong&gt;. In Version A, you could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Forget to initialize &lt;code&gt;squares = []&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Misspell &lt;code&gt;.append()&lt;/code&gt; as &lt;code&gt;.appnd()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Accidentally append the wrong value&lt;/li&gt;
&lt;li&gt;Break the loop prematurely&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Version B eliminates these failure modes. The pattern is baked in.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Newspaper Test
&lt;/h2&gt;

&lt;p&gt;Ask yourself: "Can I understand this comprehension in one quick read?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passes:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Fails:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have to stop and puzzle it out, the comprehension is too complex. Use a loop instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Loops Are Better
&lt;/h2&gt;

&lt;p&gt;Comprehensions aren't always right. Use loops when:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multiple statements per item:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Side effects:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# DON'T do this - creates useless list of Nones
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# DO this
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Complex control flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Mindset Shift
&lt;/h2&gt;

&lt;p&gt;Think of comprehensions as &lt;strong&gt;expressions that produce collections&lt;/strong&gt;, not loops that build them.&lt;/p&gt;

&lt;p&gt;When you write &lt;code&gt;[f(x) for x in items]&lt;/code&gt;, you're saying: "This IS a list of f(x) values." Not "This becomes a list through these steps."&lt;/p&gt;

&lt;p&gt;Once you internalize this, you'll reach for comprehensions naturally—and know when a loop serves you better.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is adapted from my upcoming book, Zero to AI Engineer: Python Foundations.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;I share excerpts like this on Substack → &lt;a href="https://substack.com/@samuelochaba" rel="noopener noreferrer"&gt;https://substack.com/@samuelochaba&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>cleancode</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
