<?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>Docker port mapping: connect your containers to the outside world</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 04 Aug 2026 07:26:12 +0000</pubDate>
      <link>https://dev.to/fj_palacios/docker-port-mapping-connect-your-containers-to-the-outside-world-49j0</link>
      <guid>https://dev.to/fj_palacios/docker-port-mapping-connect-your-containers-to-the-outside-world-49j0</guid>
      <description>&lt;p&gt;In the previous lesson you launched an Nginx container in detached mode. &lt;code&gt;docker ps&lt;/code&gt; showed it as &lt;code&gt;Up&lt;/code&gt;, the &lt;code&gt;PORTS&lt;/code&gt; column said &lt;code&gt;80/tcp&lt;/code&gt;, everything looked fine. You opened the browser, typed &lt;code&gt;http://localhost:80&lt;/code&gt;, and got... nothing. Blank screen. Or a connection refused error.&lt;/p&gt;

&lt;p&gt;Is the container not running? It is. Is Nginx broken? No, it's fine. Is it your network? Your machine? The universe conspiring against you?&lt;/p&gt;

&lt;p&gt;No. It's Docker's &lt;strong&gt;network isolation&lt;/strong&gt; doing exactly what it's supposed to — and you just didn't know about it yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Containers live in their own network bubble
&lt;/h2&gt;

&lt;p&gt;Every Docker container has its own network interface, its own IP address, its own ports — completely separate from the host it runs on. If you're like me when I started, this seems like unnecessary complexity. Why can't it just listen on &lt;code&gt;localhost&lt;/code&gt; like any other process?&lt;/p&gt;

&lt;p&gt;Because that would break the isolation that makes Docker useful. If containers could bind to host ports freely, two containers trying to use port 80 would collide. You couldn't run multiple services on the same host without constant conflicts.&lt;/p&gt;

&lt;p&gt;Think of it this way: each container is like an apartment in a building. It has its own windows (ports), its own internal address, its own doorbell. But from the street, if there's no visible unit number, nobody knows that apartment exists. The &lt;code&gt;80/tcp&lt;/code&gt; you see in &lt;code&gt;docker ps&lt;/code&gt; is the window facing the interior courtyard — nobody outside the building can see it.&lt;/p&gt;

&lt;p&gt;To make it accessible from the street, you need to map it to the building's front facade. That's exactly what &lt;code&gt;-p&lt;/code&gt; does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing ports with &lt;code&gt;-p&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# host_port:container_port&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;Now &lt;code&gt;http://localhost:8080&lt;/code&gt; shows Nginx's welcome page. Traffic flows 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;Your browser → localhost:8080 → Docker → container:80 → Nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker intercepts traffic arriving at port 8080 on the host and forwards it to port 80 inside the container. Nginx doesn't know it's listening on 8080 — from its perspective, it's always been on port 80. Works on my machine, because you're literally remapping which machine "your machine" is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Syntax variants
&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;# Different ports on host and container (most common)&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 nginx

&lt;span class="c"&gt;# Same port on both sides (simpler, but watch for conflicts)&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 nginx

&lt;span class="c"&gt;# Bind only to localhost — not exposed on external interfaces&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 127.0.0.1:8080:80 nginx

&lt;span class="c"&gt;# -P (uppercase): Docker auto-assigns random host ports&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-P&lt;/span&gt; nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The uppercase &lt;code&gt;-P&lt;/code&gt; is handy for quick experiments, but in production it's a nightmare — ports change on every container restart and nobody knows what to connect to. Don't use &lt;code&gt;-P&lt;/code&gt; in production. Yes, that includes the staging environment that's "basically production."&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple ports
&lt;/h3&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;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 80:80 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 443:443 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; my-app &lt;span class="se"&gt;\&lt;/span&gt;
  my-image
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repeat &lt;code&gt;-p&lt;/code&gt; as many times as you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking active port mappings
&lt;/h2&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
&lt;/span&gt;&lt;span class="gp"&gt;a1b2c3d4e5f6   nginx   "/docker-entrypoint.…"   3 seconds ago   Up 3 seconds  0.0.0.0:8080-&amp;gt;&lt;/span&gt;80/tcp   my-nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;PORTS&lt;/code&gt; column now shows &lt;code&gt;0.0.0.0:8080-&amp;gt;80/tcp&lt;/code&gt;. Read it like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0.0.0.0&lt;/code&gt; → listening on all host interfaces&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;8080&lt;/code&gt; → host port&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-&amp;gt;80/tcp&lt;/code&gt; → forwarded to port 80 inside the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you see just &lt;code&gt;80/tcp&lt;/code&gt; without the arrow — like at the beginning — the port is exposed internally but not published. Nobody outside can reach it.&lt;/p&gt;

&lt;p&gt;To see the mappings for a specific container without the noise of &lt;code&gt;docker ps&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;docker port my-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;80/tcp -&amp;gt; 0.0.0.0:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Inspecting the network with &lt;code&gt;docker inspect&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;When you need to understand exactly how a container is connected — internal IP, gateway, full port mappings — &lt;code&gt;docker inspect&lt;/code&gt; has everything:&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 | jq &lt;span class="s1"&gt;'.[0].NetworkSettings'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"IPAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.17.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Ports"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"80/tcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"HostIp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"HostPort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"8080"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Networks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bridge"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"IPAddress"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.17.0.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Gateway"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"172.17.0.1"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container has its own IP (&lt;code&gt;172.17.0.2&lt;/code&gt;) inside Docker's &lt;code&gt;bridge&lt;/code&gt; network. The gateway (&lt;code&gt;172.17.0.1&lt;/code&gt;) is the host — Docker acts as a router between the container's internal network and the outside world.&lt;/p&gt;

&lt;p&gt;Technically you could reach the container from the host using that internal IP directly, no port mapping needed. But that IP changes every time you restart the container and isn't accessible from outside the host. Port mapping is the stable solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple containers on the same host
&lt;/h2&gt;

&lt;p&gt;Here's where it gets interesting. What happens if you launch two Nginx containers at the same time?&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;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; nginx-1 nginx
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; nginx-2 nginx  &lt;span class="c"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;docker: Error response from daemon: driver failed programming external connectivity
on endpoint nginx-2: Bind for 0.0.0.0:8080 failed: port is already allocated.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeling confused? Good — this is actually the system working correctly. Port 8080 on the host is already taken by &lt;code&gt;nginx-1&lt;/code&gt;. The host only has one port 8080, the same way a city only has one Main Street.&lt;/p&gt;

