<?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: Javi Palacios</title>
    <description>The latest articles on DEV Community by Javi Palacios (@fj_palacios).</description>
    <link>https://dev.to/fj_palacios</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%2F3958394%2F53300b47-6c71-4239-a646-2a8cc4b00d1e.jpeg</url>
      <title>DEV Community: Javi Palacios</title>
      <link>https://dev.to/fj_palacios</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fj_palacios"/>
    <language>en</language>
    <item>
      <title>while loops in Python: repetition with a condition</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 15 Jul 2026 08:05:48 +0000</pubDate>
      <link>https://dev.to/fj_palacios/while-loops-in-python-repetition-with-a-condition-2ke1</link>
      <guid>https://dev.to/fj_palacios/while-loops-in-python-repetition-with-a-condition-2ke1</guid>
      <description>&lt;p&gt;Picture this: you ask the user to type a number between 1 and 10. You write the &lt;code&gt;input&lt;/code&gt;, check with an &lt;code&gt;if&lt;/code&gt; whether it's in range, and if it isn't... what do you do? Copy and paste the &lt;code&gt;input&lt;/code&gt; five times and hope they get it right by the third try? No. What you need is a loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loops&lt;/strong&gt; are the solution when you need to repeat something without knowing exactly how many times. They're not the last tool you'll learn, but they're one of the most important. And in Python, the most fundamental one is &lt;code&gt;while&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a while loop?
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;while&lt;/code&gt; loop executes a block of code &lt;strong&gt;as long as&lt;/strong&gt; a condition is true. When the condition becomes false, the loop stops. If the condition was already false from the start, the block never runs at all.&lt;/p&gt;

&lt;p&gt;The structure is intentionally similar to &lt;code&gt;if&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;while&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# This code repeats as long as the condition is True
&lt;/span&gt;    &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same colon. Same mandatory indentation. The difference is that instead of running once, the block keeps cycling until the condition is no longer met.&lt;/p&gt;

&lt;p&gt;A concrete example: counting from 1 to 5.&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;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Print current count
&lt;/span&gt;    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;     &lt;span class="c1"&gt;# Increment — or we'll be here forever
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
3
4
5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every time Python reaches the end of the block, it goes back to the &lt;code&gt;while&lt;/code&gt; header and re-evaluates the condition. When &lt;code&gt;counter&lt;/code&gt; hits 6, &lt;code&gt;6 &amp;lt;= 5&lt;/code&gt; is &lt;code&gt;False&lt;/code&gt; and the loop ends.&lt;/p&gt;

&lt;p&gt;There are three components that almost always appear together in a &lt;code&gt;while&lt;/code&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initialization&lt;/strong&gt; — &lt;code&gt;counter = 1&lt;/code&gt; (before the loop)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Condition&lt;/strong&gt; — &lt;code&gt;counter &amp;lt;= 5&lt;/code&gt; (in the header)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Update&lt;/strong&gt; — &lt;code&gt;counter += 1&lt;/code&gt; (inside the loop)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Forget the third one and you have a problem. A pretty famous one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The infinite loop: it's not a myth
&lt;/h2&gt;

&lt;p&gt;If your &lt;code&gt;while&lt;/code&gt; condition never becomes &lt;code&gt;False&lt;/code&gt;, the loop doesn't stop. Ever. Your program stays stuck running that block forever — or until you kill it with &lt;code&gt;Ctrl+C&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="c1"&gt;# ⚠️ Infinite loop — don't run this unless you enjoy Ctrl+C
&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;1&lt;/span&gt;
&lt;span class="k"&gt;while&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;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="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# x keeps growing, condition never becomes False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeling a slight sense of dread? Good. That means you already understand why updating the control variable matters.&lt;/p&gt;

&lt;p&gt;Infinite loops aren't always accidental. Sometimes you use them on purpose — more on that in a moment. But when it's not intentional, they usually come from one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You forgot to update the control variable&lt;/li&gt;
&lt;li&gt;You're updating it in the wrong direction&lt;/li&gt;
&lt;li&gt;The exit condition is impossible to reach with your logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The quickest way out in the terminal is &lt;code&gt;Ctrl+C&lt;/code&gt;. And whenever your program seems frozen... there's probably a &lt;code&gt;while True&lt;/code&gt; hiding somewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  break: exit on your terms
&lt;/h2&gt;

&lt;p&gt;Sometimes you need to leave the loop before the condition goes false. That's what &lt;code&gt;break&lt;/code&gt; is for: it stops the &lt;code&gt;while&lt;/code&gt; immediately and jumps to whatever code comes after the loop.&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;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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;number&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;   &lt;span class="c1"&gt;# Exit loop immediately when we hit 6
&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;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;Loop ended&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
2
3
4
5
Loop ended
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;break&lt;/code&gt; really shines in the &lt;code&gt;while True&lt;/code&gt; pattern, where the loop is designed to run indefinitely and the exit condition lives inside the block:&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="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do you want to continue? (y/n) &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;   &lt;span class="c1"&gt;# The only way out
&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;Continuing...&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Program ended&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 pattern — &lt;code&gt;while True&lt;/code&gt; with an inner &lt;code&gt;break&lt;/code&gt; — is completely legitimate and very common for menus, games, or anything that keeps going until the user decides to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  continue: skip one iteration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;continue&lt;/code&gt; is &lt;code&gt;break&lt;/code&gt;'s smaller sibling. Instead of exiting the loop, it &lt;strong&gt;jumps back to the start of the next iteration&lt;/strong&gt; — re-evaluates the condition and picks up from there if it's still true.&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;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;number&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="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;   &lt;span class="c1"&gt;# Skip even numbers
&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;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1
3
5
7
9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;number&lt;/code&gt; is even, &lt;code&gt;continue&lt;/code&gt; sends Python straight back to the &lt;code&gt;while&lt;/code&gt; header without executing &lt;code&gt;print&lt;/code&gt;. Only the odd numbers make it through.&lt;/p&gt;

&lt;p&gt;A warning about &lt;code&gt;continue&lt;/code&gt;: make sure the variable update happens &lt;strong&gt;before&lt;/strong&gt; the &lt;code&gt;continue&lt;/code&gt;. Look at what happens when it doesn't:&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;# ⚠️ Infinite loop — number never increments when it's even
&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;number&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="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;   &lt;span class="c1"&gt;# Jumps back before number += 1... forever
&lt;/span&gt;    &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&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;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;number&lt;/code&gt; is 0 (even), &lt;code&gt;continue&lt;/code&gt; fires before &lt;code&gt;number += 1&lt;/code&gt; has a chance to run. Next iteration, &lt;code&gt;number&lt;/code&gt; is still 0. And the next. And the next. Instant infinite loop, even though it doesn't look like one at first glance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world use cases
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;while&lt;/code&gt; shines in situations where you don't know in advance how many repetitions you'll need. A few common patterns:&lt;/p&gt;

&lt;h3&gt;
  
  
  Validating user input
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter your age: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isdigit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;   &lt;span class="c1"&gt;# Valid input, exit loop
&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;Please enter a valid number.&lt;/span&gt;&lt;span class="sh"&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;Your age is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&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;The loop keeps asking until the user gives something valid. Doesn't matter how many attempts it takes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Counter with a business condition
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;correct_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python123&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter your password: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;correct_password&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;Access granted.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remaining&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;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;Wrong password. You have &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;remaining&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; attempts left.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;max_attempts&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;Too many failed attempts. Account locked.&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 the kind of logic behind any login form. The &lt;code&gt;while&lt;/code&gt; controls the attempts; the &lt;code&gt;break&lt;/code&gt; exits on success; the final &lt;code&gt;if&lt;/code&gt; handles total failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Guessing game
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="n"&gt;secret_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randint&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;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Random number between 1 and 100
&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;Guess the number between 1 and 100.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&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;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your guess: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&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;That&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s not a number.&lt;/span&gt;&lt;span class="sh"&gt;"&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;attempts&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;secret_number&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;Too low.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;guess&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;secret_number&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;Too high.&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Correct! You got it in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; attempts.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the &lt;code&gt;while True&lt;/code&gt; has no explicit exit condition in the header — the only way out is the &lt;code&gt;break&lt;/code&gt; inside the &lt;code&gt;else&lt;/code&gt;. Until the user guesses the number, the game keeps going.&lt;/p&gt;

&lt;h2&gt;
  
  
  while vs for: when to use each
&lt;/h2&gt;

&lt;p&gt;If you've used other languages, you're probably already wondering when to pick &lt;code&gt;while&lt;/code&gt; and when to pick &lt;code&gt;for&lt;/code&gt;. Spoiler: you'll learn &lt;code&gt;for&lt;/code&gt; loops in the next tutorial, and they're the preferred choice whenever you know how many iterations you want.&lt;/p&gt;

&lt;p&gt;The rule is straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;while&lt;/code&gt;&lt;/strong&gt;: when the stopping condition depends on something that can change unpredictably (user input, network data, game state...)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;for&lt;/code&gt;&lt;/strong&gt;: when you're iterating over a sequence of known length (a list, a range of numbers, a file...)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can phrase it as "repeat this N times" or "go through this list", use &lt;code&gt;for&lt;/code&gt;. If you're thinking "keep doing this until something happens", use &lt;code&gt;while&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;You now know how to make Python both make decisions and repeat code in a controlled way. The natural next step is the &lt;code&gt;for&lt;/code&gt; loop — more predictable, more expressive for most everyday iteration, and the one you'll reach for most often in real Python code. You'll also meet &lt;code&gt;range()&lt;/code&gt;, which turns numbers into iterable sequences without having to manage counters by hand.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Write a guessing game where the user has exactly 5 attempts to guess a number between 1 and 20. After each failed attempt, tell them whether their guess is too high or too low. When the 5 attempts are up without success, reveal the secret number. Bonus: track how many attempts they used when they win, and congratulate them with a different message depending on whether they got it in 1, 2–3, or 4–5 attempts.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Interactive containers and exec: get inside, run commands, get out</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 14 Jul 2026 12:02:36 +0000</pubDate>
      <link>https://dev.to/fj_palacios/interactive-containers-and-exec-get-inside-run-commands-get-out-19me</link>
      <guid>https://dev.to/fj_palacios/interactive-containers-and-exec-get-inside-run-commands-get-out-19me</guid>
      <description>&lt;p&gt;Up until now, you've been treating containers like vending machines: put in a coin, take the snack, walk away. That's fine for learning the basics, but the moment something breaks in a real environment — and it will — you're going to need to open the machine and look inside.&lt;/p&gt;

&lt;p&gt;That's what &lt;code&gt;docker exec&lt;/code&gt; and interactive mode are for. And once you're comfortable with them, you'll realize they're probably the commands you'll reach for most.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interactive mode: what &lt;code&gt;-it&lt;/code&gt; actually means
&lt;/h2&gt;

&lt;p&gt;When you see &lt;code&gt;docker run -it&lt;/code&gt;, those two letters aren't a typo or a habit. They're two separate flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-i&lt;/code&gt; (interactive): keeps &lt;code&gt;stdin&lt;/code&gt; open, even if you're not attached to it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-t&lt;/code&gt; (tty): allocates a pseudo-terminal so the session behaves like a real shell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On their own, neither does much. Together, they give you something that looks, feels, and behaves like a proper terminal session inside the container. Without them, the process either starts and immediately exits, or sits waiting for input you can't give it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# With -it: you get an interactive shell&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@7a2f1d3c9e4b:/#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You're in. That thing before &lt;code&gt;/#&lt;/code&gt; is the container ID. Now you can run anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@7a2f1d3c9e4b:/# &lt;span class="nb"&gt;ls&lt;/span&gt; /etc
root@7a2f1d3c9e4b:/# &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release
root@7a2f1d3c9e4b:/# apt-get update
root@7a2f1d3c9e4b:/# &lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you type &lt;code&gt;exit&lt;/code&gt;, the main process of the container — &lt;code&gt;bash&lt;/code&gt;, in this case — terminates. The container stops. It doesn't disappear, but it's no longer running.&lt;/p&gt;

&lt;p&gt;This is the key mental model: &lt;strong&gt;the container's lifecycle is tied to its main process&lt;/strong&gt;. When &lt;code&gt;bash&lt;/code&gt; exits, the container stops. This feels obvious in retrospect, but it trips people up the first few times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Detached mode: the &lt;code&gt;-d&lt;/code&gt; you already know
&lt;/h2&gt;

&lt;p&gt;The opposite of interactive mode is detached mode (&lt;code&gt;-d&lt;/code&gt;). The container starts in the background and you get the control back immediately. Perfect for services that are supposed to run indefinitely: web servers, databases, message queues.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Without -d: process stays in the foreground, locks your terminal&lt;/span&gt;
docker run nginx

&lt;span class="c"&gt;# With -d: starts in the background, returns control&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3f4e5d6c7b8a9e1f2d3c4b5a6e7f8d9c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That hash is the full container ID. Your terminal is free. The container is running, serving HTTP on its internal port 80 — though nobody outside Docker can reach it yet. That's what port mapping is for, and we'll get there next lesson.&lt;/p&gt;

&lt;p&gt;To verify it's running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;CONTAINER ID   IMAGE   COMMAND                  CREATED         STATUS        PORTS   NAMES
3f4e5d6c7b8a   nginx   "/docker-entrypoint.…"   3 seconds ago   Up 3 seconds   80/tcp  my-nginx
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;code&gt;docker exec&lt;/code&gt;: get inside a running container
&lt;/h2&gt;

&lt;p&gt;Here's the one you'll use most. &lt;code&gt;docker exec&lt;/code&gt; runs an additional command inside an already-running container, without stopping or restarting it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open a bash shell in the already-running my-nginx container&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-nginx bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@3f4e5d6c7b8a:/#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference from &lt;code&gt;docker run -it&lt;/code&gt; is worth making explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;docker run -it&lt;/code&gt; &lt;strong&gt;creates a new container&lt;/strong&gt; and drops you inside it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker exec -it&lt;/code&gt; &lt;strong&gt;enters an existing container&lt;/strong&gt; that's already running&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you &lt;code&gt;exit&lt;/code&gt; from a &lt;code&gt;docker exec&lt;/code&gt; session, the container keeps running. The main process — Nginx, in this case — hasn't been touched. You just closed the extra shell you'd opened on the side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common exec patterns
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inspect Nginx's config file&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-nginx &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/nginx/nginx.conf

&lt;span class="c"&gt;# Open a bash shell to poke around&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-nginx bash

&lt;span class="c"&gt;# Some containers don't have bash — sh is always there&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-alpine sh

&lt;span class="c"&gt;# Check the environment variables of the running process&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-nginx &lt;span class="nb"&gt;env&lt;/span&gt;

&lt;span class="c"&gt;# See what processes are running inside&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-nginx ps aux

&lt;span class="c"&gt;# Install something for a one-off debugging session&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-nginx bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"apt-get update &amp;amp;&amp;amp; apt-get install -y curl"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Anything you install inside a running container with &lt;code&gt;exec&lt;/code&gt; disappears when the container is removed. If you need something to be there permanently, it goes in the Dockerfile. &lt;code&gt;exec&lt;/code&gt; is for inspection and debugging only.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;exec&lt;/code&gt; without &lt;code&gt;-it&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;You don't always need the interactive flags. If you just want to run a command and see the output, skip them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run a command and see the output directly&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-nginx nginx &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;-it&lt;/code&gt;, the command runs, prints its output, and exits. No interactive shell, no pseudo-terminal. This is the form you'd use in scripts or CI pipelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Alpine trap: bash vs sh
&lt;/h2&gt;

&lt;p&gt;This one will catch you off guard eventually. Not all images have &lt;code&gt;bash&lt;/code&gt;. Alpine-based images — which are wildly popular because of their tiny size — ship with &lt;code&gt;sh&lt;/code&gt; only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ This fails on an Alpine image&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-alpine-container bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OCI runtime exec failed: exec: "bash": executable file not found in the PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Use sh instead&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-alpine-container sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you genuinely need &lt;code&gt;bash&lt;/code&gt; in Alpine for something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-alpine-container sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"apk add bash &amp;amp;&amp;amp; bash"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Honestly though, &lt;code&gt;sh&lt;/code&gt; covers 99% of what you'll need during an inspection session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Copying files with &lt;code&gt;docker cp&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't need to enter the container — you just need to get a file out (or put one in). &lt;code&gt;docker cp&lt;/code&gt; copies files between the host filesystem and a container's filesystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  From container to host
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy Nginx's config file out of the container to your local machine&lt;/span&gt;
docker &lt;span class="nb"&gt;cp &lt;/span&gt;my-nginx:/etc/nginx/nginx.conf ./nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful for: grabbing a config you want to inspect and edit locally, pulling out logs generated inside the container, extracting build artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  From host to container
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Copy a local file into the running container&lt;/span&gt;
docker &lt;span class="nb"&gt;cp&lt;/span&gt; ./my-config.conf my-nginx:/etc/nginx/conf.d/custom.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then reload without restarting the container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-nginx nginx &lt;span class="nt"&gt;-s&lt;/span&gt; reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker cp&lt;/code&gt; vs volumes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;docker cp&lt;/code&gt; is a one-off tool — great for debugging, for pulling a file out of a live container, for quick one-time injections. For ongoing file sync between host and container (like your application source code during development), you want &lt;strong&gt;volumes&lt;/strong&gt;. Volumes are the right mechanism for that, and we'll cover them later in the course.&lt;/p&gt;