&lt;p&gt;What containers &lt;em&gt;can&lt;/em&gt; share are their internal ports — Docker's network isolation means port 80 in &lt;code&gt;nginx-1&lt;/code&gt; and port 80 in &lt;code&gt;nginx-2&lt;/code&gt; are completely independent. The only thing that can't repeat is the host port:&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;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:80 &lt;span class="nt"&gt;--name&lt;/span&gt; nginx-1 nginx  &lt;span class="c"&gt;# ✅&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8081:80 &lt;span class="nt"&gt;--name&lt;/span&gt; nginx-2 nginx  &lt;span class="c"&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 console"&gt;&lt;code&gt;&lt;span class="go"&gt;CONTAINER ID   IMAGE   PORTS                  NAMES
&lt;/span&gt;&lt;span class="gp"&gt;a1b2c3d4e5f6   nginx   0.0.0.0:8080-&amp;gt;&lt;/span&gt;80/tcp   nginx-1
&lt;span class="gp"&gt;b7c8d9e0f1a2   nginx   0.0.0.0:8081-&amp;gt;&lt;/span&gt;80/tcp   nginx-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;http://localhost:8080&lt;/code&gt; → &lt;code&gt;nginx-1&lt;/code&gt;. &lt;code&gt;http://localhost:8081&lt;/code&gt; → &lt;code&gt;nginx-2&lt;/code&gt;. Same service, two instances, zero conflicts.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if containers need to talk to each other?
&lt;/h3&gt;

&lt;p&gt;One thing that trips people up: port mapping (&lt;code&gt;-p&lt;/code&gt;) is for communication from &lt;em&gt;outside&lt;/em&gt; — your browser, an external API, a client. For containers to communicate with each other, using their internal IPs works technically but is fragile (IPs change on restart). The right solution is &lt;strong&gt;Docker networks&lt;/strong&gt;, which let containers discover each other by name instead of IP. We'll cover that properly in the networking module later in the course.&lt;/p&gt;

&lt;p&gt;For now: &lt;code&gt;-p&lt;/code&gt; is for the outside world, Docker networks are for internal container-to-container communication. Don't mix them up.&lt;/p&gt;




&lt;p&gt;With port mapping you complete the fundamental container cycle: starts isolated, does its work internally, and exposes to the world exactly what you decide — nothing more. It's one of those Docker design decisions that feels like friction at first and becomes one of the things you most appreciate once you've debugged a port collision in production at 2 AM.&lt;/p&gt;

&lt;p&gt;In the next module we jump into &lt;strong&gt;Dockerfiles&lt;/strong&gt;: so far you've been using official images as-is; starting from L6 you'll learn to build your own from scratch.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Launch two Nginx containers on different host ports (&lt;code&gt;8080&lt;/code&gt; and &lt;code&gt;8081&lt;/code&gt;). Verify with &lt;code&gt;docker ps&lt;/code&gt; that both are running and that you can reach each from the browser. Then use &lt;code&gt;docker inspect&lt;/code&gt; with &lt;code&gt;jq&lt;/code&gt; to extract the internal IP of each. Are they different? Are they on the same &lt;code&gt;bridge&lt;/code&gt; network?&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Finding bugs with git bisect</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Mon, 03 Aug 2026 11:16:44 +0000</pubDate>
      <link>https://dev.to/fj_palacios/finding-bugs-with-git-bisect-3n4l</link>
      <guid>https://dev.to/fj_palacios/finding-bugs-with-git-bisect-3n4l</guid>
      <description>&lt;p&gt;There's a bug in production. It wasn't there yesterday. Someone introduced it in one of the last two weeks of commits, but the log has 200 entries — "fix", "wip", "refactor auth", "more fixes", the usual suspects. You could check out commits one by one going backwards, testing each time. You could also read every changed line of code looking for the culprit, or try to guess based on commit messages and gut instinct. All of that is brute-force debugging: slow, exhausting, and dependent on luck in a way that should be illegal.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;git bisect&lt;/code&gt; does binary search on your commit history. Instead of reviewing 200 commits, you review 8. Git picks which one to test each time, you tell it whether the bug is there or not, and in a few steps it points a finger at the exact culprit commit. It's one of those tools where you find out it exists and immediately wonder how you've been debugging without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How binary search works here
&lt;/h2&gt;

&lt;p&gt;Same idea as finding a word in a dictionary: you don't start at page one. You open to the middle, decide whether the word comes before or after, and discard the irrelevant half. Repeat with the remaining half. Seven steps covers a 128-page dictionary.&lt;/p&gt;

&lt;p&gt;Git applies that exact logic to your commit history. You say: "I know the bug didn't exist at &lt;code&gt;v2.0.0&lt;/code&gt;, and I know it exists right now." Git checks out the middle commit, you test, you say &lt;code&gt;good&lt;/code&gt; or &lt;code&gt;bad&lt;/code&gt;, it discards the wrong half. Keep going until only one commit remains — that's the one that introduced the problem.&lt;/p&gt;

&lt;p&gt;With 200 commits, bisect needs at most &lt;strong&gt;8 steps&lt;/strong&gt;. With 1,000, around 10. The math doesn't lie.&lt;/p&gt;

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

&lt;p&gt;Before starting, make sure your working tree is clean. Bisect is going to do several automatic checkouts and doesn't want to find half-finished changes in the way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git stash          &lt;span class="c"&gt;# save any work in progress&lt;/span&gt;
git bisect start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now tell it which state is bad (current) and which was known good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect bad          &lt;span class="c"&gt;# current commit has the bug&lt;/span&gt;
git bisect good v2.0.0  &lt;span class="c"&gt;# this tag/commit was clean&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git calculates how many commits sit between the two points and checks out the middle one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bisecting: 99 revisions left to test after this (roughly 7 steps)
[a3f9c12] Refactor authentication middleware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you test. Does the bug appear? If yes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect bad
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect good
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git discards the wrong half, checks out the next candidate, and waits for your verdict. Repeat. After a few steps you'll see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;a3f9c12 is the first bad commit
commit a3f9c12
Author: Ana García &amp;lt;ana@example.com&amp;gt;
Date:   Mon Apr 14 11:23:01 2026 +0200
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;    Refactor authentication middleware
&lt;span class="err"&gt;
&lt;/span&gt; src/auth/middleware.ts | 47 ++++++++++++++++++++++++++-----------
 1 file changed, 33 insertions(+), 14 deletions(-)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git hands you the exact commit: hash, author, date, message, and changed files. What you do with that information is up to you — bisect has done its job.&lt;/p&gt;

&lt;p&gt;When you're done, whether you found the bug or just want to exit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect reset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This puts you back exactly where you were before starting. Clean slate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using references instead of hashes
&lt;/h2&gt;

&lt;p&gt;You don't need to know the exact hash of the "good" commit. Any reference Git understands works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect good v2.0.0          &lt;span class="c"&gt;# a tag&lt;/span&gt;
git bisect good main~30         &lt;span class="c"&gt;# 30 commits before main&lt;/span&gt;
git bisect good 2026-04-01      &lt;span class="c"&gt;# approximate date&lt;/span&gt;
git bisect good origin/release  &lt;span class="c"&gt;# a remote branch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The approximate date is particularly handy when you know "it worked last week" but don't remember which commit you were on. Git interprets it and finds the nearest commit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating bisect with a script
&lt;/h2&gt;