&lt;p&gt;Think of &lt;code&gt;docker cp&lt;/code&gt; as the Swiss Army knife. Volumes are the power tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing what's happening: logs and inspect
&lt;/h2&gt;

&lt;p&gt;Two more commands that pair naturally with &lt;code&gt;exec&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;docker logs&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# See all logs from the container&lt;/span&gt;
docker logs my-nginx

&lt;span class="c"&gt;# Follow logs in real time (like tail -f)&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; my-nginx

&lt;span class="c"&gt;# Show only the last 50 lines&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 50 my-nginx

&lt;span class="c"&gt;# Add timestamps&lt;/span&gt;
docker logs &lt;span class="nt"&gt;--timestamps&lt;/span&gt; my-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;docker inspect&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;logs&lt;/code&gt; tells you what's coming out, &lt;code&gt;inspect&lt;/code&gt; tells you everything about how the container is configured: environment variables, port mappings, mounted volumes, the network it's connected to, the image it came from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect my-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It returns a large JSON blob. Use &lt;code&gt;jq&lt;/code&gt; to extract what you need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check environment variables&lt;/span&gt;
docker inspect my-nginx | jq &lt;span class="s1"&gt;'.[0].Config.Env'&lt;/span&gt;

&lt;span class="c"&gt;# Check the image ID&lt;/span&gt;
docker inspect my-nginx | jq &lt;span class="s1"&gt;'.[0].Image'&lt;/span&gt;

&lt;span class="c"&gt;# Check the container's current state&lt;/span&gt;
docker inspect my-nginx | jq &lt;span class="s1"&gt;'.[0].State'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A typical debugging workflow
&lt;/h2&gt;

&lt;p&gt;Here's what all of this looks like in practice when something's broken:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Container is running but something's wrong&lt;/span&gt;
docker ps
docker logs &lt;span class="nt"&gt;--tail&lt;/span&gt; 100 &lt;span class="nt"&gt;-f&lt;/span&gt; my-app

&lt;span class="c"&gt;# 2. Logs aren't enough — enter the container to inspect&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; my-app bash

&lt;span class="c"&gt;# 3. Inside the container, investigate&lt;/span&gt;
root@abc123:/# &lt;span class="nb"&gt;ls&lt;/span&gt; /var/log/
root@abc123:/# &lt;span class="nb"&gt;cat&lt;/span&gt; /var/log/app.log
root@abc123:/# &lt;span class="nb"&gt;env&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;DATABASE

&lt;span class="c"&gt;# 4. Found a config file worth looking at — exit and pull it out&lt;/span&gt;
root@abc123:/# &lt;span class="nb"&gt;exit
&lt;/span&gt;docker &lt;span class="nb"&gt;cp &lt;/span&gt;my-app:/app/config.json ./config.json

&lt;span class="c"&gt;# 5. Edit it locally, push it back in&lt;/span&gt;
docker &lt;span class="nb"&gt;cp&lt;/span&gt; ./config.json my-app:/app/config.json

&lt;span class="c"&gt;# 6. Reload the process without restarting the container (if the app supports it)&lt;/span&gt;
docker &lt;span class="nb"&gt;exec &lt;/span&gt;my-app &lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nt"&gt;-HUP&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what makes Docker genuinely useful for debugging: you can get in, look around, modify, and reload without taking the service down.&lt;/p&gt;




&lt;p&gt;With &lt;code&gt;docker exec&lt;/code&gt;, &lt;code&gt;-it&lt;/code&gt;, and &lt;code&gt;docker cp&lt;/code&gt; in your toolkit, you can actually interact with your containers rather than just launching them and hoping for the best. The ability to step inside a running container and see exactly what's happening is what separates someone who "uses Docker" from someone who understands it.&lt;/p&gt;

&lt;p&gt;Next up: &lt;strong&gt;port mapping&lt;/strong&gt; — how to make a service running inside a container accessible from your browser or from other processes outside Docker. This is where containers start feeling truly useful for building real applications.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Start an Nginx container in detached mode. Use &lt;code&gt;docker exec&lt;/code&gt; to open a bash shell inside it. Copy &lt;code&gt;/etc/nginx/nginx.conf&lt;/code&gt; to your machine with &lt;code&gt;docker cp&lt;/code&gt;. Change &lt;code&gt;worker_processes auto;&lt;/code&gt; to &lt;code&gt;worker_processes 2;&lt;/code&gt;, push the file back into the container, and reload with &lt;code&gt;docker exec my-nginx nginx -s reload&lt;/code&gt;. Verify the configuration is valid using &lt;code&gt;docker exec my-nginx nginx -t&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Cherry-picking commits in Git</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Mon, 13 Jul 2026 11:34:46 +0000</pubDate>
      <link>https://dev.to/fj_palacios/cherry-picking-commits-in-git-3i0h</link>
      <guid>https://dev.to/fj_palacios/cherry-picking-commits-in-git-3i0h</guid>
      <description>&lt;p&gt;Production is down. There's a null pointer exception in the payment service and your on-call rotation landed on a Tuesday. You know exactly where the fix is: that commit from three days ago on &lt;code&gt;feature/payments&lt;/code&gt;, the one called "Fix null pointer in payment service." The problem? That branch is a warzone — an unfinished refactor, a half-baked API that breaks half the existing code, and at least two commits named "wip" (there are always at least two commits named "wip"). Merging that branch into &lt;code&gt;main&lt;/code&gt; would be like performing surgery by dropping a bowling ball on the patient.&lt;/p&gt;

&lt;p&gt;You don't need the whole branch. You need that one commit.&lt;/p&gt;

&lt;p&gt;That's exactly what &lt;code&gt;git cherry-pick&lt;/code&gt; is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is cherry-pick?
&lt;/h2&gt;

&lt;p&gt;Think of your Git history as a playlist. Branches are playlists, commits are individual tracks. A merge is like saying "add this entire album to my playlist." A rebase is like moving your songs so they come after a different album. &lt;code&gt;git cherry-pick&lt;/code&gt; is you pulling a single track from someone else's playlist and adding it to yours — without subscribing to their whole discography.&lt;/p&gt;

&lt;p&gt;The mechanics: Git takes the diff introduced by that specific commit (what changed between it and its parent), and replays that diff on top of your current branch as a brand-new commit. Same content, different hash. The original commit stays exactly where it was. You just get a copy of the change.&lt;/p&gt;

&lt;p&gt;That's worth repeating: &lt;strong&gt;the new commit gets a new hash&lt;/strong&gt;. We'll come back to why that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic usage
&lt;/h2&gt;

&lt;p&gt;First, find the hash of the commit you need. Run &lt;code&gt;git log&lt;/code&gt; on the source branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log feature/payments &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;a3f9c12 Fix null pointer in payment service
b7e4d89 WIP: new payment gateway integration
c2a8f01 Refactor billing module (incomplete)
d6c3b47 Add payment model
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it is — &lt;code&gt;a3f9c12&lt;/code&gt;. Switch to your target branch and apply it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch main
git cherry-pick a3f9c12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[main 9b1d4e3] Fix null pointer in payment service
 Date: Mon Apr 14 18:32:01 2026 +0200
 1 file changed, 3 insertions(+), 1 deletion(-)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. The fix is in &lt;code&gt;main&lt;/code&gt;. The other fourteen commits are still sitting on &lt;code&gt;feature/payments&lt;/code&gt;, waiting to be ready. Production breathes again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cherry-picking multiple commits
&lt;/h2&gt;

&lt;p&gt;Sometimes one commit isn't enough. You have two options.&lt;/p&gt;

&lt;h3&gt;
  
  
  Individual commits
&lt;/h3&gt;

&lt;p&gt;Pass multiple hashes in the order you want them applied:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick a3f9c12 d6c3b47 e1f2a03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git applies them one by one, in that order. If any of them causes a conflict, it stops and waits for you to sort it out before continuing.&lt;/p&gt;

&lt;h3&gt;
  
  
  A range of commits
&lt;/h3&gt;

&lt;p&gt;If the commits you need are consecutive, use range syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick a3f9c12..e1f2a03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Watch out: this syntax is &lt;strong&gt;exclusive on the left&lt;/strong&gt;. &lt;code&gt;a3f9c12..e1f2a03&lt;/code&gt; applies every commit &lt;em&gt;after&lt;/em&gt; &lt;code&gt;a3f9c12&lt;/code&gt; up to and including &lt;code&gt;e1f2a03&lt;/code&gt;. If you also want &lt;code&gt;a3f9c12&lt;/code&gt; itself, use the &lt;code&gt;^&lt;/code&gt; suffix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick a3f9c12^..e1f2a03
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This catches everyone exactly once. File it under "Git syntax that looks like line noise but actually makes sense once you've been burned by it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful flags
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;-n&lt;/code&gt; / &lt;code&gt;--no-commit&lt;/code&gt;: stage without committing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;-n&lt;/span&gt; a3f9c12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git applies the changes to the staging area but doesn't create a commit. Useful when you want to combine changes from multiple commits into a single one, or when you want to inspect what's about to happen before pulling the trigger:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;-n&lt;/span&gt; a3f9c12 d6c3b47
git status              &lt;span class="c"&gt;# review staged changes&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Apply payment fixes from feature branch"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;-e&lt;/code&gt; / &lt;code&gt;--edit&lt;/code&gt;: edit the message before committing
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;-e&lt;/span&gt; a3f9c12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Opens your editor with the original commit message so you can modify it. Handy when the original said "fix" and you want something that actually describes what was fixed.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;-x&lt;/code&gt;: record where the commit came from
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;-x&lt;/span&gt; a3f9c12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Appends a line to the commit message pointing back to the original:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fix null pointer in payment service