&lt;p&gt;This is where bisect goes from "useful" to "unreasonably powerful." If you have an automated test that reproduces the bug, you can tell Git to run that test at each step and classify commits on its own. You walk away to get coffee and come back to a solved mystery.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect start
git bisect bad HEAD
git bisect good v2.0.0
git bisect run npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git runs &lt;code&gt;npm test&lt;/code&gt; on each candidate commit. Exit code 0 means the commit is &lt;strong&gt;good&lt;/strong&gt;. Exit codes 1–127 mean &lt;strong&gt;bad&lt;/strong&gt;. Bisect advances on its own until it finds the culprit.&lt;/p&gt;

&lt;p&gt;The script can be anything: a specific test, a bash script that checks a particular behavior, even a &lt;code&gt;curl&lt;/code&gt; against a local endpoint. The only rule is that the exit code must honestly reflect whether the bug is present:&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;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# check-bug.sh&lt;/span&gt;

&lt;span class="c"&gt;# Start the app in background&lt;/span&gt;
node server.js &amp;amp;
&lt;span class="nv"&gt;SERVER_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;1

&lt;span class="c"&gt;# Test if the bug is present&lt;/span&gt;
&lt;span class="nv"&gt;RESULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:3000/api/user/1 | jq &lt;span class="s1"&gt;'.name'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Clean up&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; &lt;span class="nv"&gt;$SERVER_PID&lt;/span&gt;

&lt;span class="c"&gt;# Exit 0 = good (no bug), exit 1 = bad (bug present)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$RESULT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"null"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;1   &lt;span class="c"&gt;# bug present — name should not be null&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0   &lt;span class="c"&gt;# all good&lt;/span&gt;
&lt;span class="k"&gt;fi&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;git bisect run ./check-bug.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's one special exit code worth knowing: &lt;strong&gt;125&lt;/strong&gt;. If your script exits with 125, bisect treats that commit as untestable — it skips it without marking it good or bad. Use this when a commit doesn't compile, has broken dependencies, or otherwise can't be tested. It keeps that commit from contaminating the result.&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;# Example: skip if the project doesn't compile&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; npm run build&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;125   &lt;span class="c"&gt;# skip this commit, can't test it&lt;/span&gt;
&lt;span class="k"&gt;fi
&lt;/span&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Checking bisect progress
&lt;/h2&gt;

&lt;p&gt;If you lose track of where you are in the search:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git bisect log      &lt;span class="c"&gt;# full history of verdicts so far&lt;/span&gt;
git bisect view     &lt;span class="c"&gt;# visual representation of the remaining range&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;git bisect log&lt;/code&gt; is also useful for reproducing a session later. Save its output and use &lt;code&gt;git bisect replay&lt;/code&gt; if you need to repeat the same search from scratch.&lt;/p&gt;

&lt;h2&gt;
  
  
  When bisect isn't the right tool
&lt;/h2&gt;

&lt;p&gt;Bisect is spectacular for regression bugs — something that worked before and stopped working. That's exactly what it was designed for.&lt;/p&gt;

&lt;p&gt;But there are cases where it doesn't help as much:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The bug was always there&lt;/strong&gt;: if no "good" commit exists, bisect has no starting point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The bug is intermittent&lt;/strong&gt;: if behavior changes between runs, &lt;code&gt;good&lt;/code&gt;/&lt;code&gt;bad&lt;/code&gt; verdicts become unreliable and bisect will lead you astray.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tests take too long&lt;/strong&gt;: if each step takes ten minutes, automation loses most of its appeal. Bisect is still useful in manual mode, but the "walk away and come back to an answer" magic evaporates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those cases, &lt;code&gt;git log -S "string"&lt;/code&gt; (finds commits that added or removed a specific string) and &lt;code&gt;git blame&lt;/code&gt; are more direct alternatives. We'll cover both in the next lesson.&lt;/p&gt;




&lt;p&gt;Bisect is one of those commands that looks like magic the first time you see it work. You have 300 commits, five minutes of back-and-forth, and Git hands you the commit, the author, and the changed files. It's genuinely satisfying — especially if the commit author turns out to be someone else (or past-you from three weeks ago, which is practically the same thing).&lt;/p&gt;

&lt;p&gt;In the next tutorial we cover &lt;code&gt;git blame&lt;/code&gt; and the history exploration toolkit: how to find out who touched which line, when, and why — everything you need to make sense of a codebase you've been staring at for five minutes.&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>Recovering lost work with git reflog</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Fri, 31 Jul 2026 17:21:02 +0000</pubDate>
      <link>https://dev.to/fj_palacios/recovering-lost-work-with-git-reflog-4j2o</link>
      <guid>https://dev.to/fj_palacios/recovering-lost-work-with-git-reflog-4j2o</guid>
      <description>&lt;p&gt;If you're like me when I started, you've done a &lt;code&gt;git reset --hard&lt;/code&gt; at least once and immediately felt that specific kind of dread. Did my commits just disappear? Where's the branch I deleted? Can I get it back or did I just nuke two days of work? The short answer is: almost always, yes, you can get it back. The long answer is this lesson.&lt;/p&gt;

&lt;p&gt;Git has a safety net that almost nobody knows about until they desperately need it. It doesn't show up in intro tutorials, it's not mentioned in project onboarding docs, and nothing about the Git interface hints at its existence. It's called &lt;code&gt;reflog&lt;/code&gt;, and once you know it's there, doing "destructive" operations stops being scary.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the reflog
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;reflog&lt;/strong&gt; (short for &lt;em&gt;reference log&lt;/em&gt;) is a private journal that Git silently keeps of every movement made by any reference in your repository. Every commit, checkout, reset, rebase, merge — anything that moves &lt;code&gt;HEAD&lt;/code&gt; — gets logged with a timestamp and the hash it was pointing to at that moment.&lt;/p&gt;

&lt;p&gt;Think of it as your code editor's undo history, except it covers the entire repository. That &lt;code&gt;Ctrl+Z&lt;/code&gt; you can mash twenty times to get back to how things looked ten minutes ago? That's the reflog, but for your whole Git state.&lt;/p&gt;

&lt;p&gt;One thing to be clear about upfront: &lt;strong&gt;the reflog is entirely local&lt;/strong&gt;. It doesn't get pushed, your teammates can't see it, it's not on GitHub. It belongs to your machine. By default, Git keeps entries for 90 days before the garbage collector cleans them up. If the disaster happened yesterday, you're fine. If it was four months ago — well, here's where it gets interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Viewing the reflog
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;a3f9c12 &lt;span class="o"&gt;(&lt;/span&gt;HEAD -&amp;gt; main&lt;span class="o"&gt;)&lt;/span&gt; HEAD@&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;: commit: Add user authentication
7b2d8e4 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;: rebase &lt;span class="o"&gt;(&lt;/span&gt;finish&lt;span class="o"&gt;)&lt;/span&gt;: returning to refs/heads/main
7b2d8e4 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;: rebase &lt;span class="o"&gt;(&lt;/span&gt;pick&lt;span class="o"&gt;)&lt;/span&gt;: Fix null pointer &lt;span class="k"&gt;in &lt;/span&gt;payment service
3c1e9f6 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;3&lt;span class="o"&gt;}&lt;/span&gt;: rebase &lt;span class="o"&gt;(&lt;/span&gt;start&lt;span class="o"&gt;)&lt;/span&gt;: checkout main
f4a7b3d HEAD@&lt;span class="o"&gt;{&lt;/span&gt;4&lt;span class="o"&gt;}&lt;/span&gt;: reset: moving to HEAD~3
9e8c2a1 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;5&lt;span class="o"&gt;}&lt;/span&gt;: commit: WIP: refactor login flow
b1d4f8e HEAD@&lt;span class="o"&gt;{&lt;/span&gt;6&lt;span class="o"&gt;}&lt;/span&gt;: commit: Add password validation
c2e5a9b HEAD@&lt;span class="o"&gt;{&lt;/span&gt;7&lt;span class="o"&gt;}&lt;/span&gt;: checkout: moving from feature/auth to main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line has three parts: the hash &lt;code&gt;HEAD&lt;/code&gt; was pointing to at that moment, the relative reference (&lt;code&gt;HEAD@{n}&lt;/code&gt;, where &lt;code&gt;n&lt;/code&gt; is how many positions back), and the description of what caused the move. Your entire movement history, readable at a glance.&lt;/p&gt;

&lt;p&gt;You can filter by branch or time if the reflog gets long:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog show feature/auth       &lt;span class="c"&gt;# reflog of a specific branch&lt;/span&gt;
git reflog &lt;span class="nt"&gt;--since&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"2 days ago"&lt;/span&gt;    &lt;span class="c"&gt;# only recent entries&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recovering commits lost after a reset
&lt;/h2&gt;