(cherry picked from commit a3f9c12b3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Free traceability. Six months from now, when someone's auditing &lt;code&gt;release/1.x&lt;/code&gt; and wondering why a commit clearly born on a feature branch is sitting there, the message explains itself. In projects with multiple maintainers or multiple active release branches, &lt;code&gt;-x&lt;/code&gt; is effectively mandatory. Use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling conflicts
&lt;/h2&gt;

&lt;p&gt;Cherry-pick isn't always painless. If the changes you're bringing in touch the same lines that have changed in the target branch since the original commit was made, you'll get a conflict. Here's where things get fun (and by "fun" I mean mildly annoying, but entirely manageable).&lt;/p&gt;

&lt;p&gt;When Git hits a conflict mid-cherry-pick, it stops and tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;error: could not apply a3f9c12... Fix null pointer in payment service
hint: After resolving the conflicts, mark them with
&lt;/span&gt;&lt;span class="gp"&gt;hint: "git add/rm &amp;lt;pathspec&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;", then run
&lt;/span&gt;&lt;span class="go"&gt;hint: "git cherry-pick --continue".
hint: You can instead skip this commit with "git cherry-pick --skip".
hint: To abort and get back to the state before "git cherry-pick",
hint: run "git cherry-pick --abort".
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resolution flow is the same as merge or rebase conflicts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Open the conflicting files, resolve manually&lt;/span&gt;
&lt;span class="c"&gt;# (look for &amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; / ======= / &amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; markers and choose what stays)&lt;/span&gt;

&lt;span class="c"&gt;# 2. Mark the conflict as resolved&lt;/span&gt;
git add src/services/payment.ts

&lt;span class="c"&gt;# 3. Continue&lt;/span&gt;
git cherry-pick &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the commit you're trying to apply already exists in the target branch (maybe it was merged in earlier), Git might try to apply an empty diff. In that case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;--skip&lt;/span&gt;   &lt;span class="c"&gt;# skip this commit and continue with the rest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to back out entirely and pretend this never happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git cherry-pick &lt;span class="nt"&gt;--abort&lt;/span&gt;  &lt;span class="c"&gt;# back to square one, no harm done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't stress about conflicts — they're unavoidable when you're moving code between branches with diverged histories. Resolve them carefully, verify the result compiles and passes tests, then carry on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backporting a hotfix to release branches
&lt;/h3&gt;

&lt;p&gt;You maintain &lt;code&gt;v1.x&lt;/code&gt; and &lt;code&gt;v2.x&lt;/code&gt; in production. A security vulnerability surfaces and you fix it on &lt;code&gt;main&lt;/code&gt;. Both versions need the patch, but you absolutely don't want to push all of &lt;code&gt;main&lt;/code&gt; into either release branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fix is on main as commit f4e3d2c&lt;/span&gt;
git switch release/1.x
git cherry-pick &lt;span class="nt"&gt;-x&lt;/span&gt; f4e3d2c

git switch release/2.x
git cherry-pick &lt;span class="nt"&gt;-x&lt;/span&gt; f4e3d2c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-x&lt;/code&gt; flag leaves a paper trail in both branches. This is &lt;strong&gt;backporting&lt;/strong&gt; — carrying fixes backward in time without merging entire version lines. Cherry-pick is the standard tool for it, and it handles this use case extremely well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rescuing commits from a dead branch
&lt;/h3&gt;

&lt;p&gt;You've been on a refactoring branch for weeks. The scope ballooned, priorities shifted, and the branch isn't going anywhere. But it has two commits — utility functions, an HTTP client abstraction — that you genuinely want to keep. Those commits deserve better than dying with the branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log feature/big-refactor &lt;span class="nt"&gt;--oneline&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;7a9b1c2 Add generic retry helper
8b0c2d3 Extract HTTP client abstraction
c1d3e4f Refactor entire codebase (never mind)
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch main
git cherry-pick 7a9b1c2 8b0c2d3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch closes. The useful work survives. Everything else disappears without drama. If you're like me when I started, you probably assumed losing a branch meant losing everything in it — cherry-pick makes it easy to be selective about what's worth saving.&lt;/p&gt;

&lt;h2&gt;
  
  
  When NOT to use cherry-pick
&lt;/h2&gt;

&lt;p&gt;Cherry-pick is sharp, precise, and genuinely useful — which makes it tempting to reach for it in situations where it's the wrong tool. Using it wrong leaves you with a duplicate-commit-infested history, perpetual conflicts, and teammates giving you looks in standup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Don't use cherry-pick when you need a full merge or rebase.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If a feature branch is done and ready to integrate, use &lt;code&gt;git merge&lt;/code&gt; or &lt;code&gt;git rebase&lt;/code&gt;. Cherry-picking commit by commit is slow, error-prone, and destroys the natural traceability of your history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Pulling in an entire feature one commit at a time&lt;/span&gt;
git cherry-pick a1b2c3d e2f3a4b f3a4b5c g4b5c6d h5c6d7e

&lt;span class="c"&gt;# ✅ Integrating the feature properly&lt;/span&gt;
git merge feature/my-feature
&lt;span class="c"&gt;# or&lt;/span&gt;
git rebase feature/my-feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Don't use cherry-pick as a branch synchronization strategy.&lt;/strong&gt; If you find yourself cherry-picking the same commits across three different branches on a regular basis, you don't have a Git problem — you have a branching strategy problem. That's a different conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The duplicate hash problem.&lt;/strong&gt; When you cherry-pick a commit, Git creates a &lt;em&gt;new&lt;/em&gt; commit with the same content but a different hash. If you later merge the original branch, Git sees those changes as new and tries to apply them again — leading to conflicts or outright duplicates. The longer those branches coexist, the worse this gets. The rule is simple: use cherry-pick for specific, one-off situations (hotfixes, rescues, backports). Don't make it your routine synchronization mechanism.&lt;/p&gt;




&lt;p&gt;Cherry-pick is the scalpel of Git: precise, effective, and the wrong choice when what you actually need is a shovel. For hotfixes, backports, and rescuing useful work from abandoned branches, nothing beats it. For integrating complete features, merge and rebase do the job with far less collateral damage.&lt;/p&gt;

&lt;p&gt;In the next tutorial we'll look at &lt;code&gt;git reflog&lt;/code&gt; — Git's internal diary that records every movement of every reference, including ones you've deleted. If you've ever &lt;code&gt;reset --hard&lt;/code&gt; to the wrong commit, deleted a branch by accident, or stared at an empty working tree wondering what you just did, reflog is your safety net. We'll cover how to read it, how to navigate it, and how to recover work that feels permanently gone.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Interactive Rebase in Git: Edit Your History Like a Pro</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Sat, 11 Jul 2026 22:15:23 +0000</pubDate>
      <link>https://dev.to/fj_palacios/interactive-rebase-in-git-edit-your-history-like-a-pro-1h0i</link>
      <guid>https://dev.to/fj_palacios/interactive-rebase-in-git-edit-your-history-like-a-pro-1h0i</guid>
      <description>&lt;p&gt;You've been working on a feature for two days. Twelve commits. One says "fix", another "more fixes", one says "ok now it works", and three in a row say "wip". When you open the pull request, your history looks like a novelist's rough draft, not the work of a professional.&lt;/p&gt;

&lt;p&gt;You can clean that up before anyone sees it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git rebase -i&lt;/code&gt; — interactive rebase — gives you full control over your commits: you can reorder them, combine them, rename them, split them, or delete them entirely. All before they land in the shared history. It's like having editorial rights before publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is interactive rebase?
&lt;/h2&gt;

&lt;p&gt;Interactive rebase is &lt;code&gt;git rebase&lt;/code&gt; with the &lt;code&gt;-i&lt;/code&gt; flag (for &lt;em&gt;interactive&lt;/em&gt;). Instead of replaying commits automatically, Git opens an editor with a list of your commits and lets you decide what to do with each one.&lt;/p&gt;

&lt;p&gt;The most common invocation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; HEAD~n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;n&lt;/code&gt; is how many commits back you want to review. To edit the last five:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; HEAD~5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git will open your configured editor (vim, nano, VS Code, whatever you have) with something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;b2c3d4e&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;c3d4e5f&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;e5f6a7b&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;works&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;g7b8c9d&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="n"&gt;typo&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;h8c9d0e&lt;/span&gt; &lt;span class="n"&gt;tests&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;i9d0e1f&lt;/span&gt; &lt;span class="n"&gt;more&lt;/span&gt; &lt;span class="n"&gt;fixes&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;j0e1f2a&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;k1f2a3b&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;logout&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt;

&lt;span class="c"&gt;# Rebase 9f8e7d6..k1f2a3b onto 9f8e7d6 (11 commands)
#
# Commands:
# p, pick &amp;lt;commit&amp;gt; = use commit
# r, reword &amp;lt;commit&amp;gt; = use commit, but edit the commit message
# e, edit &amp;lt;commit&amp;gt; = use commit, but stop for amending
# s, squash &amp;lt;commit&amp;gt; = use commit, and meld into previous commit
# f, fixup &amp;lt;commit&amp;gt; = like "squash" but keep only the previous commit's log message
# x, exec &amp;lt;command&amp;gt; = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop &amp;lt;commit&amp;gt; = remove commit
# ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list shows commits &lt;strong&gt;oldest to newest&lt;/strong&gt; — the opposite of &lt;code&gt;git log&lt;/code&gt;. This trips everyone up the first few times. Yes, it's counterintuitive. No, there's nothing you can do about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The main operations
&lt;/h2&gt;

&lt;p&gt;Each line starts with an &lt;strong&gt;action&lt;/strong&gt; (defaulting to &lt;code&gt;pick&lt;/code&gt;) followed by the hash and commit message. You change the action in the editor, save, close, and Git does the work.&lt;/p&gt;

&lt;h3&gt;
  
  
  pick: keep as-is
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leave it alone. The commit stays exactly as it is. The default action.&lt;/p&gt;

&lt;h3&gt;
  
  
  reword: change only the message
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;reword&lt;/span&gt; &lt;span class="n"&gt;c3d4e5f&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git applies the commit but pauses to let you edit the message. The content doesn't change, only the text. Perfect for those "fix" or "wip" messages that scream "I had no idea what I was doing" in production history.&lt;/p&gt;

&lt;h3&gt;
  
  
  squash: combine with the previous commit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;squash&lt;/span&gt; &lt;span class="n"&gt;b2c3d4e&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git combines &lt;code&gt;b2c3d4e&lt;/code&gt; into &lt;code&gt;a1b2c3d&lt;/code&gt; and opens an editor for you to write the final commit message. You can keep both messages, discard one, or write a new one from scratch.&lt;/p&gt;

&lt;h3&gt;
  
  
  fixup: combine and discard the message
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;fixup&lt;/span&gt; &lt;span class="n"&gt;c3d4e5f&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;span class="n"&gt;fixup&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like &lt;code&gt;squash&lt;/code&gt;, but automatically discards the absorbed commit's message. It disappears without a trace, no editor, no questions asked. It's the mode for: "I want this change to exist, but I don't want anyone to know it took me three attempts."&lt;/p&gt;

&lt;h3&gt;
  
  
  drop: delete the commit
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;drop&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The commit and all its changes vanish. Use with care — the code it contains is gone. If in doubt, use &lt;code&gt;squash&lt;/code&gt; or &lt;code&gt;fixup&lt;/code&gt; instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  edit: stop to amend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="n"&gt;b2c3d4e&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git applies the commit and pauses so you can modify it: add forgotten files, change code, or even split it into multiple commits. When you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A complete example
&lt;/h2&gt;

&lt;p&gt;Start with this disaster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;b2c3d4e&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;c3d4e5f&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;e5f6a7b&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="n"&gt;typo&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Combine &lt;code&gt;b2c3d4e&lt;/code&gt; (fix) into &lt;code&gt;a1b2c3d&lt;/code&gt; (login form)&lt;/li&gt;
&lt;li&gt;Combine &lt;code&gt;c3d4e5f&lt;/code&gt; (wip) into the login form as well&lt;/li&gt;
&lt;li&gt;Give &lt;code&gt;e5f6a7b&lt;/code&gt; (fix typo) a proper message&lt;/li&gt;
&lt;li&gt;Leave the rest alone&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edit the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;fixup&lt;/span&gt; &lt;span class="n"&gt;b2c3d4e&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt;
&lt;span class="n"&gt;fixup&lt;/span&gt; &lt;span class="n"&gt;c3d4e5f&lt;/span&gt; &lt;span class="n"&gt;wip&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;span class="n"&gt;reword&lt;/span&gt; &lt;span class="n"&gt;e5f6a7b&lt;/span&gt; &lt;span class="n"&gt;fix&lt;/span&gt; &lt;span class="n"&gt;typo&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save and close. Git:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Applies &lt;code&gt;a1b2c3d&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Silently absorbs &lt;code&gt;b2c3d4e&lt;/code&gt; and &lt;code&gt;c3d4e5f&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Applies &lt;code&gt;d4e5f6a&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Applies &lt;code&gt;e5f6a7b&lt;/code&gt; and opens the editor — you write "Fix email validation regex"&lt;/li&gt;
&lt;li&gt;Applies &lt;code&gt;f6a7b8c&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;* &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
* &lt;span class="n"&gt;e5f6a7b&lt;/span&gt; &lt;span class="n"&gt;Fix&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;
* &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
* &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four clean commits where there were six. The history now tells &lt;em&gt;what&lt;/em&gt; you did, not &lt;em&gt;how many tries it took&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reordering commits
&lt;/h2&gt;

&lt;p&gt;Interactive rebase also lets you change the order. Just move the lines in the editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# Original
&lt;/span&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;

&lt;span class="c"&gt;# Reordered: password validation before the form
&lt;/span&gt;&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;d4e5f6a&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="n"&gt;validation&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;a1b2c3d&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
&lt;span class="n"&gt;pick&lt;/span&gt; &lt;span class="n"&gt;f6a7b8c&lt;/span&gt; &lt;span class="n"&gt;Add&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="n"&gt;me&lt;/span&gt; &lt;span class="n"&gt;option&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that if commits depend on each other (the second one modifies a file the first one creates), reordering them can cause conflicts. Git will let you know if that happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Splitting a commit into multiple
&lt;/h2&gt;

&lt;p&gt;Sometimes the opposite problem: a monolithic commit that mixes too many things. The &lt;code&gt;edit&lt;/code&gt; mode lets you break it apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; HEAD~3
&lt;span class="c"&gt;# Mark the commit you want to split as: edit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When Git stops at that commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Undo the commit but keep the changes in the working directory&lt;/span&gt;
git reset HEAD^

&lt;span class="c"&gt;# Now commit in logical chunks&lt;/span&gt;
git add src/auth/login.ts
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add login form component"&lt;/span&gt;

git add src/auth/validation.ts
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add email and password validation"&lt;/span&gt;

&lt;span class="c"&gt;# Continue the rebase&lt;/span&gt;
git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where there was one "Add auth stuff" commit, there are now two specific, focused commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autosquash: automating the cleanup
&lt;/h2&gt;

&lt;p&gt;If you already know a commit is going to be a fix for a previous one, you can create it with a special format from the start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a commit that will be auto-squashed onto abc123&lt;/span&gt;
git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; abc123

&lt;span class="c"&gt;# Or onto the last commit&lt;/span&gt;
git commit &lt;span class="nt"&gt;--fixup&lt;/span&gt; HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a commit with the message &lt;code&gt;fixup! Add login form&lt;/code&gt;. When you later run interactive rebase with &lt;code&gt;--autosquash&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;--autosquash&lt;/span&gt; HEAD~5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git automatically moves the &lt;code&gt;fixup!&lt;/code&gt; commits next to their target and assigns them the &lt;code&gt;fixup&lt;/code&gt; action. You just confirm the order and close the editor.&lt;/p&gt;

&lt;p&gt;It's the most efficient workflow if you have the discipline to use it from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use interactive rebase
&lt;/h2&gt;

&lt;p&gt;Interactive rebase is your pre-PR tool. The question to ask yourself: &lt;em&gt;does this history help anyone who comes after me?&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you have "wip", "fix", "temp", "asdf" commits? → &lt;code&gt;fixup&lt;/code&gt; or &lt;code&gt;squash&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Are your commit messages vague or misleading? → &lt;code&gt;reword&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Does one commit mix unrelated things? → &lt;code&gt;edit&lt;/code&gt; + &lt;code&gt;reset&lt;/code&gt; + re-commit in pieces&lt;/li&gt;
&lt;li&gt;Are commits in the wrong logical order? → reorder the lines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you &lt;strong&gt;don't&lt;/strong&gt; want to do is use interactive rebase on commits you've already pushed to a shared branch. The golden rule of rebase applies here the same as anywhere: branches that are only yours, yes; branches others have cloned, never.&lt;/p&gt;




&lt;p&gt;Interactive rebase is one of those tools that feels intimidating at first — you open the editor, see a list of cryptic commands, and think "I'm going to break something here." But with a bit of practice it becomes a habit. A clean history isn't vanity: it's documentation. It's the context someone is going to need six months from now to understand why the code is the way it is.&lt;/p&gt;

&lt;p&gt;In the next lesson we'll look at &lt;strong&gt;cherry-pick&lt;/strong&gt;: how to take a specific commit from any branch and apply it to another without merging everything. It's the perfect tool for when you have a critical fix buried in a feature branch and need to get it to production now.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>AI for Programming: The Paradigm Shift</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Sat, 11 Jul 2026 12:47:02 +0000</pubDate>
      <link>https://dev.to/fj_palacios/ai-for-programming-the-paradigm-shift-c79</link>
      <guid>https://dev.to/fj_palacios/ai-for-programming-the-paradigm-shift-c79</guid>
      <description>&lt;p&gt;Picture this: it's 2019. You're a mid-level developer. You spend your mornings writing boilerplate, your afternoons debugging stack traces, and your evenings on Stack Overflow trying to remember the exact syntax for a thing you've done a dozen times before. That was the job. Nobody thought it was weird — it was just how programming worked.&lt;/p&gt;

&lt;p&gt;Now it's 2026. The boilerplate is gone in thirty seconds. Stack traces get explained in plain English. That CRUD endpoint you used to spend a morning on? Twenty minutes with good context and a decent prompt. The job hasn't disappeared — it's been compressed. And that compression creates a gap between people who adapted and people who are still typing like it's 2019.&lt;/p&gt;

&lt;p&gt;This tutorial is about that gap. Not the tools — we'll get to those. The mental model. Because without the right mental model, even the best tools become a crutch instead of a multiplier.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old way vs the new way
&lt;/h2&gt;

&lt;p&gt;Let's be concrete about what changed, because "AI changes everything" is meaningless without examples.&lt;/p&gt;

&lt;p&gt;The 2019 workflow, if you were building a REST endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Recall the framework syntax (or search for it)
2. Type the boilerplate — route definition, middleware, validation
3. Write the business logic
4. Write the error handling
5. Write the tests (if you wrote tests)
6. Document it (if you documented)
7. Realize you missed an edge case, loop back to 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 2026 workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Describe the intent clearly to the AI
2. Review what it generates — actually understand it
3. Test it against your real requirements
4. Adjust and verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bottleneck shifted. Before, it was typing and recall. Now it's &lt;strong&gt;clarity of specification&lt;/strong&gt;. If you can't explain what you want precisely enough for another intelligent entity to implement it, the AI won't save you — it'll generate something plausible-looking that solves the wrong problem.&lt;/p&gt;

&lt;p&gt;That's not a limitation of the AI. That's the job now.&lt;/p&gt;

&lt;h2&gt;
  
  
  You're becoming an engineering director
&lt;/h2&gt;

&lt;p&gt;Here's the analogy that makes this click for most people.&lt;/p&gt;

&lt;p&gt;Imagine you've just been promoted. You used to write code all day. Now you have a team of developers — smart, fast, eager to help — and your job is to direct them effectively. You still need to understand the code deeply: you review it, you spot problems, you make architectural decisions. But you're not the one typing every line.&lt;/p&gt;

&lt;p&gt;Working with AI is exactly that dynamic. The AI is the developer. You're the director.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Engineering director without AI:
→ "Why is the auth failing?" → looks through logs → reads code → finds the bug → fixes it

Engineering director with AI:
→ "Why is the auth failing?" → shows logs + relevant code to AI → AI explains root cause → director evaluates → directs the fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What doesn't change: you still need to understand what the bug is. You still need to evaluate whether the fix makes sense. You still own the decision. The AI accelerates the mechanical parts — reading through 300 lines to find the one that matters, generating the corrected version, suggesting where to add a test.&lt;/p&gt;

&lt;p&gt;What changes: the ratio of time spent thinking versus time spent typing shifts dramatically in favor of thinking. And thinking clearly about problems turns out to be the part nobody automated.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new bottleneck: specification
&lt;/h2&gt;

&lt;p&gt;There's a concept in software engineering called &lt;strong&gt;Garbage In, Garbage Out&lt;/strong&gt;. It was coined for databases, but it describes AI interaction perfectly.&lt;/p&gt;

&lt;p&gt;An AI is not a mind reader. It doesn't know your codebase, your team's conventions, your performance requirements, or that you have a legacy service that uses a slightly non-standard error format. It only knows what you put in the context window.&lt;/p&gt;

&lt;p&gt;The quality of the output is bounded by the quality of the input. Always.&lt;/p&gt;

&lt;p&gt;Watch how different these prompts are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Weak specification:
"Write a function to validate email addresses."

✅ Strong specification:
"Write a TypeScript function that validates email addresses. It should:
- Return a boolean
- Follow RFC 5322 (standard format, no exotic edge cases)
- Reject addresses over 254 characters (per spec)
- Reject addresses with consecutive dots in the local part
- NOT use external libraries — just regex and length check

We're on Node.js 22, TypeScript 5.4 strict mode.
The function will be called thousands of times per second in a hot path,
so avoid any allocations we don't need."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first prompt gets you a function. The second gets you the function you actually need. The difference isn't the AI's capability — it's your ability to say what you want.&lt;/p&gt;

&lt;p&gt;This is the skill that compounds. The developer who can write the second prompt consistently will extract ten times more value from AI than someone who writes the first one, regardless of which model they use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation without abdication
&lt;/h2&gt;

&lt;p&gt;Here's where a lot of developers stumble: they treat AI as either a magic oracle they trust blindly, or a toy they use for trivial things and never for anything real. Both are wrong.&lt;/p&gt;

&lt;p&gt;The right mental model is &lt;strong&gt;delegation with ownership&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When you delegate to a colleague, you don't stop being responsible for the outcome. You describe what you need, you check the work, you integrate it, you own it. Delegating doesn't mean disappearing from the loop.&lt;/p&gt;

&lt;p&gt;Same with AI. The rule that matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never merge AI code you don't understand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not "review it quickly." Not "it looks right." &lt;strong&gt;Understand it.&lt;/strong&gt; If you can't explain in plain English what a piece of code does and why it's correct, you haven't reviewed it — you've rubber-stamped it. And when it breaks in production at 3 AM, "the AI wrote it" is not a defense.&lt;/p&gt;

&lt;p&gt;This is especially important for developers early in their career. The paradox is brutal: AI helps the most with the mechanical parts of coding, which is exactly the part that builds foundational understanding when you're learning. If you skip the understanding because "the AI already did it," you build speed without foundations. That works until it doesn't, and when it stops working, it stops working hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's worth delegating (and what isn't)
&lt;/h2&gt;