&lt;p&gt;The classic scenario: you ran &lt;code&gt;git reset --hard&lt;/code&gt; pointing at the wrong commit. Or the right one, but without saving what you had. It feels like emptying the recycle bin and remembering three seconds later what was in it. The key difference: Git doesn't actually empty that bin right away.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
&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;f4a7b3d &lt;span class="o"&gt;(&lt;/span&gt;HEAD -&amp;gt; main&lt;span class="o"&gt;)&lt;/span&gt; HEAD@&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;: reset: moving to HEAD~3
9e8c2a1 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;: commit: WIP: refactor login flow
b1d4f8e HEAD@&lt;span class="o"&gt;{&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;: commit: Add password validation
c2e5a9b HEAD@&lt;span class="o"&gt;{&lt;/span&gt;3&lt;span class="o"&gt;}&lt;/span&gt;: commit: Add login form
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reset is at &lt;code&gt;HEAD@{0}&lt;/code&gt;. The commits you "lost" are sitting at &lt;code&gt;HEAD@{1}&lt;/code&gt;, &lt;code&gt;HEAD@{2}&lt;/code&gt;, and &lt;code&gt;HEAD@{3}&lt;/code&gt;. Hash &lt;code&gt;9e8c2a1&lt;/code&gt; is the last state you had before things went sideways. Two paths forward:&lt;/p&gt;

&lt;h3&gt;
  
  
  Restore the full state
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; 9e8c2a1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your branch points back to the last commit you had. The three commits are back. Git had kept them all along — you'd only lost the reference, not the data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a branch from that point (the cautious route)
&lt;/h3&gt;

&lt;p&gt;If you'd rather not move your current branch until you're sure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git branch recovered-work 9e8c2a1
git switch recovered-work
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect, verify everything's there, then decide whether to merge or cherry-pick. No rush.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovering a deleted branch
&lt;/h2&gt;

&lt;p&gt;You deleted &lt;code&gt;feature/new-api&lt;/code&gt; with &lt;code&gt;git branch -D&lt;/code&gt;, convinced everything had landed in &lt;code&gt;main&lt;/code&gt;. Then you check &lt;code&gt;main&lt;/code&gt; and a commit is missing. The branch is gone — but its commits aren't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
&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;a3f9c12 &lt;span class="o"&gt;(&lt;/span&gt;HEAD -&amp;gt; main&lt;span class="o"&gt;)&lt;/span&gt; HEAD@&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;: checkout: moving from feature/new-api to main
e7b3c9d HEAD@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;: commit: Add rate limiting to API endpoints
d6a2b8f HEAD@&lt;span class="o"&gt;{&lt;/span&gt;2&lt;span class="o"&gt;}&lt;/span&gt;: commit: Implement new API versioning
c5f1a7e HEAD@&lt;span class="o"&gt;{&lt;/span&gt;3&lt;span class="o"&gt;}&lt;/span&gt;: checkout: moving from main to feature/new-api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just before the final checkout (&lt;code&gt;HEAD@{0}&lt;/code&gt;), you were at &lt;code&gt;HEAD@{1}&lt;/code&gt; with hash &lt;code&gt;e7b3c9d&lt;/code&gt; — the last commit on the deleted branch. One line to 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;git branch feature/new-api e7b3c9d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch is back with all its commits. Git doesn't delete commits when it deletes branches; it only deletes the pointer. The reflog gives you the pointer back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recovering from a bad rebase
&lt;/h2&gt;

&lt;p&gt;This is where things get genuinely gnarly. Interactive rebase is one of Git's most powerful operations, and also one of the most creative ways to end up in a state you don't fully understand. Commits squashed that shouldn't have been, messages rewritten incorrectly, content mixed between commits. You're not the first. You won't be the last.&lt;/p&gt;

&lt;p&gt;The reflog captures the exact state from the moment the rebase started. Find 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 reflog | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"rebase (start)"&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;3c1e9f6 HEAD@&lt;span class="o"&gt;{&lt;/span&gt;8&lt;span class="o"&gt;}&lt;/span&gt;: rebase &lt;span class="o"&gt;(&lt;/span&gt;start&lt;span class="o"&gt;)&lt;/span&gt;: checkout main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That hash is the snapshot from just before the rebase began. Return to 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 reset &lt;span class="nt"&gt;--hard&lt;/span&gt; 3c1e9f6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ If you already pushed the branch after the rebase, you'll need &lt;code&gt;git push --force-with-lease&lt;/code&gt; to overwrite the remote. Only do this on branches that are exclusively yours — force-pushing &lt;code&gt;main&lt;/code&gt; or a shared branch would export your problem to everyone on the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ORIG_HEAD trick
&lt;/h2&gt;

&lt;p&gt;Git, being Git, quietly saves a special reference called &lt;code&gt;ORIG_HEAD&lt;/code&gt; every time it makes a drastic move: a merge, a rebase, a reset. No announcement. No prompt asking if you want it. It just does it (this time, thankfully).&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 last merge&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; ORIG_HEAD

&lt;span class="c"&gt;# Undo last rebase&lt;/span&gt;
git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; ORIG_HEAD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The limitation: &lt;code&gt;ORIG_HEAD&lt;/code&gt; gets overwritten by each new drastic operation. It's perfect for immediate regret — you just did the merge/rebase and don't like the result — but useless if you've done more things since. That's what the reflog is for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding a commit by its content
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't know exactly when you lost something. You just have a vague clue: a filename, a partial commit message, a rough date. The reflog pairs well with &lt;code&gt;git log&lt;/code&gt; to track down the exact commit.&lt;/p&gt;

&lt;p&gt;If you remember part of the message:&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;--all&lt;/span&gt; &lt;span class="nt"&gt;--oneline&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"rate limiting"&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;e7b3c9d Add rate limiting to API endpoints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--all&lt;/code&gt; flag is the key here. It includes commits no longer referenced by any active branch — exactly the "orphan" commits that appear when you delete a branch or do a reset. If that commit ever existed on your machine, &lt;code&gt;--all&lt;/code&gt; will find it. Don't stress about scrolling through the reflog line by line if you have any clue about the message — &lt;code&gt;git log --all&lt;/code&gt; is faster.&lt;/p&gt;

&lt;p&gt;To inspect any reflog entry before restoring 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 show HEAD@&lt;span class="o"&gt;{&lt;/span&gt;5&lt;span class="o"&gt;}&lt;/span&gt;           &lt;span class="c"&gt;# content of that entry&lt;/span&gt;
git show HEAD@&lt;span class="o"&gt;{&lt;/span&gt;5&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="nt"&gt;--stat&lt;/span&gt;    &lt;span class="c"&gt;# just the changed files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  "I dropped the wrong stash"
&lt;/h3&gt;

&lt;p&gt;Stashes have their own reflog. If you ran &lt;code&gt;git stash drop&lt;/code&gt; by mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog show stash
&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;9a1b2c3 stash@&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;: WIP on main: current stash
f8e7d6c stash@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;: WIP on feature/auth: login work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it was recent, it's probably still there. Recover it 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 stash apply f8e7d6c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  "I just force-pushed to main"
&lt;/h3&gt;

&lt;p&gt;This is a genuine emergency (it should not have happened). First, find in the reflog the commit &lt;code&gt;main&lt;/code&gt; was pointing to before the force push:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reflog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the hash from before the mistake, reset to it, and push again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git reset &lt;span class="nt"&gt;--hard&lt;/span&gt; &amp;lt;hash-before-the-mistake&amp;gt;
git push &lt;span class="nt"&gt;--force-with-lease&lt;/span&gt; origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then tell your team to &lt;code&gt;git fetch&lt;/code&gt; and realign their local branches. This is one of those situations where the time between "I realized" and "I fixed it" genuinely matters.&lt;/p&gt;




&lt;p&gt;The reflog is proof that Git is, in practice, nearly impossible to use in a truly unrecoverable way. "I lost my work" in Git almost always means "I lost the reference to my work" — the commits are still there, waiting to be found.&lt;/p&gt;

&lt;p&gt;The rule that would have saved me several scary moments early on: &lt;strong&gt;when something goes wrong in Git, the first thing you do is &lt;code&gt;git reflog&lt;/code&gt;&lt;/strong&gt;. Before panicking. Before opening Stack Overflow. Before telling anyone what happened. Open the reflog, find the state you want, go back to it. Almost every time, it's that simple.&lt;/p&gt;

&lt;p&gt;In the next tutorial we'll cover &lt;code&gt;git bisect&lt;/code&gt;, which lets you run a binary search through your commit history to find exactly which commit introduced a bug — and yes, you can automate it with a script so Git does the bisecting while you drink your coffee.&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 Limitations and How to Detect Hallucinations</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Thu, 30 Jul 2026 17:40:18 +0000</pubDate>
      <link>https://dev.to/fj_palacios/ai-limitations-and-how-to-detect-hallucinations-3n5e</link>
      <guid>https://dev.to/fj_palacios/ai-limitations-and-how-to-detect-hallucinations-3n5e</guid>
      <description>&lt;p&gt;This has either already happened to you or it's going to. You ask the AI to help with something concrete — parsing a JSON response using a library you've been using for years. It gives you the code. The syntax looks right, the names make sense, the comments are clear. You copy, paste, run.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AttributeError: module 'requests' has no attribute 'get_json'&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;requests.get_json&lt;/code&gt; doesn't exist. It never existed. But the AI served it up with the same confidence it would use for &lt;code&gt;response.json()&lt;/code&gt; — the function that's actually real — with no warning, no footnote, no "hey, I'm not totally sure about this one."&lt;/p&gt;

&lt;p&gt;That's a hallucination. And here's where it gets fun.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AI lies with such conviction
&lt;/h2&gt;

&lt;p&gt;In tutorial 2, we established that LLMs predict the next word. Hallucinations flow directly from that mechanic.&lt;/p&gt;

&lt;p&gt;The model doesn't have access to a database of verified facts. It doesn't consult the official documentation before answering you. There's no internal module that says "wait, is this actually true?" What it has is a statistical model trained to generate text that sounds coherent and plausible.&lt;/p&gt;

&lt;p&gt;When you ask about a function that doesn't exist, it isn't deliberately lying — it generates text that has the &lt;em&gt;shape&lt;/em&gt; of a correct answer. It's seen thousands of responses about library functions and it "knows" what a correct answer looks like. The content may be invented. The form is always impeccable. That's the trap.&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;# What you asked for:
# "How do I parse a JSON response body with requests?"
&lt;/span&gt;
&lt;span class="c1"&gt;# What the AI might give you (invented with full confidence):
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ❌ Does not exist
&lt;/span&gt;
&lt;span class="c1"&gt;# What's actually correct:
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.example.com/data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ Method on the Response object
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both versions have the same structure, the same descriptive names, the same "this works" energy. Only one of them actually works. And the one that doesn't — if you're like me when I first started using AI for code — is exactly the one you copy without blinking because it looks so reasonable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three types of AI error
&lt;/h2&gt;

&lt;p&gt;Not all hallucinations are the same. Recognizing the type gives you a clue about where to look.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invented information
&lt;/h3&gt;

&lt;p&gt;The model generates something that never existed: a function, a parameter, a class, an endpoint. Like &lt;code&gt;requests.get_json&lt;/code&gt;. This is the friendliest type — it fails fast and loudly. The code doesn't run, the error is obvious.&lt;/p&gt;

&lt;p&gt;The typical pattern: names that sound right but don't appear in the official documentation. If a function name the AI gave you doesn't show up in your editor's autocomplete or in the docs... suspect it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Outdated information
&lt;/h3&gt;

&lt;p&gt;This one is more complicated. The model has a &lt;strong&gt;training cutoff date&lt;/strong&gt; — the point when they stopped feeding it new data. Anything that happened after that date simply doesn't exist for it. It's not ignoring it — it genuinely doesn't know.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;APIs that went through a major version change (Flask 2.x → 3.x, Django 4.x → 5.x)&lt;/li&gt;
&lt;li&gt;Deprecated methods that were removed in recent versions&lt;/li&gt;
&lt;li&gt;Security patterns that turned out to be vulnerable&lt;/li&gt;
&lt;li&gt;New approaches to things that used to be done differently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What makes this dangerous is that the code &lt;strong&gt;works&lt;/strong&gt; — just on an older version of the library. On your project, with your current &lt;code&gt;requirements.txt&lt;/code&gt;, it might fail silently, behave differently, or introduce a known vulnerability. Works on my machine™ taken to its logical extreme.&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;# What the AI might recommend (older Flask, perfectly valid in its day):
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/login&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&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;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;force&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;# What you might want to be using today:
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/login&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&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;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# force is no longer needed in Flask 3.x for JSON content-type
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither throws an error. But one can produce unexpected behavior depending on your version. The AI doesn't know — it genuinely doesn't know which version you're running unless you tell it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context blindness
&lt;/h3&gt;

&lt;p&gt;This is the subtlest, the most common in real work, and the hardest to catch. The model doesn't know your codebase, your team's conventions, your architecture's constraints. It makes assumptions — the most statistically reasonable ones. Which aren't necessarily yours.&lt;/p&gt;

&lt;p&gt;Imagine asking for help adding authentication to an endpoint. The model assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're using JWT (reasonable — it's the most common approach)&lt;/li&gt;
&lt;li&gt;You have a &lt;code&gt;users&lt;/code&gt; table in your database (reasonable)&lt;/li&gt;
&lt;li&gt;The token header is called &lt;code&gt;Authorization&lt;/code&gt; (it's the standard)&lt;/li&gt;
&lt;li&gt;You have an authentication middleware available (might not be true)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those assumptions doesn't apply to your system, the generated code might compile, pass a quick review, and fail in production in non-obvious ways.&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;# What the AI assumes:
&lt;/span&gt;&lt;span class="nd"&gt;@require_auth&lt;/span&gt;          &lt;span class="c1"&gt;# Do you have this decorator? Is that what it's called in your project?
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/api/v1/users/profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_profile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;  &lt;span class="c1"&gt;# Do you have g.current_user? With that exact name?
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# What your project might actually have:
&lt;/span&gt;&lt;span class="nd"&gt;@login_required&lt;/span&gt;        &lt;span class="c1"&gt;# Your decorator has a different name
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/api/v1/users/profile&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_profile&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Your project uses sessions, not JWT
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error here isn't that the AI is "wrong" in the abstract. It's answering your question while assuming a context that isn't yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  The warning signs to learn
&lt;/h2&gt;

&lt;p&gt;Over time, you develop a nose for this. How do I know this output is suspicious? How do I tell what it knows from what it's inventing? In the meantime, here are the most common red flags:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Names you don't recognize.&lt;/strong&gt; If the AI gives you a function, method, or class name that doesn't show up in your autocomplete or the official docs, stop before going further. It's not that you're behind — it might be straight-up invented.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maximum confidence on specific or poorly-documented topics.&lt;/strong&gt; The more obscure the topic, the higher the risk. The AI has far more training data about Flask than about your internal company library, or about the standard ORM versus the tuned version your team has been running since 2019 (and nobody ever properly documented, let's be honest).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Specific version numbers without a source.&lt;/strong&gt; "This works since version 3.2" — where did that number come from? If it can't cite a source, treat the claim as unverified.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code with the right shape that fails at runtime.&lt;/strong&gt; &lt;code&gt;ModuleNotFoundError&lt;/code&gt;, &lt;code&gt;AttributeError&lt;/code&gt;, &lt;code&gt;ImportError&lt;/code&gt; after running "looks-correct" code — classic signature of an API hallucination.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Answers that shift when you rephrase the question.&lt;/strong&gt; If you ask the same thing two different ways and get contradictory answers, the AI doesn't have certainty — it's generating plausible versions, not verified facts.&lt;/p&gt;

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

&lt;p&gt;This isn't theory. It's what keeps AI-generated code from becoming a production problem. Five steps, in order, before merging any block you didn't write yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Does it execute without errors?
   → Run it. An immediate stack trace = start hunting for the invented name

2. Do the functions it uses exist in the official documentation?
   → Search for the exact name in the docs for your version, not the latest

3. Is the syntax valid for your version?
   → Check the changelog if something looks "weird" or suspiciously new

4. Do the edge cases work?
   → Null, empty string, empty list, negative number — test the ones that matter to you

5. Do the assumptions it makes apply to your context?
   → Naming conventions, project structure, available libraries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 2 is the most commonly skipped — and the one that lets the most hallucinations through. "It works" doesn't mean "it uses real functions." It might work in your environment with your version of the library and break in CI, in production, or on another developer's machine. Don't ask me how I know.&lt;/p&gt;

&lt;p&gt;Verification doesn't need to be exhaustive for every line. For a 10-line block doing something familiar, a quick glance is enough. For authentication code, for code that touches the database, for code that handles money or permissions — careful verification, no exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "show me the documentation" tactic
&lt;/h2&gt;

&lt;p&gt;When you suspect a hallucination, there's a move that works surprisingly well: ask the AI to cite its source.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Which version introduced the strict_mode parameter for json.loads()?
Can you give me the exact link to that section in the official documentation?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When it's hallucinating, it usually can't give you a valid URL — or it gives you one that 404s. If it can give you the exact URL and the correct version number, the information is probably real.&lt;/p&gt;

&lt;p&gt;Fair warning: it's not foolproof. Newer models can generate URLs that look completely legitimate and aren't (because &lt;em&gt;inventing a plausible-sounding URL&lt;/em&gt; is also just predicting coherent text). But it adds a useful friction layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "Can you cite the section of the requests docs that covers get_json()?"
AI:  "My information may be outdated. Let me check..."
     [invented URL that 404s]
     → Stop. Verify before continuing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to compensate for context blindness
&lt;/h2&gt;

&lt;p&gt;The antidote is giving it context. This sounds obvious, but the practical difference between a context-free prompt and a context-rich one is enormous — not because the model gets smarter, but because it has to make fewer assumptions.&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;# ❌ No context:
&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help me add caching to this database query function&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# ✅ With context:
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;I have this function in Python 3.12 with SQLAlchemy 2.0:

[function code]

I want to add caching with Redis. In our project we already use redis-py 5.x
and have a client available as redis_client (injected singleton via DI).
We follow the repository pattern — caching should be transparent to the business layer.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the second prompt, the AI knows you already have Redis, which version, what the client is called, and which architectural pattern to respect. Far fewer assumptions to make — and the ones it does make are far more likely to be correct.&lt;/p&gt;

&lt;p&gt;The practical rule: before asking for help with production code, ask yourself what a new colleague would need to know to help you without making mistakes. That's exactly what the AI needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the AI can never know
&lt;/h2&gt;

&lt;p&gt;There are things that won't be in any LLM's training context, no matter when it was trained:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team's specific conventions&lt;/li&gt;
&lt;li&gt;Known bugs in your internal services&lt;/li&gt;
&lt;li&gt;The architectural decisions you made two years ago and why&lt;/li&gt;
&lt;li&gt;The business context behind why something works "strangely"&lt;/li&gt;
&lt;li&gt;The implicit dependencies between modules that nobody ever documented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these things, AI is a starting point, not an endpoint. It can generate the structure, give you the general pattern, save you the boilerplate. The adjustment to your project's context — that part is yours.&lt;/p&gt;

&lt;p&gt;This isn't a criticism of AI. It's the nature of the problem. A senior developer joining your team also doesn't know any of this on day one. The difference is that the human can ask proactively and learn over time. The AI only knows what you show it in the current context window.&lt;/p&gt;




&lt;p&gt;You now have the full map of why AI fails and how to catch it before it reaches production. The next step is putting this into practice — and for that, you need the tools. In the next tutorial, we install and configure &lt;strong&gt;opencode&lt;/strong&gt;, the agent we'll use for the rest of the course, and run the first real AI-assisted coding session on an actual project.&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>Insert Mode in depth: more than you think</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 29 Jul 2026 09:54:53 +0000</pubDate>
      <link>https://dev.to/fj_palacios/insert-mode-in-depth-more-than-you-think-1c1m</link>
      <guid>https://dev.to/fj_palacios/insert-mode-in-depth-more-than-you-think-1c1m</guid>
      <description>&lt;p&gt;After the previous tutorial, you might have the impression that Insert mode is the poor relation of Vim. Normal mode has the grammar, the operators, the &lt;code&gt;.&lt;/code&gt; command. Insert mode is where you... type. Like in any other editor.&lt;/p&gt;

&lt;p&gt;And that's partially true. But there's a real difference between someone who uses Insert mode the way they'd use VS Code and someone who understands how it actually works. That difference is what determines whether your edits are precise, composable, and repeatable with &lt;code&gt;.&lt;/code&gt; — or a mess of keystrokes that "kind of work."&lt;/p&gt;

&lt;p&gt;This tutorial is the bridge between "I can survive in Vim" and "I'm starting to feel at home."&lt;/p&gt;

&lt;h2&gt;
  
  
  Nine ways to enter Insert mode
&lt;/h2&gt;

&lt;p&gt;The most familiar is &lt;code&gt;i&lt;/code&gt;. But Vim has nine — and each one exists for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  The basics: where to land the cursor
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;Where it starts inserting&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Before&lt;/strong&gt; the cursor (the default)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;After&lt;/strong&gt; the cursor (append)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;I&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At the &lt;strong&gt;start&lt;/strong&gt; of the line (first non-blank character)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;A&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At the &lt;strong&gt;end&lt;/strong&gt; of the line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens a &lt;strong&gt;new line below&lt;/strong&gt; and enters Insert mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Opens a &lt;strong&gt;new line above&lt;/strong&gt; and enters Insert mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference between &lt;code&gt;i&lt;/code&gt; and &lt;code&gt;a&lt;/code&gt; seems minimal — one character apart — but it matters. If the cursor is on the &lt;code&gt;f&lt;/code&gt; in &lt;code&gt;foo&lt;/code&gt; and you want to type something before it, use &lt;code&gt;i&lt;/code&gt;. If you want to type after the whole word &lt;code&gt;foo&lt;/code&gt;, navigate to the &lt;code&gt;o&lt;/code&gt; and use &lt;code&gt;a&lt;/code&gt;. In practice, &lt;code&gt;a&lt;/code&gt; comes up a lot when adding something to the end of an expression.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;A&lt;/code&gt; deserves a special mention: jumping to the end of the line and entering Insert mode is so common it has its own key. The sequence &lt;code&gt;$a&lt;/code&gt; works, but &lt;code&gt;A&lt;/code&gt; is a single command.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;o&lt;/code&gt; and &lt;code&gt;O&lt;/code&gt; are hidden gems. Opening a new line and entering Insert mode at the same time is what you do 90% of the time when writing new code. In VS Code you'd press &lt;code&gt;End&lt;/code&gt; + &lt;code&gt;Enter&lt;/code&gt;. In Vim it's &lt;code&gt;o&lt;/code&gt;. One character.&lt;/p&gt;

&lt;h3&gt;
  
  
  The change variants: delete and enter
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&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;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes the &lt;strong&gt;character under the cursor&lt;/strong&gt; and enters Insert&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;Deletes the &lt;strong&gt;entire line&lt;/strong&gt; and enters Insert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deletes the &lt;strong&gt;word&lt;/strong&gt; and enters Insert mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You saw &lt;code&gt;s&lt;/code&gt; and &lt;code&gt;S&lt;/code&gt; briefly in the previous tutorial as shortcuts for &lt;code&gt;cl&lt;/code&gt; and &lt;code&gt;cc&lt;/code&gt;. The key insight is that they're not just shortcuts — they're the semantic way of saying "replace this." &lt;code&gt;s&lt;/code&gt; means "substitute this character," &lt;code&gt;S&lt;/code&gt; means "substitute this line."&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cw&lt;/code&gt; isn't really an "entry" command on its own — it's the &lt;code&gt;c&lt;/code&gt; operator with the &lt;code&gt;w&lt;/code&gt; motion. But it's so common it belongs on this list. You'll find that &lt;code&gt;c&lt;/code&gt; followed by any motion is the dominant pattern for editing existing text: &lt;code&gt;ce&lt;/code&gt;, &lt;code&gt;c$&lt;/code&gt;, &lt;code&gt;c^&lt;/code&gt;, &lt;code&gt;ciw&lt;/code&gt; (more on text objects in future tutorials).&lt;/p&gt;

&lt;h2&gt;
  
  
  Inside Insert mode: it's not a dead zone
&lt;/h2&gt;

&lt;p&gt;Once you're in Insert mode, most people just type and press &lt;code&gt;Esc&lt;/code&gt;. But there are shortcuts that work inside Insert mode that are worth their weight in gold:&lt;/p&gt;

&lt;h3&gt;
  
  
  Deleting without leaving
&lt;/h3&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;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete the previous character (same as &lt;code&gt;Backspace&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete the &lt;strong&gt;previous word&lt;/strong&gt; (very useful when you mistype)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+u&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete &lt;strong&gt;everything you've typed&lt;/strong&gt; on the current line&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Ctrl+w&lt;/code&gt; is the one I use most. You type &lt;code&gt;calculate_totlll&lt;/code&gt; and instead of mashing &lt;code&gt;Backspace&lt;/code&gt; three times, &lt;code&gt;Ctrl+w&lt;/code&gt; wipes the whole word at once. It'll be in your muscle memory within three days.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ctrl+u&lt;/code&gt; looks aggressive, but it's perfect when you've completely gone off the rails and want to restart the line from scratch without leaving Insert mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;Ctrl+o&lt;/code&gt; trick
&lt;/h3&gt;

&lt;p&gt;This one is the most interesting. &lt;code&gt;Ctrl+o&lt;/code&gt; momentarily drops you into Normal mode, executes a single command, and returns to Insert mode.&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;" You're typing, cursor is in the middle of a line&lt;/span&gt;
&lt;span class="c"&gt;" You want to jump to the start of the line without leaving Insert mode&lt;/span&gt;
Ctrl&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="k"&gt;o&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; ^    " → goes &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="k"&gt;first&lt;/span&gt; non&lt;span class="p"&gt;-&lt;/span&gt;blank &lt;span class="nb"&gt;and&lt;/span&gt; returns &lt;span class="k"&gt;to&lt;/span&gt; Insert &lt;span class="k"&gt;mode&lt;/span&gt;
Ctrl&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="k"&gt;o&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; $    " → goes &lt;span class="k"&gt;to&lt;/span&gt; end of &lt;span class="nb"&gt;line&lt;/span&gt; &lt;span class="nb"&gt;and&lt;/span&gt; returns &lt;span class="k"&gt;to&lt;/span&gt; Insert &lt;span class="k"&gt;mode&lt;/span&gt;
Ctrl&lt;span class="p"&gt;+&lt;/span&gt;&lt;span class="k"&gt;o&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; dw   " → deletes &lt;span class="k"&gt;a&lt;/span&gt; word &lt;span class="nb"&gt;and&lt;/span&gt; returns &lt;span class="k"&gt;to&lt;/span&gt; Insert &lt;span class="k"&gt;mode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't obsess over &lt;code&gt;Ctrl+o&lt;/code&gt; at the beginning. But once you've been using Vim for weeks and hit that situation where you need to do something quick without leaving Insert mode, you'll know it's there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Native autocomplete
&lt;/h3&gt;

&lt;p&gt;Vim has native autocomplete inside Insert mode, with no plugins:&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;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Next suggestion — based on words in the buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Previous suggestion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+x Ctrl+f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Autocomplete &lt;strong&gt;file paths&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Ctrl+x Ctrl+l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Autocomplete &lt;strong&gt;entire lines&lt;/strong&gt; from the buffer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;Ctrl+n&lt;/code&gt; / &lt;code&gt;Ctrl+p&lt;/code&gt; autocomplete looks at all words across your open buffers. It's not the smart LSP autocomplete — but it works with zero configuration, even with vanilla Vim, even on a remote SSH server with no internet.&lt;/p&gt;

&lt;p&gt;LazyVim already ships with a more powerful autocomplete plugin (nvim-cmp), so in your current setup you probably won't need &lt;code&gt;Ctrl+n&lt;/code&gt; daily. But knowing it exists saves you the day you're on a bare remote server with unconfigured vim.&lt;/p&gt;

&lt;h2&gt;
  
  
  The golden rule of Insert mode
&lt;/h2&gt;

&lt;p&gt;Here's the difference between someone who uses Vim and someone who uses it well:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enter Insert mode to make ONE logical change. Then leave.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not: &lt;code&gt;i&lt;/code&gt;, type three words, delete two, rewrite, add a parenthesis, remove the parenthesis...&lt;/p&gt;

&lt;p&gt;Yes: &lt;code&gt;i&lt;/code&gt;, type the word, &lt;code&gt;Esc&lt;/code&gt;. &lt;code&gt;a&lt;/code&gt;, add the parenthesis, &lt;code&gt;Esc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Why? Because the &lt;code&gt;.&lt;/code&gt; command replays the &lt;strong&gt;entire last change&lt;/strong&gt;. If you enter Insert mode, do ten different things, and leave — &lt;code&gt;.&lt;/code&gt; will replay all that chaos. If you make one clean change and leave, &lt;code&gt;.&lt;/code&gt; will replay exactly that.&lt;/p&gt;

&lt;p&gt;The granularity of your change is what makes &lt;code&gt;.&lt;/code&gt; a surgical editing tool or a confetti cannon.&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;" ✅ Clean, repeatable change&lt;/span&gt;
&lt;span class="k"&gt;cw&lt;/span&gt;                " &lt;span class="k"&gt;change&lt;/span&gt; one word
newName           " &lt;span class="nb"&gt;type&lt;/span&gt; the replacement
Esc               " leave Insert &lt;span class="k"&gt;mode&lt;/span&gt; → one &lt;span class="k"&gt;change&lt;/span&gt; registered
&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; the &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; exactly the same thing

&lt;span class="c"&gt;" ❌ Entangled change, hard to repeat&lt;/span&gt;
&lt;span class="k"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;first&lt;/span&gt; this then that &lt;span class="nb"&gt;and&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="nb"&gt;and&lt;/span&gt; rewrite Esc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This also applies to &lt;code&gt;o&lt;/code&gt; and &lt;code&gt;O&lt;/code&gt;. If you open a new line, write what you need, and leave — that's a repeatable change. If you open a new line and start reorganizing the entire function logic... it's not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Arrow keys in Insert mode (and why not to use them)
&lt;/h2&gt;

&lt;p&gt;Technically you can use arrow keys inside Insert mode to navigate. Vim allows it.&lt;/p&gt;

&lt;p&gt;Don't.&lt;/p&gt;

&lt;p&gt;The issue isn't that it's "wrong." It's that it costs time and messes up the cursor position for the &lt;code&gt;.&lt;/code&gt; command. Every time you move with arrows inside Insert mode, you're in "I don't know exactly what change I'm making" territory. Leave to Normal mode, navigate with &lt;code&gt;hjkl&lt;/code&gt; or word movements, come back to the exact spot, enter Insert mode.&lt;/p&gt;

&lt;p&gt;This sounds rigid at first. Two weeks in, you won't even think about it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nine Insert mode entries&lt;/strong&gt;: &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;I&lt;/code&gt;, &lt;code&gt;A&lt;/code&gt;, &lt;code&gt;o&lt;/code&gt;, &lt;code&gt;O&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;S&lt;/code&gt;, &lt;code&gt;c&amp;lt;motion&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;o&lt;/code&gt; and &lt;code&gt;O&lt;/code&gt;&lt;/strong&gt; open a new line and enter Insert — the most common when writing code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Ctrl+w&lt;/code&gt;&lt;/strong&gt; deletes the previous word without leaving Insert mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Ctrl+o&lt;/code&gt;&lt;/strong&gt; executes one Normal mode command momentarily and returns to Insert&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native autocomplete&lt;/strong&gt;: &lt;code&gt;Ctrl+n&lt;/code&gt; / &lt;code&gt;Ctrl+p&lt;/code&gt; with no plugins&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The golden rule&lt;/strong&gt;: one logical change per Insert session → &lt;code&gt;.&lt;/code&gt; works beautifully&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;You now have the two core pieces of Vim editing: Normal mode and Insert mode, and how to move between them deliberately. The next step is &lt;strong&gt;Visual mode&lt;/strong&gt; — explicitly selecting text to operate on, including the block visual mode (&lt;code&gt;Ctrl+v&lt;/code&gt;) that's one of those features that makes people ask "wait, a text editor can do that?"&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>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>
  </channel>
</rss>