&lt;p&gt;Not all tasks are equal. Here's the practical breakdown after two years of industry data:&lt;/p&gt;

&lt;h3&gt;
  
  
  High-value delegation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate and scaffolding&lt;/strong&gt;: This is the clearest win. Setting up a new service, creating data transfer objects, writing CRUD endpoints, configuring test infrastructure — tasks that are repetitive and well-defined. AI does these well because there's a massive amount of training data showing exactly how they should look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Explaining unfamiliar code&lt;/strong&gt;: You join a new project. There's a 200-line function with no comments, written two years ago, by someone who left the company. Give it to the AI: "explain what this function does, what it assumes about its inputs, and what could go wrong." You'll understand it in two minutes instead of twenty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Writing tests&lt;/strong&gt;: Counterintuitive for many people, but AI is genuinely good at this. Give it a function, ask for edge cases, ask for tests that cover those cases. The output quality is high because tests have structure — arrange, act, assert — and that structure is exactly the kind of pattern AI learns well. You still verify that the tests make sense, but the mechanical part of writing them is delegated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Explaining what code does in prose is exactly what a next-word prediction machine optimized on human writing should be good at. It is.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debugging stack traces&lt;/strong&gt;: "Here's the error, here's the relevant code, here's the context." This is the exact kind of "given all this information, predict what's wrong" task where large context windows shine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Low-value or risky delegation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Security-critical code&lt;/strong&gt;: Authentication logic, authorization checks, cryptographic operations, input sanitization against injection attacks. The AI knows the patterns, but the stakes of getting them wrong are severe enough that you need deep personal understanding of every line. Use AI as a starting point or reviewer, never as the sole author.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Complex algorithms&lt;/strong&gt;: Sorting, graph traversal, optimization problems — AI can produce code that looks correct and has subtle off-by-one errors. Correctness here requires proof, not confidence. Always verify independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain-specific business logic&lt;/strong&gt;: AI doesn't know that in your company, "active" users are those who logged in within 90 days, or that your pricing model has seven edge cases inherited from a 2017 migration. It will make assumptions. Some will be right. You need to catch the ones that aren't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Novel architecture decisions&lt;/strong&gt;: Should this be synchronous or async? One service or two? Event-driven or request-response? These decisions require understanding your specific system's constraints — load, latency requirements, team size, operational complexity. AI gives you options and trade-offs, which is valuable input. But it can't make the decision for you, because it doesn't know what you're optimizing for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The verification mindset
&lt;/h2&gt;

&lt;p&gt;Let's talk about the daily habit that separates effective AI users from dangerous ones.&lt;/p&gt;

&lt;p&gt;Every piece of AI-generated code goes through the same checklist before it enters your codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Run it — does it actually execute without errors?
2. Test it — does it handle the edge cases you care about?
3. Read it — do you understand every line? Could you rewrite it from scratch?
4. Question it — are there assumptions that might not hold?
5. Own it — can you defend this code in a code review?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 3 is where most people skip. "It works" and "I understand why it works" are different statements. Both matter. The first gets it past testing. The second gets it past production incidents.&lt;/p&gt;

&lt;p&gt;The habit sounds slow. In practice, it takes maybe two minutes for a typical function. And it saves you the debugging session that takes three hours, the production fix that wakes you up at 2 AM, and the six-month accumulation of code you're afraid to touch because nobody knows how it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on junior developers
&lt;/h2&gt;

&lt;p&gt;I want to be direct about something the hype glosses over.&lt;/p&gt;

&lt;p&gt;If you're early in your career, AI is both your biggest advantage and your biggest risk. The advantage: you can produce at a level above your experience, which opens doors that used to take years to open. The risk: you can ship code you don't understand, build a track record on borrowed foundations, and hit a wall when you need to debug something deep.&lt;/p&gt;

&lt;p&gt;The differentiator is the understanding habit. Use AI to go faster — absolutely. But make sure "faster" means "less time on the mechanical parts" and not "less time understanding." Your 10-years-from-now self will have the foundations or won't. The choice happens now, in how you use the tool.&lt;/p&gt;




&lt;p&gt;You now have the mental model. You know why specification matters, what's worth delegating, and how verification fits into the workflow. In the next tutorial, we get concrete about a specific failure mode: &lt;strong&gt;AI hallucinations — how to detect them, when to trust AI, and the practical toolkit for not getting burned&lt;/strong&gt;. Because knowing the theory of verification is one thing. Knowing what hallucinations look like in the wild is another.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>How LLMs Work (Without the Math)</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Fri, 10 Jul 2026 12:13:08 +0000</pubDate>
      <link>https://dev.to/fj_palacios/how-llms-work-without-the-math-71m</link>
      <guid>https://dev.to/fj_palacios/how-llms-work-without-the-math-71m</guid>
      <description>&lt;p&gt;In the previous tutorial, we said LLMs "predict text." And you probably thought: "okay, but that doesn't actually tell me anything useful." Fair. We need to go one level deeper — not down to the math level, but to the level of &lt;em&gt;what's actually happening&lt;/em&gt;, because that directly explains why AI sometimes invents functions that don't exist, why the same prompt can give different answers, and why giving it more context makes a massive difference.&lt;/p&gt;

&lt;p&gt;This tutorial is arguably the most important one in this module. Not because of what you'll learn to do, but because of the mental model you'll build. And that mental model, I promise, will save you hours of frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  The next-word prediction machine
&lt;/h2&gt;

&lt;p&gt;Here's the uncomfortable truth: an LLM doesn't "understand" anything. It has no opinions of its own, no lived experience, no intuition. What it has is something stranger and, in a way, more impressive: it's read absurd quantities of text and learned, statistically, what words tend to follow other words.&lt;/p&gt;

&lt;p&gt;The task during training was brutal in its simplicity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Training text:  "The cat sat on the..."
Objective:       Predict "roof"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multiply that by trillions of examples, over weeks, across thousands of GPUs. The result is a model that, given any fragment of text, can predict with surprising accuracy what comes next.&lt;/p&gt;

&lt;p&gt;And here's the magic nobody expected: &lt;strong&gt;predicting the next word well requires understanding an enormous amount about the world&lt;/strong&gt;. To predict that after "The president signed the..." comes something related to legislation or agreements, the model has to have learned what a president is, what they do, in what contexts they act. Not as an abstract concept — as a statistical pattern extracted from millions of texts.&lt;/p&gt;

&lt;p&gt;Is that "understanding"? Philosophers have been arguing about it for years. For us as developers, the practical answer is: it doesn't matter. What matters is that the emergent behavior is useful, and knowing its origin makes us use it better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The intern who has read all of Stack Overflow
&lt;/h2&gt;

&lt;p&gt;The most useful analogy I know for an LLM is this:&lt;/p&gt;

&lt;p&gt;Imagine an &lt;strong&gt;extraordinarily well-read intern&lt;/strong&gt;. They've read the complete documentation for Python, Node.js, Rust, and 50 other languages. They've processed millions of Stack Overflow posts. They've seen tens of thousands of GitHub repositories. They've read technical articles, blog posts, programming books. All of that is in there, compressed somehow.&lt;/p&gt;

&lt;p&gt;But there are things that intern simply cannot do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What they can do:
✅ Synthesize information from multiple sources quickly
✅ Write coherent, structured code
✅ Explain concepts at different levels of detail
✅ Generate tests, docs, and boilerplate at high speed
✅ Recognize patterns and suggest improvements

What they can't do:
❌ Look up new information (unless given tools to do so)
❌ Remember what you talked about yesterday
❌ Know with certainty whether what they're saying is correct
❌ Access your codebase (unless you show it to them)
❌ Know anything that happened after their training cutoff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last point — the training cutoff — matters. The model doesn't know what happened after they closed the data tap. If a library's API changed six months ago, the LLM might give you the old syntax with total confidence. It's not lying: it genuinely doesn't know.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI says nonsense with absolute confidence
&lt;/h2&gt;

&lt;p&gt;This is what's called &lt;strong&gt;hallucination&lt;/strong&gt;, and understanding why it happens is fundamental to not falling into the trap.&lt;/p&gt;

&lt;p&gt;The model generates text by predicting the next word. At no point does it have access to a mechanism that says "wait, is this actually true?" There's no query to a facts database. No verification module. There's statistical prediction, and that's it.&lt;/p&gt;

&lt;p&gt;So when you ask about a Python function that 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="c1"&gt;# Prompt: "How do I use python.utils.magic_sort()?"
# AI response:
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;python.utils&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;magic_sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_list&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stable&lt;/span&gt;&lt;span class="o"&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;# The stable parameter ensures equal elements maintain their relative order
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI didn't look up &lt;code&gt;magic_sort&lt;/code&gt; anywhere. It generated text that &lt;em&gt;sounds like&lt;/em&gt; the correct answer to that question, based on how answers about Python functions tend to look. The name, the parameters, the explanation — all of it has the right shape. It just doesn't exist.&lt;/p&gt;

&lt;p&gt;This isn't a bug they're going to fix. It's a direct consequence of how these models work. That's why verification isn't optional — it's part of the workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Warning signs you should learn to recognize
&lt;/h3&gt;

&lt;p&gt;Over time, you develop a nose for this. In the meantime, here are the most common signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Overly confident response&lt;/strong&gt; about a very specific or poorly-documented topic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Function names that "sound right"&lt;/strong&gt; but that you don't recognize&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Specific library versions&lt;/strong&gt; or release dates with no source&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code that has the right shape&lt;/strong&gt; but throws &lt;code&gt;AttributeError&lt;/code&gt; or &lt;code&gt;ModuleNotFoundError&lt;/code&gt; when you run it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explanations that shift&lt;/strong&gt; when you repeat the question with slight variations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The golden rule: treat AI output like code from a colleague who's very smart but works in a rush. Review before you trust, always.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context is your superpower
&lt;/h2&gt;

&lt;p&gt;Here's the variable you control the most and that makes the biggest difference: &lt;strong&gt;the context you give the model&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The LLM has no memory between sessions (unless you explicitly provide it). Every conversation starts from zero. The only thing it knows about you, your project, and your problem is what's inside the current context window.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;context window&lt;/strong&gt; is the amount of text the model can "see" at once. Claude Sonnet, for example, has a 200,000-token window — enough for a medium-sized complete codebase. This is enormous and relatively recent: two years ago, windows were 8,000 tokens and you had to manually manage what to include.&lt;/p&gt;

&lt;p&gt;What does this mean for you in practice?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Weak prompt:
"My code doesn't work, help me"

✅ Context-rich prompt:
"I have this Python function that should parse dates in ISO 8601 format,
but it fails when the string includes a timezone offset (e.g., 2026-03-07T10:30:00+01:00).
Here's the code: [code]
And here's the error: [error]
I'm using Python 3.12 with dateutil 2.9.0"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference in response quality is enormous. Not because the model got smarter — but because it has the information it needs to predict relevant answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Temperature: why the same question gives different answers
&lt;/h2&gt;

&lt;p&gt;If you've ever asked the same thing twice and gotten different answers, that's not a bug. It's by design.&lt;/p&gt;

&lt;p&gt;When the model predicts the next word, it doesn't always pick the most probable one. There's a parameter called &lt;strong&gt;temperature&lt;/strong&gt; that controls how much randomness gets injected into that choice. With high temperature (more creative), it might pick less probable words. With low temperature (more deterministic), it almost always picks the most probable one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Temperature 0.0  → very deterministic, consistent responses
Temperature 0.5  → balance between creativity and consistency
Temperature 1.0  → more variation, more creative, more erratic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For code, you generally want low temperature: you want the model to give you the most probable solution, not to experiment. For brainstorming or idea generation, a bit more temperature helps break out of familiar patterns.&lt;/p&gt;

&lt;p&gt;opencode manages this automatically based on task type, but it's good to know the concept exists — especially when you notice the AI being "more conservative" or "more creative" depending on context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Newer doesn't always mean better (for you)
&lt;/h2&gt;

&lt;p&gt;One last expectation-breaker: just because a new model came out doesn't mean you should immediately switch to it for everything.&lt;/p&gt;

&lt;p&gt;Models are optimized for different objectives. A very new model might be better at mathematical reasoning but worse at following complex instructions. Or it might have a smaller context window. Or it might be significantly slower or more expensive.&lt;/p&gt;

&lt;p&gt;Model choice should be pragmatic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full codebase analysis&lt;/td&gt;
&lt;td&gt;Large context window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repetitive code generation&lt;/td&gt;
&lt;td&gt;Speed and cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Architecture design&lt;/td&gt;
&lt;td&gt;Reasoning capability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick, simple answers&lt;/td&gt;
&lt;td&gt;Any lightweight model&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;opencode lets you specify the model per session. In practice, Claude Sonnet is an excellent starting point for most development tasks — a good balance of quality, speed, and cost. We'll go deeper on this when we look at the model landscape in tutorial 5.&lt;/p&gt;




&lt;p&gt;With this mental model in place, you can start working with AI more intelligently: you know why verification matters, you know why context is everything, and you know that hallucinations aren't dark magic — they're statistical prediction applied confidently. In the next tutorial, we go to the next level: &lt;strong&gt;the paradigm shift of going from writing code to directing an AI&lt;/strong&gt; — and why the ability to specify clearly becomes the most valuable skill you can develop.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Normal Mode: The Power of Vim</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:45:57 +0000</pubDate>
      <link>https://dev.to/fj_palacios/normal-mode-the-power-of-vim-1mh9</link>
      <guid>https://dev.to/fj_palacios/normal-mode-the-power-of-vim-1mh9</guid>
      <description>&lt;p&gt;Every new Vim user makes the same mistake: they treat Normal mode as an inconvenience — that awkward state you have to pass through to get to Insert mode, where the "real work" happens. They tap &lt;code&gt;i&lt;/code&gt;, type a word, tap &lt;code&gt;Esc&lt;/code&gt;, tap &lt;code&gt;i&lt;/code&gt; again, type another word. Normal mode is just the lobby.&lt;/p&gt;

&lt;p&gt;Here's the thing: Normal mode is not the lobby. It's the whole building.&lt;/p&gt;

&lt;p&gt;In VS Code or Sublime, you spend 80% of your time navigating: moving the cursor, selecting text, deleting words, jumping between lines. The keyboard handles maybe 20% of that; the mouse handles the rest. Vim flips this completely. Normal mode gives you a vocabulary of precise, composable movements and operations — and once you're fluent, you'll navigate and edit without ever lifting your hands from the keyboard.&lt;/p&gt;

&lt;p&gt;This tutorial is where Vim starts to click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moving faster than one character at a time
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;hjkl&lt;/code&gt; got you into the game. Now it's time to actually play.&lt;/p&gt;

&lt;h3&gt;
  
  
  Word movements
&lt;/h3&gt;

&lt;p&gt;Instead of pressing &lt;code&gt;l&lt;/code&gt; seventeen times to get to the word you want, Vim lets you jump by word:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;w&lt;/code&gt; — jump to the &lt;strong&gt;start&lt;/strong&gt; of the next word&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;b&lt;/code&gt; — jump &lt;strong&gt;back&lt;/strong&gt; to the start of the previous word&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;e&lt;/code&gt; — jump to the &lt;strong&gt;end&lt;/strong&gt; of the current (or next) word&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three form a triangle. &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are opposites; &lt;code&gt;e&lt;/code&gt; gets you to the last character of a word. In practice you'll use &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; constantly, and &lt;code&gt;e&lt;/code&gt; when you need to land precisely on the final character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The quick brown fox jumps over the lazy dog
^   ^     ^     ^   ^     ^    ^   ^    ^
                              w movements (one per press)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also the uppercase variants: &lt;code&gt;W&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;E&lt;/code&gt;. These do the same thing, but they define "word" differently — they skip over punctuation and treat &lt;code&gt;user_name&lt;/code&gt;, &lt;code&gt;foo.bar&lt;/code&gt;, and &lt;code&gt;http://example.com&lt;/code&gt; as single words. The lowercase &lt;code&gt;w&lt;/code&gt; would stop at every underscore and dot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="c"&gt;" Cursor on 'u' in user_name&lt;/span&gt;
&lt;span class="k"&gt;w&lt;/span&gt;    " → stops at &lt;span class="s1"&gt;'_'&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;between user &lt;span class="nb"&gt;and&lt;/span&gt; name&lt;span class="p"&gt;)&lt;/span&gt;
W    " → skips &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;next&lt;/span&gt; whitespace&lt;span class="p"&gt;-&lt;/span&gt;separated chunk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule of thumb: use lowercase when you care about punctuation boundaries, uppercase when you want to jump over the whole "thing."&lt;/p&gt;

&lt;h3&gt;
  
  
  Line movements
&lt;/h3&gt;

&lt;p&gt;Getting around within a line:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; — go to the &lt;strong&gt;absolute start&lt;/strong&gt; of the line (column 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; — go to the &lt;strong&gt;first non-blank character&lt;/strong&gt; (ignores indentation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$&lt;/code&gt; — go to the &lt;strong&gt;end&lt;/strong&gt; of the line&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;g_&lt;/code&gt; — go to the &lt;strong&gt;last non-blank character&lt;/strong&gt; (ignores trailing spaces)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You'll use &lt;code&gt;^&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; far more than &lt;code&gt;0&lt;/code&gt; and &lt;code&gt;g_&lt;/code&gt;. Code has indentation; &lt;code&gt;^&lt;/code&gt; puts you at the actual first character of the code, not at column 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  File movements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;gg&lt;/code&gt; — jump to the &lt;strong&gt;first line&lt;/strong&gt; of the file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;G&lt;/code&gt; — jump to the &lt;strong&gt;last line&lt;/strong&gt; of the file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;5G&lt;/code&gt; or &lt;code&gt;:5&lt;/code&gt; — jump to &lt;strong&gt;line 5&lt;/strong&gt; (replace 5 with any number)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+d&lt;/code&gt; — scroll half a page &lt;strong&gt;down&lt;/strong&gt; (d for down)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+u&lt;/code&gt; — scroll half a page &lt;strong&gt;up&lt;/strong&gt; (u for up)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+f&lt;/code&gt; — full page &lt;strong&gt;forward&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Ctrl+b&lt;/code&gt; — full page &lt;strong&gt;back&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;Ctrl+d&lt;/code&gt; / &lt;code&gt;Ctrl+u&lt;/code&gt; pair deserves a special mention. Once these are in your muscle memory, you'll browse through files at a completely different speed. Down, down, down, up — no more dragging a scrollbar with the mouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vim grammar: operator + motion
&lt;/h2&gt;

&lt;p&gt;This is the part where it all clicks. Stay with me.&lt;/p&gt;

&lt;p&gt;Every command in Vim follows a simple grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[count] operator motion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;count&lt;/strong&gt; (optional): how many times to apply&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;operator&lt;/strong&gt;: what to do (&lt;code&gt;d&lt;/code&gt; for delete, &lt;code&gt;c&lt;/code&gt; for change, &lt;code&gt;y&lt;/code&gt; for yank/copy)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;motion&lt;/strong&gt;: where to do it (&lt;code&gt;w&lt;/code&gt; for word, &lt;code&gt;$&lt;/code&gt; for end of line, &lt;code&gt;j&lt;/code&gt; for down)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You already know several motions: &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;G&lt;/code&gt;. The moment you learn even one operator, you can combine it with every motion you know.&lt;/p&gt;

&lt;h3&gt;
  
  
  The operators
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operator&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Delete&lt;/strong&gt; (and put in clipboard — it's actually "cut")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Change&lt;/strong&gt; (delete and immediately enter Insert mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yank&lt;/strong&gt; (copy to clipboard without deleting)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Put&lt;/strong&gt; (paste — after cursor by default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;P&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Put before&lt;/strong&gt; the cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Combining them
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;dw    " &lt;span class="k"&gt;delete&lt;/span&gt; from &lt;span class="nb"&gt;cursor&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; start of &lt;span class="k"&gt;next&lt;/span&gt; word
db    " &lt;span class="k"&gt;delete&lt;/span&gt; from &lt;span class="nb"&gt;cursor&lt;/span&gt; back &lt;span class="k"&gt;to&lt;/span&gt; start of current word
&lt;span class="k"&gt;d&lt;/span&gt;$    " &lt;span class="k"&gt;delete&lt;/span&gt; from &lt;span class="nb"&gt;cursor&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; end of &lt;span class="nb"&gt;line&lt;/span&gt;
d0    " &lt;span class="k"&gt;delete&lt;/span&gt; from &lt;span class="nb"&gt;cursor&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; start of &lt;span class="nb"&gt;line&lt;/span&gt;
dG    " &lt;span class="k"&gt;delete&lt;/span&gt; from &lt;span class="nb"&gt;cursor&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; end of &lt;span class="k"&gt;file&lt;/span&gt;
d5j   " &lt;span class="k"&gt;delete&lt;/span&gt; current &lt;span class="nb"&gt;line&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="nb"&gt;lines&lt;/span&gt; below &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt; total&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;cw&lt;/span&gt;    " &lt;span class="k"&gt;change&lt;/span&gt; word &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt; word&lt;span class="p"&gt;,&lt;/span&gt; enter Insert &lt;span class="k"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;c&lt;/span&gt;$    " &lt;span class="k"&gt;change&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; end of &lt;span class="nb"&gt;line&lt;/span&gt;
&lt;span class="k"&gt;c&lt;/span&gt;^    " &lt;span class="k"&gt;change&lt;/span&gt; from start of code &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;cursor&lt;/span&gt;

yw    " &lt;span class="k"&gt;yank&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; one word
&lt;span class="k"&gt;y&lt;/span&gt;$    " &lt;span class="k"&gt;yank&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; end of &lt;span class="nb"&gt;line&lt;/span&gt;
yy    " &lt;span class="k"&gt;yank&lt;/span&gt; entire &lt;span class="nb"&gt;line&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;special case — doubled operator &lt;span class="p"&gt;=&lt;/span&gt; full &lt;span class="nb"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last one is worth dwelling on: &lt;strong&gt;doubling an operator applies it to the whole line&lt;/strong&gt;. &lt;code&gt;dd&lt;/code&gt; deletes the current line. &lt;code&gt;yy&lt;/code&gt; yanks it. &lt;code&gt;cc&lt;/code&gt; changes it. You'll use &lt;code&gt;dd&lt;/code&gt; and &lt;code&gt;yy&lt;/code&gt; constantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  A concrete example
&lt;/h3&gt;

&lt;p&gt;Say you're editing this Python function and want to rename &lt;code&gt;calculate_total&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;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&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;sum&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;price&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;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Position your cursor on the &lt;code&gt;c&lt;/code&gt; of &lt;code&gt;calculate_total&lt;/code&gt;. Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;cw&lt;/span&gt;    " deletes &lt;span class="s1"&gt;'calculate_total'&lt;/span&gt; &lt;span class="nb"&gt;and&lt;/span&gt; drops you into Insert &lt;span class="k"&gt;mode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;compute_sum&lt;/code&gt;, press &lt;code&gt;Esc&lt;/code&gt;. Done. Two keystrokes to delete the word, type the replacement, exit Insert mode.&lt;/p&gt;

&lt;p&gt;Without Vim: click at the start of the word, click-drag to the end, type the replacement. Five gestures, two of which require the mouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dot command: the most powerful key in Vim
&lt;/h2&gt;

&lt;p&gt;Once you've made a change, you can repeat it instantly: press &lt;code&gt;.&lt;/code&gt; (dot).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.&lt;/code&gt; command replays the last change you made — whatever it was. Added &lt;code&gt;async&lt;/code&gt; before a function? Press &lt;code&gt;.&lt;/code&gt; to add it to the next one. Deleted a line with &lt;code&gt;dd&lt;/code&gt;? Press &lt;code&gt;.&lt;/code&gt; to delete the next line. Changed a word with &lt;code&gt;cw&lt;/code&gt;? Press &lt;code&gt;.&lt;/code&gt; to change the next word the same way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="k"&gt;cw&lt;/span&gt;    " &lt;span class="k"&gt;change&lt;/span&gt; current word&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s1"&gt;'newName'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; press Esc
&lt;span class="k"&gt;w&lt;/span&gt;     " &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;next&lt;/span&gt; occurrence
&lt;span class="p"&gt;.&lt;/span&gt;     " &lt;span class="nb"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;change&lt;/span&gt; that word &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="s1"&gt;'newName'&lt;/span&gt; too
&lt;span class="k"&gt;w&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;     " &lt;span class="nb"&gt;and&lt;/span&gt; again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how Vim users do what looks like multiple-cursor editing without actually using multiple cursors. Find the pattern, make the change once, then &lt;code&gt;.&lt;/code&gt; your way through the file.&lt;/p&gt;

&lt;p&gt;Don't underestimate &lt;code&gt;.&lt;/code&gt;. Experienced Vim users think about their edits in terms of "how do I make this change once, repeatably?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Counts: do it N times
&lt;/h2&gt;

&lt;p&gt;Any command can be prefixed with a number to repeat it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="k"&gt;w&lt;/span&gt;      " &lt;span class="k"&gt;move&lt;/span&gt; forward &lt;span class="m"&gt;3&lt;/span&gt; words
&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="k"&gt;j&lt;/span&gt;      " &lt;span class="k"&gt;move&lt;/span&gt; down &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="nb"&gt;lines&lt;/span&gt;
&lt;span class="m"&gt;3&lt;/span&gt;dd     " &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="nb"&gt;lines&lt;/span&gt;
&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="k"&gt;p&lt;/span&gt;      " &lt;span class="nb"&gt;paste&lt;/span&gt; twice
&lt;span class="m"&gt;10&lt;/span&gt;G     " &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;line&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Counts plus operators plus motions give you a genuinely expressive system. &lt;code&gt;d3w&lt;/code&gt; means "delete 3 words." &lt;code&gt;y5j&lt;/code&gt; means "yank current line plus 5 below." You're not memorizing a list of commands; you're speaking a language.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few essential shortcuts
&lt;/h2&gt;

&lt;p&gt;Some combinations appear so often they've earned their own single keys:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Shortcut&lt;/th&gt;
&lt;th&gt;Equivalent&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;D&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete to end of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c$&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change to end of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;yy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Yank entire line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;x&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete character under cursor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Substitute character (delete + Insert mode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Substitute entire line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These aren't exceptions to the grammar — they're just common abbreviations. Learn them as shortcuts, not as separate rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical exercise
&lt;/h2&gt;

&lt;p&gt;Open any code file you've been working on and spend five minutes on this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to a function or method name using &lt;code&gt;w&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; only — no arrow keys&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;cw&lt;/code&gt; to rename it, type the new name, press &lt;code&gt;Esc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;gg&lt;/code&gt; to go to the top of the file&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;G&lt;/code&gt; to go to the bottom&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;10G&lt;/code&gt; (or whatever line number makes sense) to jump somewhere in the middle&lt;/li&gt;
&lt;li&gt;Delete three lines with &lt;code&gt;3dd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Paste them back with &lt;code&gt;p&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Find another word you want to delete and use &lt;code&gt;dw&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;u&lt;/code&gt; to undo&lt;/li&gt;
&lt;li&gt;Press &lt;code&gt;.&lt;/code&gt; — notice what happens&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you feel slightly overwhelmed at this point, that's completely normal. For now, don't stress about memorizing every combination — focus on the grammar: &lt;strong&gt;operator + motion&lt;/strong&gt;. Once that's in your head, the rest follows naturally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key concepts from this lesson
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Word movements&lt;/strong&gt;: &lt;code&gt;w&lt;/code&gt; / &lt;code&gt;b&lt;/code&gt; / &lt;code&gt;e&lt;/code&gt; jump by word; &lt;code&gt;W&lt;/code&gt; / &lt;code&gt;B&lt;/code&gt; / &lt;code&gt;E&lt;/code&gt; jump by WORD (ignoring punctuation)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line movements&lt;/strong&gt;: &lt;code&gt;^&lt;/code&gt; → first non-blank, &lt;code&gt;$&lt;/code&gt; → end of line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File movements&lt;/strong&gt;: &lt;code&gt;gg&lt;/code&gt; → top, &lt;code&gt;G&lt;/code&gt; → bottom, &lt;code&gt;Ctrl+d&lt;/code&gt; / &lt;code&gt;Ctrl+u&lt;/code&gt; → scroll&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The grammar&lt;/strong&gt;: &lt;code&gt;[count] operator motion&lt;/code&gt; — &lt;code&gt;d3w&lt;/code&gt;, &lt;code&gt;c$&lt;/code&gt;, &lt;code&gt;y5j&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operators&lt;/strong&gt;: &lt;code&gt;d&lt;/code&gt; (delete/cut), &lt;code&gt;c&lt;/code&gt; (change), &lt;code&gt;y&lt;/code&gt; (yank/copy), &lt;code&gt;p&lt;/code&gt; (put/paste)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Doubled operators&lt;/strong&gt; = whole line: &lt;code&gt;dd&lt;/code&gt;, &lt;code&gt;yy&lt;/code&gt;, &lt;code&gt;cc&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.&lt;/code&gt;&lt;/strong&gt; repeats the last change — use it constantly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Counts&lt;/strong&gt; multiply any command: &lt;code&gt;3dd&lt;/code&gt;, &lt;code&gt;5j&lt;/code&gt;, &lt;code&gt;2p&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You now understand what makes Vim fundamentally different from every editor you've used before. It's not the number of shortcuts — it's the grammar. Operators combine with motions, counts scale them, and &lt;code&gt;.&lt;/code&gt; repeats them. You're not memorizing; you're composing.&lt;/p&gt;

&lt;p&gt;The next step is understanding the other side of the grammar: &lt;strong&gt;Insert mode in depth&lt;/strong&gt;. There's more there than just "press &lt;code&gt;i&lt;/code&gt; and type" — Vim has a set of powerful ways to enter and exit Insert mode that will make your edits more precise and your &lt;code&gt;.&lt;/code&gt; repetitions more effective.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>vim</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Conditional statements in Python: if, elif, and else</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 08 Jul 2026 10:30:06 +0000</pubDate>
      <link>https://dev.to/fj_palacios/conditional-statements-in-python-if-elif-and-else-48g0</link>
      <guid>https://dev.to/fj_palacios/conditional-statements-in-python-if-elif-and-else-48g0</guid>
      <description>&lt;p&gt;Every program you've written so far works exactly the same every time you run it. It doesn't matter what data you enter, doesn't matter what happens: the code always follows the same path, top to bottom, no detours.&lt;/p&gt;

&lt;p&gt;That's fine for a calculator script. But real programs need to &lt;em&gt;react&lt;/em&gt;. An online store that shows the same price to everyone regardless of whether they have a discount isn't very useful. A fitness app that tells you to exercise even when you've already done it isn't either.&lt;/p&gt;

&lt;p&gt;The ability to &lt;strong&gt;make decisions&lt;/strong&gt; is what separates a rigid program from one that does something interesting. And in Python, that ability is called &lt;strong&gt;control flow&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a conditional statement?
&lt;/h2&gt;

&lt;p&gt;Think about how you make decisions in real life. "If it's raining, grab an umbrella. If it's not, go out normally." That's exactly what an &lt;code&gt;if&lt;/code&gt; does in Python: it runs a block of code only when a condition is met.&lt;/p&gt;

&lt;p&gt;The basic structure 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;if&lt;/span&gt; &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# This code runs only if the condition is True
&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;The condition is met&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;Two important things before we continue:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The colon&lt;/strong&gt; (&lt;code&gt;:&lt;/code&gt;): it's mandatory at the end of the &lt;code&gt;if&lt;/code&gt; line. Forget it and Python will throw a &lt;code&gt;SyntaxError&lt;/code&gt; without mercy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Indentation&lt;/strong&gt;: the block of code inside the &lt;code&gt;if&lt;/code&gt; must be &lt;strong&gt;indented&lt;/strong&gt; — typically 4 spaces. In Python, indentation isn't optional or decorative. It's part of the syntax. Without it, the code doesn't belong to the &lt;code&gt;if&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="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;You are an adult&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Inside the if — runs if True
&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;You can vote&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 inside — same indentation
&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;This always shows up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Outside the if — always runs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;age&lt;/code&gt; is 20, you'll see all three messages. If &lt;code&gt;age&lt;/code&gt; were 15, you'd only see the last one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The else: plan B
&lt;/h2&gt;

&lt;p&gt;What if you want to do something different when the condition &lt;em&gt;isn't&lt;/em&gt; met? That's what &lt;code&gt;else&lt;/code&gt; is for. It works like the "otherwise" in everyday speech:&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;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&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;It&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s hot, stay inside&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;Pleasant temperature, go for a walk&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;&lt;code&gt;else&lt;/code&gt; carries no condition — it simply catches all the cases the &lt;code&gt;if&lt;/code&gt; didn't cover. If the &lt;code&gt;if&lt;/code&gt; condition is &lt;code&gt;True&lt;/code&gt;, the &lt;code&gt;if&lt;/code&gt; block runs and &lt;code&gt;else&lt;/code&gt; is ignored. If it's &lt;code&gt;False&lt;/code&gt;, the opposite happens.&lt;/p&gt;

&lt;p&gt;One important thing: &lt;strong&gt;&lt;code&gt;else&lt;/code&gt; always pairs with an &lt;code&gt;if&lt;/code&gt;&lt;/strong&gt;. You can't have a standalone &lt;code&gt;else&lt;/code&gt;. Python would complain, and rightly so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The elif: when there are more than two options
&lt;/h2&gt;

&lt;p&gt;Life is rarely binary. Sometimes you need to evaluate multiple conditions in order, until you find one that's true. That's where &lt;code&gt;elif&lt;/code&gt; comes in — short for "else if":&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;grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&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;A — Excellent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&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;B — Good&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&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;C — Average&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&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;D — Passing&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;F — Fail&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;Average&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evaluation order is &lt;strong&gt;top to bottom&lt;/strong&gt;. Python checks the first condition; if it's &lt;code&gt;False&lt;/code&gt;, it moves to the next &lt;code&gt;elif&lt;/code&gt;; if that's also &lt;code&gt;False&lt;/code&gt;, to the next one... until one is &lt;code&gt;True&lt;/code&gt; or it reaches the &lt;code&gt;else&lt;/code&gt;. As soon as it finds a true condition, &lt;strong&gt;it runs that block and exits&lt;/strong&gt;. It doesn't keep checking the rest.&lt;/p&gt;

&lt;p&gt;This means order matters. A lot. Look at what happens if we mix up the conditions:&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;grade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;95&lt;/span&gt;

&lt;span class="c1"&gt;# ⚠️ Wrong order — will never reach the higher grades
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&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;D — Passing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# This runs for 95... not what we wanted
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&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;C — Average&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&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;B — Good&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;grade&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&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;A — Excellent&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight d"&gt;&lt;code&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="err"&gt;—&lt;/span&gt; &lt;span class="n"&gt;Passing&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A grade of 95 classified as passing. The problem: &lt;code&gt;95 &amp;gt;= 60&lt;/code&gt; is &lt;code&gt;True&lt;/code&gt;, so Python runs that block and never reaches the 90 condition. The general rule: &lt;strong&gt;most restrictive first, most permissive last&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truth and falsiness in Python
&lt;/h2&gt;

&lt;p&gt;Python has a pretty flexible view of what it considers true and what it considers false. Any value can be used as a condition, not just &lt;code&gt;True&lt;/code&gt; and &lt;code&gt;False&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="c1"&gt;# These are "falsy" — Python treats them as False in a condition
&lt;/span&gt;&lt;span class="k"&gt;if&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;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;This never shows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Neither does this&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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;An empty list either&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;None&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;None either&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# These are "truthy" — any non-empty, non-zero value
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;42&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;A non-zero number: truthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A non-empty string: truthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="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;3&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;A list with elements: truthy&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 comes in handy. Instead of writing &lt;code&gt;if len(my_list) &amp;gt; 0&lt;/code&gt;, you can just write &lt;code&gt;if my_list&lt;/code&gt;. Python knows an empty list is "false" and one with elements is "true".&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s your name? &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&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;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&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;You didn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t enter your name&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;Clean, direct, readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compound conditions
&lt;/h2&gt;

&lt;p&gt;You already know how to use &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;or&lt;/code&gt;, and &lt;code&gt;not&lt;/code&gt; from the previous lesson. Now it all makes sense inside an &lt;code&gt;if&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="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="n"&gt;has_license&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;has_license&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;You can rent a car&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;You don&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t meet the requirements&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saturday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saturday&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sunday&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;It&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the weekend, time to rest&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;Back to work&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;is_premium&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;is_premium&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;Go premium to access this content&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;A condition with &lt;code&gt;and&lt;/code&gt; is only true if &lt;strong&gt;all&lt;/strong&gt; sub-conditions are true. With &lt;code&gt;or&lt;/code&gt;, it's enough for &lt;strong&gt;one&lt;/strong&gt; to be true. With &lt;code&gt;not&lt;/code&gt;, you invert the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested conditionals: with care
&lt;/h2&gt;

&lt;p&gt;It's perfectly valid to put an &lt;code&gt;if&lt;/code&gt; inside another &lt;code&gt;if&lt;/code&gt;. These are called &lt;strong&gt;nested conditionals&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;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;
&lt;span class="n"&gt;has_ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;has_ticket&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;You can enter the concert&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;You&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re old enough but you need a ticket&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;You can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t enter, you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re underage&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;It works, but use them sparingly. Every nesting level adds complexity and makes the code harder to read. If you find yourself three &lt;code&gt;if&lt;/code&gt; levels deep, you can probably simplify with &lt;code&gt;and&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="c1"&gt;# ✅ Cleaner with and
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;has_ticket&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;You can enter the concert&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;You&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re old enough but you need a ticket&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;You can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t enter, you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;re underage&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;The unwritten rule: if you can avoid nesting, avoid it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A program that makes real decisions
&lt;/h2&gt;

&lt;p&gt;Let's put everything together in a concrete example. A library access system that checks multiple conditions:&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;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How old are you? &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;has_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do you have a library card? (y/n) &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;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;books_on_loan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How many books do you currently have on loan? &lt;/span&gt;&lt;span class="sh"&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="c1"&gt;# Empty line for readability
&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&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;Sorry, you need to be at least 6 years old to use the library.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;has_card&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;You need a library card. You can get one at the reception desk.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;books_on_loan&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&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;You&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ve reached the loan limit. Return a book before borrowing more.&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;Welcome! You can borrow books.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;books_on_loan&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&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;Remember, you only have&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;books_on_loan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;loans remaining.&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;Notice the structure: we check the cases that block access first, and leave the successful case for the &lt;code&gt;else&lt;/code&gt;. This is a common pattern called &lt;strong&gt;early exit&lt;/strong&gt; — filtering out the negative cases upfront so the main flow stays clean at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;if condition:&lt;/code&gt; runs a block only if the condition is true&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;elif&lt;/code&gt; chains alternative conditions; Python evaluates top to bottom and stops at the first true one&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;else&lt;/code&gt; catches all cases that didn't match any previous condition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indentation&lt;/strong&gt; (4 spaces) isn't decorative — it defines which code belongs to which block&lt;/li&gt;
&lt;li&gt;Python has &lt;strong&gt;truthiness&lt;/strong&gt;: values like &lt;code&gt;0&lt;/code&gt;, &lt;code&gt;""&lt;/code&gt;, &lt;code&gt;[]&lt;/code&gt;, and &lt;code&gt;None&lt;/code&gt; evaluate as &lt;code&gt;False&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Nested conditionals work, but &lt;code&gt;and&lt;/code&gt; / &lt;code&gt;or&lt;/code&gt; are usually cleaner&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;With conditional statements, your programs can finally make decisions. But decisions alone aren't enough — often you need to repeat an action a certain number of times, or as long as a condition holds. In the next tutorial you'll learn &lt;strong&gt;&lt;code&gt;while&lt;/code&gt; loops&lt;/strong&gt;: how to make Python repeat code automatically, and why you need to watch out for infinite loops.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Write a program that asks the user for a numeric grade from 0 to 10 and displays the letter grade (A, B, C, D, F). Add validation: if the user enters a number outside the 0–10 range, show an error message instead of trying to classify it. Bonus: what happens if they enter a negative number? What if they enter text instead of a number?&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Docker images and containers: understanding the relationship</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 07 Jul 2026 06:39:39 +0000</pubDate>
      <link>https://dev.to/fj_palacios/docker-images-and-containers-understanding-the-relationship-4d18</link>
      <guid>https://dev.to/fj_palacios/docker-images-and-containers-understanding-the-relationship-4d18</guid>
      <description>&lt;p&gt;You pulled &lt;code&gt;ubuntu&lt;/code&gt;, you pulled &lt;code&gt;nginx&lt;/code&gt;, you've been running containers and destroying them. But here's the thing — do you actually know what an image &lt;em&gt;is&lt;/em&gt;? Not the one-liner ("a template"), but what's actually happening on your disk? Why does pulling &lt;code&gt;node:22&lt;/code&gt; take a while the first time and almost nothing the second? Why can you run ten containers from the same image and none of them affect each other?&lt;/p&gt;

&lt;p&gt;If you got through lesson 2 with a vague intuition of how this works, this lesson is about turning that intuition into a mental model you can rely on. Because everything in Docker — Dockerfiles, image caching, volumes, multi-stage builds — makes a lot more sense once you understand what's happening underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Docker image, really?
&lt;/h2&gt;

&lt;p&gt;Let's start with what an image is &lt;em&gt;not&lt;/em&gt;. It's not a file. It's not a zip archive. It's not a snapshot of a running system.&lt;/p&gt;

&lt;p&gt;A Docker image is a &lt;strong&gt;stack of layers&lt;/strong&gt;. Each layer is a set of filesystem changes — files added, files modified, files deleted. When you stack them all together, you get a complete filesystem that a container can run. Think of it like a Git commit history: each commit (layer) describes what changed, and the full history gives you the current state of the project.&lt;/p&gt;

&lt;p&gt;This is called a &lt;strong&gt;union filesystem&lt;/strong&gt; (OverlayFS is the most common implementation on Linux). Docker merges these read-only layers into a single coherent view, and that's what the container sees as its filesystem.&lt;/p&gt;

&lt;p&gt;Let's make it concrete. Imagine an image built like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Layer 1 (Base): Ubuntu 22.04 — adds ~80MB of OS files
Layer 2: apt-get install python3 — adds Python interpreter
Layer 3: pip install flask — adds Flask and its dependencies
Layer 4: COPY app.py /app/ — adds your application code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer only stores the diff from the previous one. And here's where it gets efficient: if you have ten different images that all start from &lt;code&gt;ubuntu:22.04&lt;/code&gt;, they all share that first layer on disk. Docker doesn't download or store it ten times — it references the same layer. Pull a new image that shares base layers with something you already have, and Docker will say "already exists" for those layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull python:3.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;3.12: Pulling from library/python
fa9b7e77b6bf: Already exists       ← base layers shared with other images
48be9699aae1: Already exists
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;b0c4ce42c5d0: Pull complete        ← new layers specific to python:3.12
Digest: sha256:...
Status: Downloaded newer image for python:3.12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the layer cache doing its job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exploring layers
&lt;/h2&gt;

&lt;p&gt;You can inspect exactly what layers an image contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image inspect nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dumps a large JSON object. The relevant section is &lt;code&gt;RootFS.Layers&lt;/code&gt;, which lists the SHA256 hashes of each layer. You can also see a higher-level view with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker image &lt;span class="nb"&gt;history &lt;/span&gt;nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;IMAGE          CREATED        CREATED BY                                      SIZE
&lt;/span&gt;&lt;span class="gp"&gt;a7be6198544f   2 weeks ago    CMD ["nginx" "-g" "daemon off;&lt;/span&gt;&lt;span class="s2"&gt;"]               0B
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    STOPSIGNAL SIGQUIT                             0B
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    EXPOSE 80                                      0B
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    COPY /etc/nginx /etc/nginx /                   4.61kB
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    RUN /bin/sh -c apt-get update &amp;amp;&amp;amp; apt-get ...   91.4MB
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    ENV NGINX_VERSION=1.27.4                       0B
&lt;/span&gt;&lt;span class="gp"&gt;&amp;lt;missing&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="s2"&gt;2 weeks ago    FROM debian:bookworm-slim                      0B
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it bottom to top: start with the base &lt;code&gt;debian:bookworm-slim&lt;/code&gt;, install packages, copy Nginx config, set the startup command. Each line is a layer (or a metadata instruction that adds 0 bytes).&lt;/p&gt;

&lt;h2&gt;
  
  
  Image tags: this is not optional knowledge
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;docker pull nginx&lt;/code&gt;, you're actually running &lt;code&gt;docker pull nginx:latest&lt;/code&gt;. The &lt;code&gt;:latest&lt;/code&gt; is a &lt;strong&gt;tag&lt;/strong&gt;, and &lt;code&gt;latest&lt;/code&gt; is the default. Tags are mutable pointers — today's &lt;code&gt;nginx:latest&lt;/code&gt; is not the same image as &lt;code&gt;nginx:latest&lt;/code&gt; from six months ago. It just points to the newest stable build.&lt;/p&gt;

&lt;p&gt;In production, you never use &lt;code&gt;latest&lt;/code&gt;. You pin to a specific version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull nginx:1.27.4         &lt;span class="c"&gt;# Specific patch version — deterministic&lt;/span&gt;
docker pull nginx:1.27           &lt;span class="c"&gt;# Minor version — gets patches&lt;/span&gt;
docker pull nginx:1              &lt;span class="c"&gt;# Major version — gets minor updates too&lt;/span&gt;
docker pull nginx:latest         &lt;span class="c"&gt;# ❌ Fine for local experiments, not for prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because &lt;code&gt;latest&lt;/code&gt; means your deployment changes every time you pull. Upgrading Nginx should be a deliberate decision, not a side effect of running &lt;code&gt;docker pull&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's also a pattern you'll see often: &lt;strong&gt;variant tags&lt;/strong&gt;. The same version with different base images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nginx:1.27.4-alpine    &lt;span class="c"&gt;# Alpine Linux base (~5MB), minimal, fast to pull&lt;/span&gt;
nginx:1.27.4           &lt;span class="c"&gt;# Debian base (~180MB), more compatible&lt;/span&gt;
nginx:1.27.4-slim      &lt;span class="c"&gt;# Reduced Debian, middle ground&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alpine is popular for production because of its tiny size. The tradeoff: it uses &lt;code&gt;musl libc&lt;/code&gt; instead of &lt;code&gt;glibc&lt;/code&gt;, which can cause subtle compatibility issues with some software. For most things it's fine; for others it's a frustrating debugging session (because C library mismatches).&lt;/p&gt;

&lt;h2&gt;
  
  
  The container lifecycle
&lt;/h2&gt;

&lt;p&gt;An image is static. A container is alive — and it has a lifecycle.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;docker run&lt;/code&gt;, the container doesn't just appear in a running state. It goes through a series of states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created → Running → (Paused) → Stopped → Removed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Created&lt;/strong&gt;: the container exists but hasn't started yet. You can do this explicitly with &lt;code&gt;docker create&lt;/code&gt;, or it's an implicit step inside &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running&lt;/strong&gt;: the main process is executing. The container consumes CPU, RAM, and filesystem resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paused&lt;/strong&gt;: the process is suspended (SIGSTOP). The container still exists in memory but isn't doing anything. &lt;code&gt;docker pause&lt;/code&gt; and &lt;code&gt;docker unpause&lt;/code&gt;. Rarely used, but useful for debugging — freeze a container in place and inspect its state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stopped&lt;/strong&gt;: the main process has exited. The container still exists as a stopped entity — its filesystem is preserved, its logs are still accessible. You can restart it with &lt;code&gt;docker start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Removed&lt;/strong&gt;: the container is gone. Filesystem, logs, everything. You can't bring it back.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Walk through the lifecycle manually&lt;/span&gt;
docker create &lt;span class="nt"&gt;--name&lt;/span&gt; demo nginx          &lt;span class="c"&gt;# Created&lt;/span&gt;
docker start demo                        &lt;span class="c"&gt;# Running&lt;/span&gt;
docker pause demo                        &lt;span class="c"&gt;# Paused&lt;/span&gt;
docker unpause demo                      &lt;span class="c"&gt;# Running again&lt;/span&gt;
docker stop demo                         &lt;span class="c"&gt;# Stopped (SIGTERM → SIGKILL after 10s)&lt;/span&gt;
docker &lt;span class="nb"&gt;rm &lt;/span&gt;demo                           &lt;span class="c"&gt;# Removed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or skip the ceremony and do everything at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; nginx    &lt;span class="c"&gt;# Creates, starts, and removes when done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The writable layer: why containers don't modify images
&lt;/h2&gt;

&lt;p&gt;Here's a key detail that confuses people. If images are read-only, how can a container write files?&lt;/p&gt;

&lt;p&gt;When Docker creates a container from an image, it adds a &lt;strong&gt;thin writable layer&lt;/strong&gt; on top of all the read-only image layers. This is where everything the container writes goes — log files, temporary data, any &lt;code&gt;apt-get install&lt;/code&gt; you do inside the container. The image layers beneath are never touched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────┐
│   Container writable layer  │  ← changes here only
├─────────────────────────────┤
│   Image layer 4 (app.py)    │  read-only
├─────────────────────────────┤
│   Image layer 3 (Flask)     │  read-only
├─────────────────────────────┤
│   Image layer 2 (Python)    │  read-only
├─────────────────────────────┤
│   Image layer 1 (Ubuntu)    │  read-only
└─────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the container is removed, the writable layer disappears with it. The image is exactly as it was. Start another container from the same image and you get a fresh writable layer — a clean slate.&lt;/p&gt;

&lt;p&gt;This is what makes containers &lt;strong&gt;ephemeral by design&lt;/strong&gt;. They're not meant to be permanent homes for data. Anything you want to persist beyond a container's lifetime goes in a volume — but that's a topic for later in the course.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten containers, one image
&lt;/h2&gt;

&lt;p&gt;Let's make this tangible. You can have ten containers running from a single image simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start ten nginx containers, each on a different port&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 10&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"808&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:80"&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"nginx-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; nginx
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker ps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;CONTAINER ID   IMAGE   COMMAND                  PORTS                  NAMES
&lt;/span&gt;&lt;span class="gp"&gt;a1b2c3d4e5f6   nginx   "/docker-entrypoint.…"   0.0.0.0:8081-&amp;gt;&lt;/span&gt;80/tcp   nginx-1
&lt;span class="gp"&gt;b2c3d4e5f6a7   nginx   "/docker-entrypoint.…"   0.0.0.0:8082-&amp;gt;&lt;/span&gt;80/tcp   nginx-2
&lt;span class="c"&gt;...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each one is completely isolated. Writing to the filesystem of &lt;code&gt;nginx-1&lt;/code&gt; doesn't affect &lt;code&gt;nginx-2&lt;/code&gt;. They all share the read-only Nginx image layers. On disk, the overhead of ten containers is ten tiny writable layers — not ten full copies of Nginx.&lt;/p&gt;

&lt;p&gt;Clean up when done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 10&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;docker stop &lt;span class="s2"&gt;"nginx-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="s2"&gt;"nginx-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or more directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-aq&lt;/span&gt; &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx-&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pulling, listing, and removing images
&lt;/h2&gt;

&lt;p&gt;A few commands you'll use constantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download an image without running it&lt;/span&gt;
docker pull node:22-alpine

&lt;span class="c"&gt;# List all locally available images&lt;/span&gt;
docker images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;REPOSITORY   TAG          IMAGE ID       CREATED        SIZE
nginx        latest       a7be6198544f   2 weeks ago    192MB
node         22-alpine    f7d2a4e85d2c   3 weeks ago    141MB
ubuntu       22.04        3db8720ecbf5   4 weeks ago    77.9MB
python       3.12         b3a18c9e2f1d   3 weeks ago    1.02GB
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove an image (only works if no containers — running or stopped — reference it)&lt;/span&gt;
docker rmi nginx

&lt;span class="c"&gt;# Force remove (also removes derived containers)&lt;/span&gt;
docker rmi &lt;span class="nt"&gt;-f&lt;/span&gt; nginx

&lt;span class="c"&gt;# Remove all unused images&lt;/span&gt;
docker image prune
docker image prune &lt;span class="nt"&gt;-a&lt;/span&gt;    &lt;span class="c"&gt;# Also removes images with no running containers (more aggressive)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ You can't remove an image while a container (even a stopped one) references it. Remove the container first, then the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming containers: don't skip this
&lt;/h2&gt;

&lt;p&gt;If you don't name your containers, Docker assigns random adjective-noun combinations (&lt;code&gt;ecstatic_hopper&lt;/code&gt;, &lt;code&gt;confident_kepler&lt;/code&gt;). Endearing, but useless for anything beyond a quick experiment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Always name your containers&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; web-server nginx
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; api-backend node:22-alpine node app.js
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; db-primary postgres:16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Named containers are easier to reference in every subsequent command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs web-server
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; web-server bash
docker stop web-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you use Docker Compose (coming later), naming becomes even more important because services discover each other by name over Docker's internal network.&lt;/p&gt;




&lt;p&gt;The image-container relationship is the foundation of everything Docker does. Layers explain the cache behavior, the efficiency of pulling images, and why modifying a container doesn't affect others sharing the same image. The lifecycle explains why containers are designed to be disposable — and why data persistence requires a different mechanism.&lt;/p&gt;

&lt;p&gt;In the next lesson we'll go hands-on with &lt;strong&gt;interactive containers and exec&lt;/strong&gt;: running shells inside containers, copying files in and out, and understanding when to use &lt;code&gt;-it&lt;/code&gt; vs &lt;code&gt;-d&lt;/code&gt; and why the difference matters.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Pull three different images (&lt;code&gt;nginx&lt;/code&gt;, &lt;code&gt;node:22-alpine&lt;/code&gt;, &lt;code&gt;python:3.12-slim&lt;/code&gt;). Create two containers from &lt;code&gt;nginx&lt;/code&gt;, stop one, remove the other. View the logs of the remaining container. Check what layers &lt;code&gt;nginx&lt;/code&gt; and &lt;code&gt;python:3.12-slim&lt;/code&gt; have in common using &lt;code&gt;docker image inspect&lt;/code&gt;. Clean everything up with &lt;code&gt;docker system prune&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Installing Docker and running your first container</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Mon, 06 Jul 2026 11:32:44 +0000</pubDate>
      <link>https://dev.to/fj_palacios/installing-docker-and-running-your-first-container-843</link>
      <guid>https://dev.to/fj_palacios/installing-docker-and-running-your-first-container-843</guid>
      <description>&lt;p&gt;Every tool has its initiation ritual. With Git it's &lt;code&gt;git init&lt;/code&gt;. With Node it's &lt;code&gt;npm install&lt;/code&gt; and watching &lt;code&gt;node_modules&lt;/code&gt; devour half your disk. With Docker, it's a command that's been the opening line of every tutorial for years:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks trivially simple. But what it does under the hood is anything but: Docker downloads an image from the internet, creates a container, runs it, prints a message, and destroys it — all in seconds. No Dockerfile. No configuration. And yet you've just completed the full container lifecycle.&lt;/p&gt;

&lt;p&gt;Before you get there, though, you need to install the thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker Desktop vs Docker Engine: which one?
&lt;/h2&gt;

&lt;p&gt;The first fork in the road, and worth understanding upfront.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Engine&lt;/strong&gt; is the raw engine: the daemon that manages containers, no GUI, built for Linux servers and people who live in the terminal. This is what you'll run in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Desktop&lt;/strong&gt; is a desktop application that bundles Docker Engine, a graphical interface, and a compatibility layer that makes Docker work on macOS and Windows (which don't have a native Linux kernel). It also includes Docker Compose and a few extra tools.&lt;/p&gt;

&lt;p&gt;The practical rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Linux&lt;/strong&gt;: install Docker Engine directly. Docker Desktop exists for Linux but adds an unnecessary virtualization layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS&lt;/strong&gt;: Docker Desktop is the standard choice and the simplest path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows&lt;/strong&gt;: Docker Desktop with WSL2. Without WSL, the pain is real (because Windows).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation on Linux
&lt;/h2&gt;

&lt;p&gt;Linux is Docker's native environment, so installation is the most straightforward here. Arch users already know the drill — &lt;code&gt;pacman -S docker&lt;/code&gt; and done. For everyone else, the most reliable method that works across Ubuntu, Debian, Fedora, and most derivatives is the official script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Download and run the official install script&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://get.docker.com &lt;span class="nt"&gt;-o&lt;/span&gt; get-docker.sh
&lt;span class="nb"&gt;sudo &lt;/span&gt;sh get-docker.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script detects your distribution and configures the right repositories automatically. Once installed, there's one step most people miss, and it causes the classic "why am I getting permission denied?" moment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add your user to the docker group&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; docker &lt;span class="nv"&gt;$USER&lt;/span&gt;

&lt;span class="c"&gt;# Apply the group change immediately (or log out and back in)&lt;/span&gt;
newgrp docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without this, you'll need &lt;code&gt;sudo&lt;/code&gt; for every Docker command. Technically it works, but it's annoying and goes against how the workflow is meant to feel.&lt;/p&gt;

&lt;p&gt;Verify everything is working:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;Docker version 27.4.1, build f31f73a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;docker info&lt;/code&gt; shows you the daemon's state: how many containers are running, how many images you have locally, the kernel version, the storage driver. If you see all that without errors, the engine is running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation on macOS
&lt;/h2&gt;

&lt;p&gt;Two options. The most comfortable for most people is Docker Desktop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Via Homebrew (recommended)&lt;/span&gt;
brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or download directly from &lt;a href="https://docker.com" rel="noopener noreferrer"&gt;docker.com&lt;/a&gt;. Once installed, open Docker Desktop from Applications. You'll see the whale icon in the menu bar — when it stops animating, the engine is ready.&lt;/p&gt;

&lt;p&gt;The minimalist alternative, if you'd rather skip the GUI, is &lt;a href="https://orbstack.dev" rel="noopener noreferrer"&gt;OrbStack&lt;/a&gt;: lighter, faster to start, and fully compatible with all standard Docker commands. More and more people on macOS are using it instead of Docker Desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation on Windows (WSL2)
&lt;/h2&gt;

&lt;p&gt;Windows needs WSL2 (Windows Subsystem for Linux) as its foundation. If you don't have it yet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run in PowerShell as Administrator&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;wsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--install&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart, then install Docker Desktop from &lt;a href="https://docker.com" rel="noopener noreferrer"&gt;docker.com&lt;/a&gt;. During installation, make sure "Use WSL 2 based engine" is checked.&lt;/p&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Important&lt;/strong&gt;: if you have WSL1 installed from before, upgrade to WSL2 first. Docker works with WSL1, but the performance is noticeably worse and you'll hit weird edge cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker's architecture (what actually happens when you type a command)
&lt;/h2&gt;

&lt;p&gt;Before running that first container, it's worth understanding what's happening behind the scenes. When you type &lt;code&gt;docker run nginx&lt;/code&gt;, you're not just launching a process. There are three pieces talking to each other:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Client&lt;/strong&gt; (the CLI): the tool you use in the terminal. It converts your commands into REST API calls to the daemon. It's just a client — it doesn't do the heavy lifting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Daemon&lt;/strong&gt; (&lt;code&gt;dockerd&lt;/code&gt;): the process that actually manages containers, images, networks, and volumes. It runs in the background. When you run &lt;code&gt;docker run&lt;/code&gt;, the client tells the daemon "start this container", and the daemon does it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Registry&lt;/strong&gt;: the image repository. &lt;a href="https://hub.docker.com" rel="noopener noreferrer"&gt;Docker Hub&lt;/a&gt; is the default public registry. When you request an image you don't have locally, the daemon downloads it from there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[docker CLI] → REST API → [dockerd] → pulls from [Docker Hub]
                                     → creates the container
                                     → manages its lifecycle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This client-server model has a practical implication: you can point your local Docker client at a daemon running on a remote server. Useful for managing production from your terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Images vs containers: the mold and the pieces
&lt;/h2&gt;

&lt;p&gt;This distinction is fundamental. Get it wrong and Docker will feel confusing forever. Get it right and everything clicks.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;image&lt;/strong&gt; is a read-only template. It defines which base OS to use, what software to install, what files to copy in, what command to run at startup. It's immutable — it doesn't change. Think of it like a class in object-oriented programming, or a cookie cutter.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;container&lt;/strong&gt; is a running instance of an image. It's what actually executes, what consumes CPU and RAM, what has its own isolated filesystem and its own network. You can create ten containers from the same image — ten cookies from the same cutter, each independent of the others.&lt;/p&gt;

&lt;p&gt;When a container stops, it's gone (unless you explicitly told it to persist data). The image stays, ready to create more containers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first container
&lt;/h2&gt;

&lt;p&gt;With Docker installed, the moment of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The message walks you through exactly what just happened. The first time it takes a moment because it's downloading the image. The second time it's instant — the image is cached locally.&lt;/p&gt;

&lt;p&gt;Now something a bit more interesting: an interactive container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;root@a3f8c2b1d9e7:/#&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeling disoriented? That's expected. You just opened a shell inside a completely isolated Ubuntu container. You can install things, delete files, break whatever you want — when you exit, the container disappears and your machine is untouched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Inside the container&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release
&lt;span class="nb"&gt;ls&lt;/span&gt; /
&lt;span class="nb"&gt;whoami&lt;/span&gt;  &lt;span class="c"&gt;# root (containers run as root by default — we'll address this later)&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the classic web server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run Nginx in the background, mapping port 8080 on your machine to port 80 in the container&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open your browser at &lt;code&gt;http://localhost:8080&lt;/code&gt;. You have an Nginx server running in a container, without having installed anything on your system. When you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker stop my-nginx
docker &lt;span class="nb"&gt;rm &lt;/span&gt;my-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The essential commands
&lt;/h2&gt;

&lt;p&gt;You don't need to memorize all of this now — it'll sink in through use. But having it as a reference helps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull &amp;lt;image&amp;gt;        &lt;span class="c"&gt;# Download image from registry&lt;/span&gt;
docker images              &lt;span class="c"&gt;# List locally available images&lt;/span&gt;
docker run &amp;lt;image&amp;gt;         &lt;span class="c"&gt;# Create and start a container&lt;/span&gt;
docker ps                  &lt;span class="c"&gt;# List running containers&lt;/span&gt;
docker ps &lt;span class="nt"&gt;-a&lt;/span&gt;               &lt;span class="c"&gt;# List all containers (including stopped)&lt;/span&gt;
docker stop &amp;lt;container&amp;gt;    &lt;span class="c"&gt;# Stop a container (SIGTERM)&lt;/span&gt;
docker start &amp;lt;container&amp;gt;   &lt;span class="c"&gt;# Start a stopped container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;container&amp;gt;      &lt;span class="c"&gt;# Remove a stopped container&lt;/span&gt;
docker rmi &amp;lt;image&amp;gt;         &lt;span class="c"&gt;# Remove an image&lt;/span&gt;
docker logs &amp;lt;container&amp;gt;    &lt;span class="c"&gt;# View container output&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;container&amp;gt; bash  &lt;span class="c"&gt;# Open shell in running container&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One handy trick: &lt;code&gt;docker run --rm&lt;/code&gt; removes the container automatically when it stops. Perfect for quick experiments where you don't care about persistence.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Container disappears automatically when you exit&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; ubuntu bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;After experimenting, you'll have accumulated images and stopped containers. To clean house:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Remove all stopped containers&lt;/span&gt;
docker container prune

&lt;span class="c"&gt;# Remove unused images&lt;/span&gt;
docker image prune

&lt;span class="c"&gt;# Remove everything unused (containers, images, networks, build cache)&lt;/span&gt;
docker system prune
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ &lt;code&gt;docker system prune&lt;/code&gt; is aggressive — it will ask for confirmation, but it removes everything not actively in use. Use it deliberately.&lt;/p&gt;




&lt;p&gt;Docker is installed, and you've run your first containers. In the next lesson, we'll go deeper into the image-container relationship: what image layers are, how the full container lifecycle works, and how to inspect and manage everything you have running.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Common beginner mistakes in Python (and how to fix them)</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Sat, 04 Jul 2026 10:13:39 +0000</pubDate>
      <link>https://dev.to/fj_palacios/common-beginner-mistakes-in-python-and-how-to-fix-them-php</link>
      <guid>https://dev.to/fj_palacios/common-beginner-mistakes-in-python-and-how-to-fix-them-php</guid>
      <description>&lt;p&gt;Your code doesn't work. Python shows you some red text, and you stare at the screen wondering what just happened. Sound familiar?&lt;/p&gt;

&lt;p&gt;If you're like me when I started, your first instinct is to close the terminal and pretend it never happened. Your second instinct is to paste the error into Google and hope someone on Stack Overflow had the exact same problem in 2014.&lt;/p&gt;

&lt;p&gt;There's a better way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python errors are not your enemy
&lt;/h2&gt;

&lt;p&gt;Here's the thing most beginners don't realize: &lt;strong&gt;Python's error messages are the most helpful feedback you'll get&lt;/strong&gt;. They tell you exactly what went wrong, where it went wrong, and often how to fix it. The problem isn't that the messages are unhelpful — it's that we don't know how to read them yet.&lt;/p&gt;

&lt;p&gt;Let's fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a traceback
&lt;/h2&gt;

&lt;p&gt;When Python encounters an error, it shows you a &lt;strong&gt;traceback&lt;/strong&gt; — a trail of breadcrumbs leading to the problem. Here's a typical one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="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="n"&gt;File&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;five&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;add&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&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;unsupported&lt;/span&gt; &lt;span class="n"&gt;operand&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&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;int&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;str&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it &lt;strong&gt;from bottom to top&lt;/strong&gt;. That's the key insight. The last line is the actual error. Everything above it is the path Python took to get there.&lt;/p&gt;

&lt;p&gt;Breaking it down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;TypeError&lt;/code&gt;&lt;/strong&gt; — the category of the error&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unsupported operand type(s) for +: 'int' and 'str'&lt;/code&gt;&lt;/strong&gt; — what happened in plain English&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;File "hello.py", line 2, in add&lt;/code&gt;&lt;/strong&gt; — where it happened&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;return a + b&lt;/code&gt;&lt;/strong&gt; — the exact line of code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this information alone, you can fix most errors without searching the internet. You know &lt;em&gt;what&lt;/em&gt; went wrong, &lt;em&gt;where&lt;/em&gt; it went wrong, and &lt;em&gt;what the code looked like&lt;/em&gt; when it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most common Python errors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  SyntaxError — Python can't understand you
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;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;Hello World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ Missing closing parenthesis
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello.py&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&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;Hello World&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                       &lt;span class="o"&gt;^&lt;/span&gt;
&lt;span class="nb"&gt;SyntaxError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="n"&gt;was&lt;/span&gt; &lt;span class="n"&gt;never&lt;/span&gt; &lt;span class="n"&gt;closed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python found a problem so fundamental it couldn't even run your code. Common causes:&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;# ❌ Missing colon after if/for/while/def
&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;5&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="c1"&gt;# ✅ Correct
&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;5&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Mismatched quotes
&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;

# ✅ Correct
message = &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;World&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;^&lt;/code&gt; character in the error points to where Python got confused. It's not always exactly where &lt;em&gt;you&lt;/em&gt; made the mistake — Python sometimes notices a problem later than where it occurred — but it's a good starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  IndentationError — Python takes whitespace seriously
&lt;/h3&gt;

&lt;p&gt;This one catches everyone. Python uses &lt;strong&gt;indentation to define code blocks&lt;/strong&gt;, not curly braces like many other languages. Four spaces (or one tab, consistently) per level:&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;# ❌ Missing indentation inside if
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;You can vote&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# IndentationError: expected an indented block
&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ Correct
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;18&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;You can vote&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ❌ Inconsistent indentation (mixing spaces and tabs)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="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;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# 4 spaces
&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;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 1 tab — TabError!
&lt;/span&gt;
&lt;span class="c1"&gt;# ✅ Consistent: always 4 spaces
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="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;Hello&lt;/span&gt;&lt;span class="sh"&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="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Configure your editor to &lt;strong&gt;always use spaces&lt;/strong&gt; (4 of them), never tabs. This avoids the mixing problem entirely. VS Code does this by default for Python files.&lt;/p&gt;

&lt;h3&gt;
  
  
  NameError — using something that doesn't exist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ NameError: name 'message' is not defined
&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python executes code top to bottom. If you try to use a variable before defining it, Python has no idea what you're talking about. The fix: define before use.&lt;/p&gt;

&lt;p&gt;Another common variant:&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;user_name&lt;/span&gt; &lt;span class="o"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ NameError: name 'username' is not defined
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spot the difference? &lt;code&gt;user_name&lt;/code&gt; vs &lt;code&gt;username&lt;/code&gt;. &lt;strong&gt;Python is case-sensitive and typo-sensitive.&lt;/strong&gt; &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;Name&lt;/code&gt;, and &lt;code&gt;NAME&lt;/code&gt; are three completely different variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  TypeError — mixing incompatible types
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your age: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# input() always returns a string
&lt;/span&gt;&lt;span class="n"&gt;next_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;          &lt;span class="c1"&gt;# ❌ TypeError: can only concatenate str (not "int") to str
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeError: can only concatenate str (not "int") to str
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix: convert the type explicitly.&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;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your age: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# ✅ Convert to int immediately
&lt;/span&gt;&lt;span class="n"&gt;next_year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;Next year you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll be &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;next_year&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the other direction — trying to print a number directly in a concatenation:&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;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&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;Your score: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# ❌ TypeError
&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;Your score: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Convert int to str
&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;Your score: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;score&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="c1"&gt;# ✅ Better: just use an f-string
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ValueError — right type, wrong value
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ ValueError: invalid literal for int() with base 10: 'hello'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type is correct (&lt;code&gt;int()&lt;/code&gt; expects a string to convert), but the content isn't — &lt;code&gt;"hello"&lt;/code&gt; can't be converted to an integer.&lt;/p&gt;

&lt;p&gt;Common scenario: reading user input and assuming it's a number.&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;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter a number: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# User types "abc"
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                 &lt;span class="c1"&gt;# ❌ ValueError
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll handle this properly when we cover error handling later in the course. For now, just understand what the error means.&lt;/p&gt;

&lt;h3&gt;
  
  
  IndexError — going out of bounds
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&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="mi"&gt;20&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ IndexError: list index out of range
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list has 3 elements (indices 0, 1, 2). Index 5 doesn't exist. Python tells you: you went off the edge.&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;# ✅ Access within bounds
&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;numbers&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="c1"&gt;# 10
&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;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="c1"&gt;# 30 — last element, always safe
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AttributeError — method doesn't exist on that type
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&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;number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ AttributeError: 'int' object has no attribute 'upper'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.upper()&lt;/code&gt; is a string method. Integers don't have it. You're calling something that doesn't exist on that type of object.&lt;/p&gt;

&lt;p&gt;Usually this means either you have the wrong type, or you made a typo in the method name:&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;    &lt;span class="c1"&gt;# ❌ AttributeError: 'str' object has no attribute 'uper'
&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="c1"&gt;# ✅
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Debugging strategies for beginners
&lt;/h2&gt;

&lt;p&gt;Knowing the errors is half the battle. The other half is systematically finding them. Here's the approach that works:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Read the error message completely
&lt;/h3&gt;

&lt;p&gt;Don't glance at the red text and panic. Read it. What type of error? What line? What was the code doing? The answer is almost always in the traceback.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Add &lt;code&gt;print()&lt;/code&gt; to inspect your variables
&lt;/h3&gt;

&lt;p&gt;The simplest debugging tool in existence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="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;DEBUG: price=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, quantity=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;quantity&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="c1"&gt;# Add this temporarily
&lt;/span&gt;    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;quantity&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;DEBUG: total=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;total&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="c1"&gt;# And this
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Print the values right before the line that crashes. Nine times out of ten, you'll immediately see the problem: the variable has the wrong value, the wrong type, or is &lt;code&gt;None&lt;/code&gt; when you expected something else.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Isolate the problem
&lt;/h3&gt;

&lt;p&gt;If you have 50 lines of code and something's broken, you don't need to fix all 50 lines. Find the &lt;em&gt;minimum&lt;/em&gt; code that reproduces the error:&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;# Instead of debugging your entire program, test the specific operation:
&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Isolate this — does it fail here?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Check your assumptions
&lt;/h3&gt;

&lt;p&gt;Most bugs come from an assumption that turns out to be wrong. "I assumed this would be a number." "I assumed the list had at least one element." "I assumed Python counts from 1."&lt;/p&gt;

&lt;p&gt;When your code doesn't behave as expected, ask: what am I assuming here that might not be true?&lt;/p&gt;

&lt;h2&gt;
  
  
  Case sensitivity is not optional
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="o"&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ❌ NameError: name 'name' is not defined
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python sees &lt;code&gt;Name&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt; as completely different things. This is by design and non-negotiable. Pick a convention (&lt;code&gt;snake_case&lt;/code&gt; as per PEP 8) and stick to it.&lt;/p&gt;

&lt;p&gt;Same goes for built-in functions:&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="nf"&gt;print&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;# ✅
&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;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# ❌ NameError — Python booleans are True/False, capital T/F
&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;none&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# ❌ NameError — it's None, not none
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The &lt;code&gt;print()&lt;/code&gt; debugging workflow
&lt;/h2&gt;

&lt;p&gt;Before diving into proper debuggers, get comfortable with this pattern — it works, it's fast, and every professional uses it at some point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Original code that's misbehaving
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add print statements to understand the flow
&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;user_input = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# The !r shows the repr — useful for strings
&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;process_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&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;result = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;!r}&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;The &lt;code&gt;!r&lt;/code&gt; format specifier shows the &lt;code&gt;repr()&lt;/code&gt; of the value, which includes quotes around strings. This makes it easy to spot trailing spaces, newlines (&lt;code&gt;\n&lt;/code&gt;), or other invisible characters that are often the culprit.&lt;/p&gt;




&lt;p&gt;Errors aren't failures — they're Python talking to you. The faster you get comfortable reading tracebacks, the faster you'll write working code. Every error message is a clue, and now you know how to read them.&lt;/p&gt;

&lt;p&gt;In the next tutorial we move into &lt;strong&gt;control flow&lt;/strong&gt;: &lt;code&gt;if&lt;/code&gt;, &lt;code&gt;else&lt;/code&gt;, and &lt;code&gt;elif&lt;/code&gt;. For the first time, your programs will be able to make decisions based on what the user does or what the data looks like.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Intentionally write code with 5 different types of errors from this tutorial (SyntaxError, IndentationError, NameError, TypeError, ValueError). Run each one, read the traceback carefully, and fix it. You'll remember them much better than any list of rules.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Rewriting History with git rebase</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Fri, 03 Jul 2026 12:04:43 +0000</pubDate>
      <link>https://dev.to/fj_palacios/rewriting-history-with-git-rebase-46bp</link>
      <guid>https://dev.to/fj_palacios/rewriting-history-with-git-rebase-46bp</guid>
      <description>&lt;p&gt;Imagine you've spent three days working on a new feature in your &lt;code&gt;feature/login&lt;/code&gt; branch. Meanwhile, someone has pushed four commits to &lt;code&gt;master&lt;/code&gt; that touch parts of the code you're working with. When the time comes to integrate your work, the history is going to look like a plate of spaghetti that nobody wants to untangle.&lt;/p&gt;

&lt;p&gt;There's a cleaner alternative: &lt;code&gt;git rebase&lt;/code&gt;. A tool that rewrites history so everything appears to have happened in a straight line, without the knots that merge leaves behind. But with great power comes responsibility, and there's one rule that, if broken, will make your teammates' lives miserable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is rebase?
&lt;/h2&gt;

&lt;p&gt;To understand rebase, you first need to be clear on what merge does. When you run &lt;code&gt;git merge master&lt;/code&gt; from your &lt;code&gt;feature&lt;/code&gt; branch, Git creates a new &lt;strong&gt;merge commit&lt;/strong&gt; that joins the two histories. The result works, but the history is branched — two lines converging at a point.&lt;/p&gt;

&lt;p&gt;Rebase does something different. It literally &lt;strong&gt;replays your commits&lt;/strong&gt; on top of the target branch. It's as if you started your work today, after all the latest changes to &lt;code&gt;master&lt;/code&gt;, even though you actually started three days ago.&lt;/p&gt;

&lt;p&gt;An analogy: imagine you're writing a report while your manager keeps updating the base draft. With merge, your version and your manager's version are combined into a final document with merge markers. With rebase, it's as if you had read your manager's updated draft before you started writing your section. The result is cleaner, the authorship clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How git rebase works
&lt;/h2&gt;

&lt;p&gt;Here's the mechanics. You have this situation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;      A---B---C  feature
     /
D---E---F---G    master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commits &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, and &lt;code&gt;C&lt;/code&gt; are yours on &lt;code&gt;feature&lt;/code&gt;. Commits &lt;code&gt;F&lt;/code&gt; and &lt;code&gt;G&lt;/code&gt; arrived on &lt;code&gt;master&lt;/code&gt; while you were working.&lt;/p&gt;

&lt;p&gt;When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git switch feature
git rebase master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git does three things in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Finds the common ancestor between &lt;code&gt;feature&lt;/code&gt; and &lt;code&gt;master&lt;/code&gt; (commit &lt;code&gt;E&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Temporarily saves your commits (&lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, &lt;code&gt;C&lt;/code&gt;) as patches&lt;/li&gt;
&lt;li&gt;Replays them one by one on top of &lt;code&gt;G&lt;/code&gt;, the latest commit on &lt;code&gt;master&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              A'--B'--C'  feature
             /
D---E---F---G             master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Commits &lt;code&gt;A'&lt;/code&gt;, &lt;code&gt;B'&lt;/code&gt;, and &lt;code&gt;C'&lt;/code&gt; are new commits (with new hashes) that contain the same changes as &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;B&lt;/code&gt;, and &lt;code&gt;C&lt;/code&gt;, but applied on top of &lt;code&gt;G&lt;/code&gt;. The history is &lt;strong&gt;linear&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden rule of rebase
&lt;/h2&gt;

&lt;p&gt;⚠️ &lt;strong&gt;Never rebase commits that have already been pushed to a public branch.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the most important rule and the one most people break when they're learning. The reason is technical, but the consequence is very concrete.&lt;/p&gt;

&lt;p&gt;Rebase creates new commits. Even though the content is the same, the hashes change. If other people have cloned those commits and you rewrite them with rebase, when they try to pull they'll find an incompatible history. You'll create a mess of duplicate commits and conflicts that are very hard to undo.&lt;/p&gt;

&lt;p&gt;The practical rule is simple: &lt;strong&gt;only rebase branches that are exclusively yours&lt;/strong&gt;. Your local &lt;code&gt;feature&lt;/code&gt; branch that nobody else has touched: yes. &lt;code&gt;master&lt;/code&gt;, &lt;code&gt;develop&lt;/code&gt;, or any branch your teammates have cloned: never.&lt;/p&gt;

&lt;h2&gt;
  
  
  Basic rebase: updating your branch with master's changes
&lt;/h2&gt;

&lt;p&gt;The most common case is wanting to bring your branch up to date with the latest changes from &lt;code&gt;master&lt;/code&gt; before opening a pull request. The workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# First, make sure master is up to date&lt;/span&gt;
git switch master
git pull

&lt;span class="c"&gt;# Then rebase your feature branch on top of master&lt;/span&gt;
git switch feature/login
git rebase master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there are no conflicts, Git replays your commits automatically and you're done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Successfully rebased and updated refs/heads/feature/login.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;feature/login&lt;/code&gt; branch now starts from the latest commit on &lt;code&gt;master&lt;/code&gt;. When you merge or open a pull request, it will be a clean &lt;strong&gt;fast-forward&lt;/strong&gt; with no merge commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resolving conflicts during rebase
&lt;/h2&gt;

&lt;p&gt;Sometimes Git can't replay a commit automatically because there are conflicts. In that case it stops and tells you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Auto-merging src/auth/login.ts
CONFLICT (content): Merge conflict in src/auth/login.ts
error: could not apply a1b2c3d... Add login validation
hint: Resolve all conflicts manually, mark them as resolved with
&lt;/span&gt;&lt;span class="gp"&gt;hint: "git add/rm &amp;lt;conflicted_files&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;", then run "&lt;/span&gt;git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;&lt;span class="s2"&gt;".
&lt;/span&gt;&lt;span class="go"&gt;hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the original state, run "git rebase --abort".
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The process to resolve:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Edit the file&lt;/strong&gt; with the conflict and choose which code to keep&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark the conflict as resolved&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add src/auth/login.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Continue the rebase&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;--continue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git will apply the next commit and so on until it's done. If you get lost at any point or decide it's not worth continuing, you have an escape hatch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git rebase &lt;span class="nt"&gt;--abort&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns your branch exactly to the state it was in before you started the rebase. As if nothing happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rebase vs merge: when to use each
&lt;/h2&gt;

&lt;p&gt;There's no absolute answer. It depends on context and your team's conventions. But there's a useful heuristic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Recommendation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Updating your local branch with &lt;code&gt;master&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rebase&lt;/strong&gt; — cleaner history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integrating a finished feature into &lt;code&gt;main&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Merge&lt;/strong&gt; — preserves the context of the work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared branches with the team&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Merge&lt;/strong&gt; — never rebase&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preparing commits for a clean PR&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Rebase&lt;/strong&gt; — ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Branch with many small "WIP" commits&lt;/td&gt;
&lt;td&gt;Interactive rebase (next lesson)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The philosophy behind rebase is: &lt;em&gt;history should tell the story of the project, not the story of how you developed it&lt;/em&gt;. Some teams prefer to see exactly how the work evolved, and for them merge is more honest. Others prefer a linear, clean history that's easy to read. Neither stance is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking the result
&lt;/h2&gt;

&lt;p&gt;After a successful rebase, you can verify that your history is linear with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;--graph&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;* c3d4e5f (HEAD -&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;feature/login&lt;span class="o"&gt;)&lt;/span&gt; Add remember me option
&lt;span class="go"&gt;* b2c3d4e Add password validation
* a1b2c3d Add login form
* 9f8e7d6 (master) Fix user session timeout
* 8e7d6c5 Add user profile page
* 7d6c5b4 Initial commit
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linear, clean, easy to read. Each of your feature commits applied on top of the latest state of &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;




&lt;p&gt;Rebase is one of those tools that feels a little dizzying at first because it "rewrites history," but once you master it and use it in the right context, it becomes a natural part of your workflow. The key is to internalize the golden rule: your own local branches, yes; public and shared branches, never.&lt;/p&gt;

&lt;p&gt;In the next lesson we'll look at &lt;strong&gt;interactive rebase&lt;/strong&gt; (&lt;code&gt;git rebase -i&lt;/code&gt;), a variant that lets you reorganize, combine, and edit individual commits in your branch before integrating them. It's the perfect tool for cleaning up those "fix typo" and "wip wip" commits before they end up in the history forever.&lt;/p&gt;

&lt;p&gt;Never stop coding!&lt;/p&gt;

</description>
      <category>git</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
