<?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>Using AI to learn programming concepts</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Mon, 14 Sep 2026 16:54:03 +0000</pubDate>
      <link>https://dev.to/fj_palacios/using-ai-to-learn-programming-concepts-54he</link>
      <guid>https://dev.to/fj_palacios/using-ai-to-learn-programming-concepts-54he</guid>
      <description>&lt;p&gt;You're reading about recursion. The explanation starts fine: “a function that calls itself.” Fair enough. You keep going. There's a factorial example. Still sort of fine. Then suddenly there's a call stack, a base case, return values unwinding in reverse order, and your brain returns &lt;code&gt;HTTP 500&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So you do the reasonable thing: you ask AI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain recursion to me.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the AI gives you a correct definition, a correct example, and a correct analogy. Everything is correct. You still don't get it. Fantastic. We have produced documentation with a positive attitude and actionable insights.&lt;/p&gt;

&lt;p&gt;The problem isn't that AI can't teach. It can. Very well, actually. The problem is that many people use it like a searchable encyclopedia with a chat box, when for learning programming it works much better as a technical tutor: patient, adaptable, and available at 2 AM, which is when questions actually decide to show up. During the day they hide. Like bugs.&lt;/p&gt;

&lt;p&gt;In the previous lesson we looked at &lt;a href="https://dev.to/en/tutorials/advanced-rctf-application"&gt;advanced context, role, task, and format&lt;/a&gt;. Now we'll apply the same idea to something more delicate: learning concepts without settling for an explanation that sounds good but collapses the moment you try to write code.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as a tutor, not an oracle
&lt;/h2&gt;

&lt;p&gt;AI should not be the divine voice descending from the cloud to hand you truth in Markdown. If you use it that way, you train yourself to accept answers that sound convincing. And as we saw earlier in the course, a convincing answer is not always a correct one.&lt;/p&gt;

&lt;p&gt;For learning, the useful mental model is this: &lt;strong&gt;AI is a tutor you can direct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That changes the conversation. You don't just ask “explain closures” and passively receive. You tell it what you know, what you don't know, what confuses you, and how you want to test your understanding.&lt;/p&gt;

&lt;p&gt;Look at the difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ Explain closures in Python.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can work, yes. It can also give you a generic explanation that starts well, becomes abstract, and ends with the same &lt;code&gt;outer()&lt;/code&gt; and &lt;code&gt;inner()&lt;/code&gt; example you've seen in twenty blog posts.&lt;/p&gt;

&lt;p&gt;Now try this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a Python mentor for someone learning programming.

Context:
I understand variables, functions, and local scope, but I struggle to understand
why an inner function can remember values from an outer function after the outer
function has finished running.

Task:
Explain closures by connecting them to what I already know. Don't use decorators yet.

Format:
1. Short explanation
2. Realistic analogy
3. Minimal Python example
4. Common mistake
5. One question to check whether I understood it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second version doesn't ask for “content.” It asks for a learning experience. It's like going to the doctor: “it hurts” doesn't help much; “it hurts here, since yesterday, when I do this” gives someone something to work with.&lt;/p&gt;

&lt;p&gt;And yes, you can ask questions that feel too basic. You should, actually.&lt;/p&gt;

&lt;h2&gt;
  
  
  “Stupid” questions that are not stupid
&lt;/h2&gt;

&lt;p&gt;One of the best things about using AI to learn is that you can ask without the social pressure of looking lost. No teammate looking over your shoulder. No rushed teacher. No senior sighing because you mixed up &lt;code&gt;map()&lt;/code&gt; and a &lt;code&gt;for&lt;/code&gt; loop. Just you, the question, and a text box.&lt;/p&gt;

&lt;p&gt;Use that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I know this is basic, but: why does Python use indentation instead of braces?
&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;I don't understand what it means for a variable to be "mutable".
Explain it assuming I know what a list is, but not what happens underneath.
&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;I've read the docs about `return`, but I still confuse returning a value
with printing it to the screen. Can you compare them with small examples?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These questions are gold. Not because they're sophisticated, but because they hit the exact place where your brain is stuck. General documentation can't guess that place.&lt;/p&gt;

&lt;p&gt;If you're following the Python course from scratch, this approach fits concepts like &lt;a href="https://dev.to/en/tutorials/variables-data-types-python"&gt;variables and data types&lt;/a&gt;, &lt;a href="https://dev.to/en/tutorials/defining-functions-python"&gt;functions&lt;/a&gt;, or lists. Don't just ask “what is a list?” Ask about the specific confusion: “why does &lt;code&gt;append()&lt;/code&gt; modify the list but &lt;code&gt;string.upper()&lt;/code&gt; doesn't modify the original string?” That's where real learning starts.&lt;/p&gt;

&lt;p&gt;A good learning question usually looks 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;I'm learning [concept].
I already understand [what you know].
I'm confused by [specific blocker].
Explain it using [kind of example that helps you].
Then ask me a question to check whether I understood it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No ceremony required. You're not submitting a petition to the International Prompt Committee. You're just making it clear where you are and where you fell over.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Feynman technique with AI
&lt;/h2&gt;

&lt;p&gt;The Feynman technique is brutally simple: if you want to check whether you understand something, try explaining it in your own words. If you can't, you don't understand it yet. If your explanation is hand-wavy, you don't understand it either. And if you explain it using words you couldn't define, your brain has just outsourced the problem.&lt;/p&gt;

&lt;p&gt;With AI, this process has one more participant: someone who doesn't get tired of listening to you explain things badly, doesn't sigh when you take your time, and has exercises ready for when you finish.&lt;/p&gt;

&lt;p&gt;Recommended flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1:
Explain Python generators with a minimal example.

Step 2:
I'll explain it back in my own words. Correct me if there are errors or vague parts:
[your explanation]

Step 3:
Ask me 3 questions to check whether I understood it.
One should be conceptual, one should be code reading, and one should require writing code.

Step 4:
Here are my answers:
[your answers]

Tell me what I understood, what I got wrong, and what I should review.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the important detail: you're not asking for yet another explanation. You're forcing yourself to produce one. That shift costs something. The kind of cost you feel when you've been reading someone else's code for a while and suddenly have to write your own. But that's exactly where learning starts.&lt;/p&gt;

&lt;p&gt;Example explanation you might send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I think a generator is a function that doesn't return all values at once.
It uses yield to pause execution and continue later from the same point.
That helps avoid loading a whole list into memory.

I'm not sure whether the generator remembers local variables between calls or
recalculates them each time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last sentence is the gem: “I'm not sure...” Don't hide it. Explicit doubts are anchors. If you leave them out because you're embarrassed, the AI can't help where it actually matters.&lt;/p&gt;

&lt;p&gt;The dangerous part comes next: AI can correct you incorrectly. Yes, shocking, a probabilistic tool can make things up. We're not looking for faith here; we're looking for initial feedback. If the explanation matters, verify it against official docs, tests, or real code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “but why?” chain
&lt;/h2&gt;

&lt;p&gt;Some concepts stay blurry because you memorize them too high up. You can repeat the sentence, but you don't know what holds it up.&lt;/p&gt;

&lt;p&gt;“We use functions to avoid repeating code.” Fine. But why is repeating code bad?&lt;/p&gt;

&lt;p&gt;“Because it makes maintenance harder.” Fine. But why?&lt;/p&gt;

&lt;p&gt;“Because if a rule changes, you have to change it in several places.” Fine. And what happens if you forget one?&lt;/p&gt;

&lt;p&gt;Now we're getting somewhere.&lt;/p&gt;

&lt;p&gt;You can use AI to walk down that staircase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want to truly understand why code duplication is a problem.
Run a "but why" chain with me.
Don't give me a long explanation at the start: ask me, wait for my answer,
and then go deeper based on what I say.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or, if you want a single response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Explain why we reduce duplicated code in programming using a 5-level chain:
1. Simple answer
2. Why that answer matters
3. What problem appears in a real project
4. What bug it could cause
5. What design principle sits underneath
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful answer might look 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;1. We avoid duplication so we don't write the same thing several times.
2. It matters because each copy can become outdated.
3. In a real project, a duplicated discount rule might be changed in one endpoint
   but not another.
4. That can make two users receive different prices for the same purchase.
5. The principle underneath is DRY: one rule should have one source of truth.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns a textbook phrase into a causal chain. And a causal chain is easier to remember because it has weight. It isn't “DRY because reasons.” It's “DRY because if you duplicate business rules, production eventually sends you a bill with interest.”&lt;/p&gt;

&lt;p&gt;AI is especially useful here because it doesn't get tired of your “but why?” A person, by the fourth one, starts looking at the door. AI doesn't. Unfair advantage; use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The connection game
&lt;/h2&gt;

&lt;p&gt;Learning programming is not collecting loose definitions. It's connecting ideas.&lt;/p&gt;

&lt;p&gt;A Python list is similar to an array in other languages, but not exactly the same. A &lt;code&gt;dict&lt;/code&gt; is like an address book where you look things up by name, but it also has internal rules. A pure function is like an honest vending machine: same input, same output. If only all functions were like that. If only vending machines were too.&lt;/p&gt;

&lt;p&gt;AI can help you create those connections if you ask explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I'm learning Python lists.
I already understand variables and `for` loops.

Explain lists by connecting them to:
1. An everyday analogy
2. How they work with a `for` loop
3. How they differ from a string
4. A common beginner mistake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also ask for comparisons between technologies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How is Django's ORM similar to writing SQL directly?
How is it different?
Explain it for someone who knows basic SELECT, INSERT, and WHERE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or connections between patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is Python's `@timer` decorator related to the design pattern called Decorator?
Tell me what they share, where they differ, and where people usually get confused.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These questions work because they don't let the concept float in the air. They tie it to something you already know. Learning this way is like building a bridge: if there is no starting bank, it doesn't matter how pretty the bridge is; it ends in the middle of the river. Very poetic for a programming tutorial.&lt;/p&gt;

&lt;p&gt;If you're learning several things at once — say Python, Git, and a bit of Docker — ask for connections between them. AI can help you see that a development environment is not a pile of unrelated tools, but a system where each piece has a job. And if you overdo it until everything seems connected to everything else, don't worry: that's also a phase. It passes. Mostly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify that you understand, not that you nodded
&lt;/h2&gt;

&lt;p&gt;The danger of learning with AI is that explanations often sound very good. Too good. Clean paragraphs, neat structure, confident tone. Your brain reads that and says: “yes, yes, got it.” Sure it did. Your brain also says “I'll just check one quick thing on YouTube,” and we know how that ends.&lt;/p&gt;

&lt;p&gt;To know whether you understand, you need to produce something.&lt;/p&gt;

&lt;p&gt;Ask for exercises:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Give me 5 progressive exercises about functions in Python.
Don't give me the solutions yet.
Each exercise should check a different concept.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask for code reading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Show me a small code snippet with lists and loops.
Ask me 4 questions about what it prints and why.
Don't reveal the answer until I respond.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ask it to find errors in your reasoning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is my explanation of the difference between `print()` and `return`:
[your explanation]

Point out errors, incomplete parts, and sentences that sound good but are imprecise.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And above all, ask for variations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Give me a similar exercise, but with a different trap.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sentence is more powerful than it looks. If you only solve an exercise identical to the example, you may have memorized the visual pattern. A variation checks whether you understood the idea.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Concept: `return` ends function execution.

Give me 3 code snippets:
1. One where `return` appears inside an `if`
2. One where there is code after `return`
3. One where a function calls another function that returns a value

Ask me what each one prints and why.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is less comfortable than reading another explanation. But learning programming was never just reading. Reading is looking at the map. Solving exercises is walking. And yes, sometimes you step in mud. That's called software development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Be careful: AI can help you learn wrong faster
&lt;/h2&gt;

&lt;p&gt;AI accelerates learning. That sounds good, but there's a flip side: it can also accelerate misunderstandings.&lt;/p&gt;

&lt;p&gt;If it explains a concept incorrectly and you don't verify it, you can internalize a wrong idea with a lot of confidence. Worse: because the explanation was clear, you'll be less likely to suspect it. It's the educational equivalent of a bug with good naming: it looks respectable until it breaks something important.&lt;/p&gt;

&lt;p&gt;Practical rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the concept is basic and general, use AI to understand and practice.&lt;/li&gt;
&lt;li&gt;If the concept affects security, concurrency, money, or real data, verify with official documentation.&lt;/li&gt;
&lt;li&gt;If there is code, run it.&lt;/li&gt;
&lt;li&gt;If you can't run the code, at least ask AI to trace execution step by step, then compare it yourself.&lt;/li&gt;
&lt;li&gt;If two answers contradict each other, don't choose the one that sounds more elegant. Investigate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful verification prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want to check this explanation before trusting it.
Look for possible inaccuracies, dangerous simplifications, or cases where it doesn't hold:

[paste explanation]

Format:
1. What is correct
2. What is imprecise
3. Example where the explanation fails or needs nuance
4. Corrected version in 5 lines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also ask it to cite documentation, but remember: a made-up citation still looks like a citation. If it matters, open the real docs. Yes, in a browser. I know, very medieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  Template for learning any concept
&lt;/h2&gt;

&lt;p&gt;Save this template and use it when a concept refuses to land:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a patient and precise technical mentor.

Context:
I'm learning [concept].
I already understand [previous knowledge].
I'm confused by [specific blocker].
I'm using [language/tool] and want examples in that context.

Task:
Help me understand the concept progressively.
Do not assume I understood it until I can explain it and solve an exercise.

Format:
1. Explanation in 5-7 lines
2. Realistic analogy
3. Minimal example
4. Counterexample or common mistake
5. Comprehension question
6. Small exercise without solution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after answering the exercise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is my solution:
[your solution]

Evaluate it using this format:
1. What is correct
2. What is wrong or incomplete
3. What concept my answer proves I understood
4. What concept I should review
5. A similar exercise with one variation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key is closing the loop: explanation, production, feedback, variation. Without that last part, it's easy to confuse familiarity with understanding. Familiarity is “this rings a bell.” Understanding is “I can use it when the example changes.” Not the same thing, even if your brain tries to sell you the bundle.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Use AI as a directed tutor, not an oracle&lt;/li&gt;
&lt;li&gt;Basic questions are valuable when they name the exact confusion&lt;/li&gt;
&lt;li&gt;The Feynman technique works best when you explain and ask for correction&lt;/li&gt;
&lt;li&gt;The “but why?” chain turns memorized phrases into causal understanding&lt;/li&gt;
&lt;li&gt;The connection game ties new concepts to previous knowledge&lt;/li&gt;
&lt;li&gt;To verify understanding, produce something: explain, read code, write code, or solve exercises&lt;/li&gt;
&lt;li&gt;Clear explanations can still be wrong; verify important claims&lt;/li&gt;
&lt;li&gt;A good learning session ends with feedback and a variation of the exercise&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge:&lt;/strong&gt; Pick a concept that still feels uncomfortable — recursion, closures, mutability, &lt;code&gt;return&lt;/code&gt;, anything — and use the final template. Don't move to the next topic until you've explained the concept in your own words and solved a variation of the exercise.&lt;/p&gt;

&lt;p&gt;Learning with AI is not about receiving faster answers; it's about building a smarter practice system. In the next lesson we'll get into prompts for code generation, and the bar goes up: understanding an explanation won't be enough anymore. You'll need to ask for verifiable code without letting AI turn a small function into a cathedral of boilerplate.&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>Context, role, task, and format: advanced application</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:37:12 +0000</pubDate>
      <link>https://dev.to/fj_palacios/context-role-task-and-format-advanced-application-1c0o</link>
      <guid>https://dev.to/fj_palacios/context-role-task-and-format-advanced-application-1c0o</guid>
      <description>&lt;p&gt;Here's how it goes: you discover RCTF, try it out, and something clicks. The AI stops handing you vague garbage. You're sold on it. Then the next day you ask it something real — a weird bug, a half-built feature, a module that needs structure — and it falls flat. Same framework, same four letters, completely hollow answer.&lt;/p&gt;

&lt;p&gt;Was RCTF a lie? Do you need more acronyms? Are we three lessons away from a full methodology with a keynote, a certification program, and a branded water bottle?&lt;/p&gt;

&lt;p&gt;No. The framework is fine. The problem is that last lesson we used it like a form to fill out. This lesson is about using it the way you'd actually use it at work: with real context, iteration, and formats that give you something you can act on — not just a well-formatted nothing.&lt;/p&gt;

&lt;p&gt;If the framework isn't fresh in your head, go back to &lt;a href="https://dev.to/en/tutorials/the-art-of-the-prompt-core-principles"&gt;the prompt core principles lesson&lt;/a&gt;. This lesson assumes you already know what &lt;strong&gt;role&lt;/strong&gt;, &lt;strong&gt;context&lt;/strong&gt;, &lt;strong&gt;task&lt;/strong&gt;, and &lt;strong&gt;format&lt;/strong&gt; mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake: treating RCTF like a form
&lt;/h2&gt;

&lt;p&gt;A prompt can include RCTF and still be bad. That's important.&lt;/p&gt;

&lt;p&gt;Look at this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a senior backend developer.
Context: I have an API.
Task: help me improve it.
Format: give me a list.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It has all four letters. It also has the precision of a weather report written by someone glancing out the window: “there appears to be sky.”&lt;/p&gt;

&lt;p&gt;The role is too generic. The context doesn't say stack, problem, constraints, or goal. The task has no useful verb. The format asks for a list, but defines no criteria. It's RCTF in name only.&lt;/p&gt;

&lt;p&gt;Now compare it with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a backend engineer specialized in REST APIs with Node.js.

Context: I'm maintaining an Express API that manages orders.
Stack: Node.js 22, Express, PostgreSQL, zod for validation, and Vitest.
Problem: the POST /orders endpoint has too much logic in the controller:
it validates input, calculates discounts, writes to the database, and emits events.
I want to refactor without changing behavior because this is already in production.

Task: propose a refactor plan in small steps. Do not write code yet.

Format:
1. Diagnosis of mixed responsibilities
2. Safe 4-6 step plan
3. Risks for each step
4. Tests I should add before touching code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same structure. Completely different result.&lt;/p&gt;

&lt;p&gt;It's not about length. Each part removes an assumption. And every assumption the AI doesn't have to make is one fewer ticket in the nonsense lottery. Context is almost always where most of them pile up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rich context doesn't mean your life story
&lt;/h2&gt;

&lt;p&gt;Context is the most important part of RCTF, and also the easiest one to ruin. People often jump from “I give no context” to “here's half the company in Markdown.” Neither extreme helps.&lt;/p&gt;

&lt;p&gt;Rich context isn't long context. It's &lt;strong&gt;relevant&lt;/strong&gt; context.&lt;/p&gt;

&lt;p&gt;A good context block answers these questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What are you building?&lt;/li&gt;
&lt;li&gt;What stack or tools are you using?&lt;/li&gt;
&lt;li&gt;What exact area are you touching?&lt;/li&gt;
&lt;li&gt;What constraints must the solution respect?&lt;/li&gt;
&lt;li&gt;What have you already tried?&lt;/li&gt;
&lt;li&gt;What does “good” mean in this case?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Project example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project context:
I'm working on an e-commerce backend with Django 5 and Django REST Framework.
The database is PostgreSQL 15. We use Redis for cache and Celery for async jobs.
The code has type hints, tests with pytest, and the goal is to avoid breaking
compatibility with the public API.

Relevant domain:
- orders/: Order, OrderItem, Coupon
- products/: Product, Category, Inventory
- users/: CustomUser, Address

Important constraint:
I cannot change the endpoint contract because published mobile apps consume it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a documentation dump. It's the minimum map the AI needs so it doesn't propose something that breaks production in sentence two.&lt;/p&gt;

&lt;p&gt;Don't overthink it. Keep a base context block for the project and adapt it per task. Think of it like an &lt;code&gt;.env.example&lt;/code&gt; file: not the actual secrets, just enough structure that you're not starting from scratch every time.&lt;/p&gt;

&lt;p&gt;Project context gets you in the building. Code context puts you in the right room.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code context: the bug doesn't live alone
&lt;/h2&gt;

&lt;p&gt;When you ask about a bug, project context helps, but code context does the heavy lifting.&lt;/p&gt;

&lt;p&gt;A decent debugging prompt should include four pieces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When asking for bug help, include:
1. The relevant code
2. The failing test or reproduction steps
3. The full error message
4. What you expected vs what actually happened
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fourth point sounds obvious until you forget it. The AI can read the error, but it can't infer your intent. “This fails” isn't a diagnosis; it's a scream. Understandable, yes. Useful, less so.&lt;/p&gt;

&lt;p&gt;Weak example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This function breaks for users without email. Fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a senior Python developer.

Context:
This function normalizes user data imported from CSV. Some users don't have an
email because they come from old records. In that case I want to return None,
not raise an exception.

Code:
````python
def normalize_email(user):
    return user["email"].strip().lower()
````

Error:
KeyError: 'email'

Expected behavior:

- If user["email"] exists and contains text, return it normalized
- If it doesn't exist or is empty, return None
- Keep the function small and easy to test

Task:
Propose the implementation and 3 pytest tests.

Format:

1. Final code
2. Tests
3. Brief explanation of the covered cases

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI doesn't get better because it has become “smarter.” It gets better because it no longer has to guess the function contract. You gave it the walls of the room before asking it to arrange the furniture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Iterate: a conversation, not a vending machine
&lt;/h2&gt;

&lt;p&gt;Here's the mental model most people skip: the AI is not a vending machine. You don't drop in a prompt, wait for the answer to fall out, and walk away with your solution. That's how you get responses that technically address the question and solve nothing.&lt;/p&gt;

&lt;p&gt;The next mental shift is this: don't try to solve everything in one giant prompt.&lt;/p&gt;

&lt;p&gt;AI works better when the conversation moves in rounds. The same way you'd work with a person: first define direction, then get into details, then review, correct, and test. You don't tell a teammate: “design, implement, test, document, optimize, and deploy this system; I'll be back in ten minutes.” Well, maybe at some companies. And then things happen.&lt;/p&gt;

&lt;p&gt;An iterative flow could look 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;Round 1:
Propose a structure for implementing user registration in Flask.
Architecture and files only, no code.

Round 2:
Now implement the User model and validation layer.
Keep the code simple and testable.

Round 3:
Write tests for the happy path and these error cases:
- invalid email
- duplicate email
- password too short

Round 4:
The duplicate email test fails with this error: [paste error]
Diagnose the cause before proposing changes.

Round 5:
Refactor the validation to reduce duplication without changing behavior.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This avoids two classic problems: huge responses that mix incompatible decisions, and changes you can't evaluate because everything happened at once.&lt;/p&gt;

&lt;p&gt;Practical rule: &lt;strong&gt;each round should change one main thing&lt;/strong&gt;. Architecture. Implementation. Tests. Debugging. Refactor. If you mix everything, you won't know which part worked and which part just smuggled a gremlin into your repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Format: ask for a response you can actually use
&lt;/h2&gt;

&lt;p&gt;Format isn't decoration. It's the interface between the AI's response and your workflow.&lt;/p&gt;

&lt;p&gt;If you ask “explain this,” you get an explanation. If you ask “give me a plan with risks and tests,” you get something you can execute. Sounds small, but it's the difference between reading content and moving work forward.&lt;/p&gt;

&lt;p&gt;Useful formats for development:&lt;/p&gt;

&lt;h3&gt;
  
  
  For code generation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format:
1. Implementation code
2. Example usage
3. Minimal tests
4. Technical decisions and trade-offs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  For debugging
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format:
1. Likely root cause
2. Why it happens
3. How to confirm it
4. Minimal fix
5. How to prevent it in the future
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  For learning a concept
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format:
1. One-sentence definition
2. Realistic analogy
3. Minimal code example
4. Common mistake to avoid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  For comparing options
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Format:
Table with columns:
- Option
- When to use it
- Advantages
- Risks
- Recommendation for my case
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trick is asking for the format that matches your next action. If you're deciding, ask for a comparison. If you're implementing, ask for steps and tests. If you're learning, ask for an explanation, analogy, and gotcha. The AI doesn't know your next move unless you tell it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Templates you can reuse
&lt;/h2&gt;

&lt;p&gt;Here are three practical templates. Not to copy like scripture, but to adapt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a senior developer specialized in [language/framework].

Context:
[What the code does]
[Relevant stack]
[Constraints: don't change public API, keep compatibility, etc.]

Code:
[paste code]

Task:
Review the code for readability issues, potential bugs, and edge cases.
Do not refactor yet.

Format:
1. 3-line summary
2. Issues ordered by impact
3. For each issue: why it matters and how you would confirm it
4. Questions that must be answered before touching code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Designing an implementation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a pragmatic software engineer.

Context:
[Feature you want to build]
[Stack]
[Constraints]
[What already exists]

Task:
Propose a simple implementation design. Prioritize small, verifiable changes.
Do not write code yet.

Format:
1. Assumptions
2. Proposed design
3. Files you would touch
4. Step-by-step plan
5. Tests needed
6. Risks and alternatives
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Asking for a technical explanation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Role: act as a technical mentor.

Context:
I'm learning [concept]. I already understand [previous knowledge], but I'm stuck on [specific blocker].

Task:
Explain [concept] by connecting it to what I already know.

Format:
1. Short explanation
2. Analogy
3. Minimal example
4. Counterexample
5. Question to check whether I understood it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good template doesn't replace thinking. It removes mental boilerplate so you can focus on the actual problem. Like editor snippets: helpful when you know what you're inserting, dangerous when you fire them blindly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What not to put in context
&lt;/h2&gt;

&lt;p&gt;Less fun, but necessary: not all context should be shared.&lt;/p&gt;

&lt;p&gt;Don't paste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;tokens&lt;/li&gt;
&lt;li&gt;passwords&lt;/li&gt;
&lt;li&gt;real personal data&lt;/li&gt;
&lt;li&gt;company secrets&lt;/li&gt;
&lt;li&gt;full database dumps&lt;/li&gt;
&lt;li&gt;proprietary code if your tool or policy doesn't allow it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Yes, the AI “needs context.” No, it doesn't need your production token. That sentence belongs on a mug, right next to “don't run commands you don't understand.”&lt;/p&gt;

&lt;p&gt;If you need to show data, anonymize it:&lt;br&gt;
&lt;/p&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;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pro"&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;And if the problem depends on a sensitive value, describe it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The token exists and has a valid JWT format, but the backend returns 401.
I can't share the real token; I can share the anonymized header and claims.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Context quality isn't measured by how many secrets you expose. It's measured by how many assumptions you remove without creating a new problem.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;RCTF doesn't help if each block is generic; each part must reduce assumptions&lt;/li&gt;
&lt;li&gt;Rich context means relevant context, not long context&lt;/li&gt;
&lt;li&gt;For bugs, include code, reproduction, error, and expected behavior&lt;/li&gt;
&lt;li&gt;AI conversations work better in small rounds than in one giant prompt&lt;/li&gt;
&lt;li&gt;Each round should change one main thing&lt;/li&gt;
&lt;li&gt;Format should connect the response to your next action&lt;/li&gt;
&lt;li&gt;Templates help, but they don't replace thinking&lt;/li&gt;
&lt;li&gt;Never share secrets, tokens, or real personal data as “context”&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge:&lt;/strong&gt; Take a real prompt you've used for code help and turn it into a three-round conversation: (1) context and diagnosis, (2) solution proposal, (3) tests or verification. In each round, specify a different format based on what you need to do next.&lt;/p&gt;

&lt;p&gt;RCTF is no longer just a checklist: it's a way to direct a technical conversation. In the next lesson we'll look at how to use AI to learn programming concepts without getting stuck with explanations that sound good but collapse under pressure — because real understanding isn't nodding at a convincing answer, it's being able to spot when someone is selling smoke with nice syntax.&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>Search in Vim: find text without losing your flow</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 09 Sep 2026 11:33:00 +0000</pubDate>
      <link>https://dev.to/fj_palacios/search-in-vim-find-text-without-losing-your-flow-2npa</link>
      <guid>https://dev.to/fj_palacios/search-in-vim-find-text-without-losing-your-flow-2npa</guid>
      <description>&lt;p&gt;You're inside a 400-line file. You need to find where &lt;code&gt;userId&lt;/code&gt; is used, but your fingers start the classic ritual: &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, look around, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;, &lt;code&gt;k&lt;/code&gt; because you overshot, sigh, repeat. Where was that variable? Why are there three similar blocks? Who wrote this endless function? Ah. You did. Always more fun when the culprit is you.&lt;/p&gt;

&lt;p&gt;Vim doesn't want you wandering through files like you're searching for your keys in the dark — room by room, hand on the wall. It wants you to jump straight to the place. Search isn't a secondary tool: it's a way to move.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of search
&lt;/h2&gt;

&lt;p&gt;In Vim there are two search families worth separating early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Search inside the current line&lt;/strong&gt;: &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt;, &lt;code&gt;T&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search across the file&lt;/strong&gt;: &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt;, &lt;code&gt;N&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;#&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first helps you move quickly inside one specific line. The second lets you jump between matches across the whole file.&lt;/p&gt;

&lt;p&gt;If you're coming from &lt;a href="https://dev.to/en/tutorials/visual-mode-vim"&gt;Visual mode&lt;/a&gt;, this fits nicely with what you already know: search can become part of a selection. Press &lt;code&gt;v&lt;/code&gt;, search with &lt;code&gt;/&lt;/code&gt;, and Vim extends the selection up to the match. Not magic; just Vim's grammar doing overtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching for characters inside a line
&lt;/h2&gt;

&lt;p&gt;Start with a line like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalPrice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculatePrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unitPrice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Place the cursor at the beginning of the line, on the &lt;code&gt;c&lt;/code&gt; in &lt;code&gt;const&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;f{character}&lt;/code&gt; finds the next character on the same line and places the cursor &lt;strong&gt;on top&lt;/strong&gt; of 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="nb"&gt;fp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command jumps to the &lt;code&gt;p&lt;/code&gt; in &lt;code&gt;totalPrice&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;f&lt;/code&gt; stands for &lt;em&gt;find&lt;/em&gt;. Easy enough. For once, Vim didn't call it &lt;code&gt;zq&lt;/code&gt; or something that looks like a router spell.&lt;/p&gt;

&lt;p&gt;The backward version is &lt;code&gt;F{character}&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;F&lt;span class="p"&gt;=&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It searches for &lt;code&gt;=&lt;/code&gt; to the left from wherever you are.&lt;/p&gt;

&lt;p&gt;The important difference comes with &lt;code&gt;t&lt;/code&gt; and &lt;code&gt;T&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;t{character}&lt;/code&gt; searches forward, but stops &lt;strong&gt;right before&lt;/strong&gt; the found character:&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;t&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the line above, that leaves you right before the &lt;code&gt;(&lt;/code&gt; in &lt;code&gt;calculatePrice(...)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;T{character}&lt;/code&gt; does the same thing backward: it searches to the left and stops just after the found character.&lt;/p&gt;

&lt;p&gt;The mental table looks like this:&lt;/p&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;Direction&lt;/th&gt;
&lt;th&gt;Where the cursor lands&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f{c}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forward&lt;/td&gt;
&lt;td&gt;On the character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;F{c}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backward&lt;/td&gt;
&lt;td&gt;On the character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;t{c}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Forward&lt;/td&gt;
&lt;td&gt;Before the character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;T{c}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Backward&lt;/td&gt;
&lt;td&gt;After the character&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why does &lt;code&gt;t&lt;/code&gt; exist if we already have &lt;code&gt;f&lt;/code&gt;? Because sometimes you don't want to land on the character; you want to stop right before it so you can operate precisely.&lt;/p&gt;

&lt;p&gt;For example, to delete up to but not including the opening parenthesis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;dt&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From &lt;code&gt;const totalPrice = calculatePrice...&lt;/code&gt;, that deletes from the cursor up to just before &lt;code&gt;(&lt;/code&gt;. If you used &lt;code&gt;df(&lt;/code&gt;, it would also delete the parenthesis. Sometimes you want the door; sometimes you want the doormat.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeating character search with ; and ,
&lt;/h2&gt;

&lt;p&gt;After using &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;F&lt;/code&gt;, &lt;code&gt;t&lt;/code&gt;, or &lt;code&gt;T&lt;/code&gt;, repeat the same search with &lt;code&gt;;&lt;/code&gt;:&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;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
;
;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Imagine this line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;createUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;f,&lt;/code&gt; jumps to the first comma. Each &lt;code&gt;;&lt;/code&gt; jumps to the next comma. Tiny command, constant real-world use.&lt;/p&gt;

&lt;p&gt;To repeat in the opposite direction, use &lt;code&gt;,&lt;/code&gt;:&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="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, &lt;code&gt;;&lt;/code&gt; goes forward and &lt;code&gt;,&lt;/code&gt; goes backward. Is it intuitive? Sort of. Will you end up using it without thinking? Also yes. Vim has many things that feel like private jokes at first and later become muscle memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching across the file with /
&lt;/h2&gt;

&lt;p&gt;To search forward across the file, use &lt;code&gt;/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;/userId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;Enter&lt;/code&gt; and Vim jumps to the next occurrence of &lt;code&gt;userId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This changes how you move. Instead of counting lines or hammering &lt;code&gt;j&lt;/code&gt;, you type what you're looking for. If the file is a city, &lt;code&gt;/&lt;/code&gt; is taking the subway. You're not walking door to door checking nameplates.&lt;/p&gt;

&lt;p&gt;To search for the next occurrence, press &lt;code&gt;n&lt;/code&gt;:&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;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To go back to the previous occurrence, press &lt;code&gt;N&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;N
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Important detail: &lt;code&gt;n&lt;/code&gt; repeats the search in the same direction you originally used. If you searched with &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt; goes forward. If you searched with &lt;code&gt;?&lt;/code&gt;, &lt;code&gt;n&lt;/code&gt; goes backward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching backward with ?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;?&lt;/code&gt; is like &lt;code&gt;/&lt;/code&gt;, but it searches backward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;?userId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;n&lt;/code&gt; keeps searching backward&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;N&lt;/code&gt; reverses direction and searches forward&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This sounds like a tongue twister until you've used it twice. Short rule: &lt;strong&gt;&lt;code&gt;n&lt;/code&gt; repeats, &lt;code&gt;N&lt;/code&gt; reverses&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you ever press &lt;code&gt;n&lt;/code&gt; and the cursor goes the “wrong” way, Vim isn't being dramatic. The last search probably started with &lt;code&gt;?&lt;/code&gt;. It happens. Breathe, press &lt;code&gt;N&lt;/code&gt;, move on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Searching the word under the cursor with &lt;code&gt;*&lt;/code&gt; and &lt;code&gt;#&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This is one of those commands you should pick up early because it removes a lot of noise.&lt;/p&gt;

&lt;p&gt;Place the cursor on a word and press &lt;code&gt;*&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vim searches forward for the next occurrence of that whole word.&lt;/p&gt;

&lt;p&gt;Press &lt;code&gt;#&lt;/code&gt; and it does the same thing backward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;#
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical case: you're on &lt;code&gt;userId&lt;/code&gt;, press &lt;code&gt;*&lt;/code&gt;, then walk through its uses with &lt;code&gt;n&lt;/code&gt;. No typing the word, no opening a side panel, no reaching for the mouse like you're asking the editor for permission.&lt;/p&gt;

&lt;p&gt;There are also &lt;code&gt;g*&lt;/code&gt; and &lt;code&gt;g#&lt;/code&gt;, which search partial matches. The difference:&lt;/p&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 searches for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whole word forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;#&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whole word backward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;g*&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Partial match forward&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;g#&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Partial match backward&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're on &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt; searches for &lt;code&gt;user&lt;/code&gt; as a whole word. &lt;code&gt;g*&lt;/code&gt; may also find &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;currentUser&lt;/code&gt;, or &lt;code&gt;users&lt;/code&gt;, depending on the text. Useful, but be careful: searching too broadly is like asking for “some food” and getting the supermarket inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search highlighting: hlsearch, incsearch, and noh
&lt;/h2&gt;

&lt;p&gt;When you search, Vim can highlight all matches. That's controlled by &lt;code&gt;hlsearch&lt;/code&gt;:&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="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;hlsearch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From then on, each search leaves matches highlighted.&lt;/p&gt;

&lt;p&gt;You can also enable &lt;code&gt;incsearch&lt;/code&gt;:&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="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nb"&gt;incsearch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;incsearch&lt;/code&gt;, Vim shows matches while you type the pattern. Type &lt;code&gt;/use&lt;/code&gt;, and before pressing &lt;code&gt;Enter&lt;/code&gt; you already see where you'll land. Very useful for avoiding blind searches.&lt;/p&gt;

&lt;p&gt;Now comes the classic Vim moment: you enable &lt;code&gt;hlsearch&lt;/code&gt;, search for something, finish the job, and the file stays highlighted like someone attacked your screen with a marker and too much coffee.&lt;/p&gt;

&lt;p&gt;To clear the current highlight without disabling the option, use:&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="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;nohlsearch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the short version:&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="p"&gt;:&lt;/span&gt;&lt;span class="k"&gt;noh&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't turn &lt;code&gt;hlsearch&lt;/code&gt; off forever. It only clears the current search; the next search will highlight again. That's what you want most of the time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case sensitivity and precise searches
&lt;/h2&gt;

&lt;p&gt;You can force a case-insensitive search with &lt;code&gt;\c&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;/userid\&lt;span class="k"&gt;c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That finds &lt;code&gt;userid&lt;/code&gt;, &lt;code&gt;userId&lt;/code&gt;, &lt;code&gt;USERID&lt;/code&gt;, and so on.&lt;/p&gt;

&lt;p&gt;You can force a case-sensitive search with &lt;code&gt;\C&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight viml"&gt;&lt;code&gt;/userId\C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That searches exactly for &lt;code&gt;userId&lt;/code&gt;, respecting uppercase and lowercase letters.&lt;/p&gt;

&lt;p&gt;There are global options like &lt;code&gt;ignorecase&lt;/code&gt; and &lt;code&gt;smartcase&lt;/code&gt;, but you don't need to go there yet. For now, &lt;code&gt;\c&lt;/code&gt; and &lt;code&gt;\C&lt;/code&gt; give you local control when you need it. Not everything needs to become permanent configuration. Yes, even in Vim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search and replace: first contact
&lt;/h2&gt;

&lt;p&gt;Full substitution deserves its own lesson later, but you need the basics here because substitution is tied closely to search.&lt;/p&gt;

&lt;p&gt;To replace on the current line:&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="p"&gt;:&lt;/span&gt;s&lt;span class="sr"&gt;/var/&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;/&lt;span class="k"&gt;g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changes every &lt;code&gt;var&lt;/code&gt; to &lt;code&gt;let&lt;/code&gt; on the current line.&lt;/p&gt;

&lt;p&gt;To replace across the whole file:&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="p"&gt;:&lt;/span&gt;%s&lt;span class="sr"&gt;/var/&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;/&lt;span class="k"&gt;g&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;%&lt;/code&gt; means “the whole file”. The final &lt;code&gt;g&lt;/code&gt; means “all occurrences on each line”, not just the first one.&lt;/p&gt;

&lt;p&gt;And here's the version you should use when you're not 100% sure:&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="p"&gt;:&lt;/span&gt;%s&lt;span class="sr"&gt;/var/&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt;/gc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;c&lt;/code&gt; asks for confirmation at each change.&lt;/p&gt;

&lt;p&gt;Global replacements without confirmation are fine when you know exactly what you're doing. When you don't, it's like using a chainsaw to open a cardboard box: technically it works, but you'll spend a while explaining the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Search as motion
&lt;/h2&gt;

&lt;p&gt;The most powerful thing about search in Vim isn't finding text. Every editor can do that. The powerful part is that search is also a &lt;strong&gt;motion&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means you can combine it with operators from &lt;a href="https://dev.to/en/tutorials/normal-mode-power-of-vim"&gt;Normal mode's grammar&lt;/a&gt;:&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;d&lt;/span&gt;/userId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That deletes from the cursor to the next occurrence of &lt;code&gt;userId&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can also use searches after entering Visual 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="k"&gt;v&lt;/span&gt;/error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That selects from the cursor to the next occurrence of &lt;code&gt;error&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is where Vim stops feeling like a list of isolated commands and starts behaving like a small language: operator + motion + repetition. Search isn't “find text”; it's telling the cursor where the next destination is.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;f{c}&lt;/code&gt; and &lt;code&gt;F{c}&lt;/code&gt; search for a character on the line and land on it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;t{c}&lt;/code&gt; and &lt;code&gt;T{c}&lt;/code&gt; search for a character but stop before or after it&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;;&lt;/code&gt; repeats the last character search; &lt;code&gt;,&lt;/code&gt; repeats it in the opposite direction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/pattern&lt;/code&gt; searches forward; &lt;code&gt;?pattern&lt;/code&gt; searches backward&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;n&lt;/code&gt; repeats the search; &lt;code&gt;N&lt;/code&gt; reverses direction&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;*&lt;/code&gt; and &lt;code&gt;#&lt;/code&gt; search for the word under the cursor forward or backward&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:noh&lt;/code&gt; clears the current highlight without disabling &lt;code&gt;hlsearch&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;:%s/old/new/gc&lt;/code&gt; replaces across the file with confirmation&lt;/li&gt;
&lt;li&gt;Search also works as a motion inside Vim's grammar&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Open a real code file and practice without using arrows or &lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt; for long movements. Search for a variable with &lt;code&gt;*&lt;/code&gt;, walk through its uses with &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;N&lt;/code&gt;, jump between commas with &lt;code&gt;f,&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt;, and try a safe replacement with &lt;code&gt;:%s/old/new/gc&lt;/code&gt; using names you can undo without drama.&lt;/p&gt;

&lt;p&gt;You now know how to move through a file without walking line by line. In the next lesson we'll cover &lt;strong&gt;marks and jumps&lt;/strong&gt;: how to leave “bookmarks” inside code and return to previous positions without depending on your memory, which is already busy remembering passwords, commands, and why you opened that browser tab.&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>Releasing new versions of our projects using Git tags</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 08 Sep 2026 08:29:47 +0000</pubDate>
      <link>https://dev.to/fj_palacios/releasing-new-versions-of-our-projects-using-git-tags-4b2m</link>
      <guid>https://dev.to/fj_palacios/releasing-new-versions-of-our-projects-using-git-tags-4b2m</guid>
      <description>&lt;p&gt;We're learning a lot of new things to master Git, but until now we haven't said anything about the versions of our projects. We know that you are very advanced in your project and thanks to Git you are working in a more efficient way. You are about to publish the version 0.1 of your project, so people can try it and leave their opinions, but… How the heck we create a new version of our software in Git? Let's go to it!&lt;/p&gt;

&lt;p&gt;In Git there is a function to add &lt;em&gt;tags&lt;/em&gt; to our &lt;em&gt;commits&lt;/em&gt;, which are only a few pointers (like &lt;strong&gt;HEAD&lt;/strong&gt; or each of the branches, as we saw before), this allows us to reference a &lt;em&gt;commit&lt;/em&gt; that we want to be able to locate and access it easily. We can actually create &lt;em&gt;tags&lt;/em&gt; for any &lt;em&gt;commit&lt;/em&gt; we want to locate quickly once time has passed, but it's true that this function is generally used to launch versions of our applications.&lt;/p&gt;

&lt;p&gt;FYI: There are two types of &lt;em&gt;tags&lt;/em&gt;, &lt;em&gt;annotated&lt;/em&gt; and &lt;em&gt;lightweight tags&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightweight tags
&lt;/h3&gt;

&lt;p&gt;First we will focus on the &lt;em&gt;lightweight tags&lt;/em&gt;, which are the easiest to learn. This type of &lt;em&gt;tags&lt;/em&gt; is usually used to locate a specially relevant &lt;em&gt;commit&lt;/em&gt; but not a release of a new version. Why? Because you can only add a label with a name and this doesn't seem very useful, isn't it? It's a kind of reminder, usually a temporary one.&lt;/p&gt;

&lt;p&gt;If you want to add the &lt;em&gt;tag&lt;/em&gt; to the most recent &lt;em&gt;commit&lt;/em&gt; just run the &lt;code&gt;git tag v0.1&lt;/code&gt; command, and if we use the &lt;code&gt;git log --oneline --decorate&lt;/code&gt; command we'll see that now our &lt;strong&gt;HEAD&lt;/strong&gt; also has the &lt;strong&gt;tag: v0.1&lt;/strong&gt; pointer; if we want to add a &lt;em&gt;tag&lt;/em&gt; to a previous &lt;em&gt;commit&lt;/em&gt; you can also do it, either through the hash of the &lt;em&gt;commit&lt;/em&gt; (the &lt;em&gt;code&lt;/em&gt; that references the &lt;em&gt;commit&lt;/em&gt; located first in the &lt;code&gt;log&lt;/code&gt; command) or, as we've learned to do, referencing it in a relative way from the most recent &lt;em&gt;commit&lt;/em&gt; (&lt;em&gt;HEAD&lt;/em&gt;), like: &lt;code&gt;git tag v0.1 HEAD~2&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Annotated tags
&lt;/h3&gt;

&lt;p&gt;In simple words, an &lt;em&gt;annotated tag&lt;/em&gt; is something like a &lt;em&gt;commit&lt;/em&gt; as it's treated as an object by Git; in this type of &lt;em&gt;tags&lt;/em&gt; we can add a message that explain the reason for having added that &lt;em&gt;tag&lt;/em&gt;, in addition, the date it was added and who added it will be saved; that's why it's great for our code versioning, since more information is stored and in the message we can write what are the changes that this new version adds.&lt;/p&gt;

&lt;p&gt;The command is very similar to the one we have already learned, but adding some parameters. If we want to write a short message, we can do it as in &lt;em&gt;commits&lt;/em&gt;: &lt;code&gt;git tag -a v0.1 -m "Testing Git tags"&lt;/code&gt; but if you want to write a longer message, which is recommended, just write &lt;code&gt;git tag -a v0.1&lt;/code&gt; so that Git opens us the text editor that we've configured previously to write everything we want. Logically we can also add &lt;em&gt;annotated tags&lt;/em&gt; for previous &lt;em&gt;commits&lt;/em&gt;: &lt;code&gt;git tag -a v0.1 -m "Testing Git tags" HEAD~2&lt;/code&gt; easy peasy, right?&lt;/p&gt;

&lt;h3&gt;
  
  
  Listing tags
&lt;/h3&gt;

&lt;p&gt;We can also get a list of all added &lt;em&gt;tags&lt;/em&gt;, and the easiest way is through the &lt;code&gt;git tag&lt;/code&gt; command. So easy for you guys, huh? Well, what if you already have a mature development and you have many added versions? Holy shit! That list would be very long! But fortunately there's a way to filter the results. If we want to filter the added &lt;em&gt;tags&lt;/em&gt; of the 0.* versions we could execute the &lt;code&gt;git tag -l "v0.*"&lt;/code&gt; command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting more information about a tag
&lt;/h3&gt;

&lt;p&gt;If we've added an &lt;em&gt;annotated tag&lt;/em&gt;, with the &lt;code&gt;git show v0.1&lt;/code&gt; command we can see all the information related to the &lt;em&gt;tag&lt;/em&gt; but also with the &lt;em&gt;commit&lt;/em&gt; to which it refers; if we've added a &lt;em&gt;lightweight tag&lt;/em&gt;, then we'll only get information about the &lt;em&gt;commit&lt;/em&gt; to which it refers because from the &lt;em&gt;tag&lt;/em&gt; there would be no information to obtain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Removing tags
&lt;/h3&gt;

&lt;p&gt;At any time we can delete a tag, whether it's an &lt;em&gt;annotated tag&lt;/em&gt; or not, with the same command: &lt;code&gt;git tag -d v0.1&lt;/code&gt;. This command doesn't need more explanation, isn't it?&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending a tag to a remote node
&lt;/h3&gt;

&lt;p&gt;If you want to send a certain &lt;em&gt;tag&lt;/em&gt; to a remote node (if you do not know what a remote is, take a look at &lt;a href="https://dev.to/en/tutorials/creating-our-first-repository-on-github/"&gt;Creating our first repository on GitHub&lt;/a&gt;) you can do it in the same way as we'd send a new branch: &lt;code&gt;git push origin v0.1&lt;/code&gt; and if you want to send more than one &lt;em&gt;tag&lt;/em&gt; you can execute the command &lt;code&gt;git push origin --tags&lt;/code&gt; to send them all with a single command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Working with tags on remote nodes
&lt;/h3&gt;

&lt;p&gt;When sending &lt;em&gt;tags&lt;/em&gt; to remote nodes like &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; or &lt;a href="https://gitlab.com/" rel="noopener noreferrer"&gt;GitLab&lt;/a&gt; we offer users who visit our projects the information that a new version was released but also that they can download it easily, and for convenience it's created automatically. In GitHub, for example, when a new &lt;em&gt;tag&lt;/em&gt; is sent, it obviously appears in the section of &lt;em&gt;tags&lt;/em&gt;, but also in the &lt;em&gt;releases&lt;/em&gt; one, therefore create a new version of our application so that users can download it is a quick and easy task.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zkckyts3m9xw8u17a4y.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7zkckyts3m9xw8u17a4y.webp" alt="Tags section on GitHub" width="800" height="204"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can no longer say that you've not shared with the world a version of your application (I hope an open source application) because you didn't know how to do it.&lt;/p&gt;

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

</description>
      <category>git</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Defining functions in Python: def, parameters, and return</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Fri, 04 Sep 2026 10:56:54 +0000</pubDate>
      <link>https://dev.to/fj_palacios/defining-functions-in-python-def-parameters-and-return-2ch8</link>
      <guid>https://dev.to/fj_palacios/defining-functions-in-python-def-parameters-and-return-2ch8</guid>
      <description>&lt;p&gt;You copy three lines of code. Then three more. Then the same three again, but with a different number. At first it feels productive. Ten minutes later your file looks like a haunted photocopier: lots of repetition, very little intention, and every tiny change has to be made in four different places because apparently we enjoy suffering.&lt;/p&gt;

&lt;p&gt;That's where functions come in. Not as some academic ritual with fancy terminology, but as a way to tell Python: "this operation has a name; run it whenever I ask."&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a function?
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;function&lt;/strong&gt; is a reusable block of code. You give it a name, put instructions inside it, and later you execute that block by calling the function.&lt;/p&gt;

&lt;p&gt;Think of a coffee machine. You don't want to manually repeat every step each morning: grind beans, heat water, press button, stare into the void while life compiles. You want to press &lt;code&gt;make_coffee()&lt;/code&gt; and let the process happen. A function is that: a name attached to a sequence of steps.&lt;/p&gt;

&lt;p&gt;In the previous lesson you worked with lists and methods like &lt;a href="https://dev.to/en/tutorials/list-methods-python"&gt;&lt;code&gt;append()&lt;/code&gt; and &lt;code&gt;remove()&lt;/code&gt;&lt;/a&gt;. If you think about it, you've already been using functions constantly: &lt;code&gt;print()&lt;/code&gt;, &lt;code&gt;len()&lt;/code&gt;, &lt;code&gt;range()&lt;/code&gt;. The difference now is that you're going to create your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first function with def
&lt;/h2&gt;

&lt;p&gt;In Python, you define a function with the &lt;code&gt;def&lt;/code&gt; keyword:&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;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, Python&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 &lt;strong&gt;defines&lt;/strong&gt; the function, but it doesn't run it yet. It's like putting a recipe in a drawer: it exists, but nobody is cooking.&lt;/p&gt;

&lt;p&gt;To execute it, call the function by name with parentheses:&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;greet&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, Python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;Hello&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;function_name&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Function body
&lt;/span&gt;    &lt;span class="n"&gt;statement_1&lt;/span&gt;
    &lt;span class="n"&gt;statement_2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;def&lt;/code&gt; line ends with &lt;code&gt;:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The function body is indented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you forget the indentation, Python complains. And this time Python is right. Indentation tells Python which code belongs inside the function and which code is outside. It's not decoration; it's syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why functions matter
&lt;/h2&gt;

&lt;p&gt;Look at this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;
&lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&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;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;
&lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&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;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;
&lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;
&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&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;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works. Duct tape on a leaking pipe also works for a while. That doesn't make it architecture.&lt;/p&gt;

&lt;p&gt;The issue isn't that the code is long; the issue is that the same idea is repeated. If the tax rate changes tomorrow, you have to update it everywhere. Miss one place and congratulations: you've created an accounting bug, which sounds boring until it bites.&lt;/p&gt;

&lt;p&gt;With a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&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;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/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;30.25
48.4
119.79
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the logic lives in one place. That's the &lt;strong&gt;DRY&lt;/strong&gt; principle: &lt;em&gt;Don't Repeat Yourself&lt;/em&gt;. Not because repetition is morally wrong, but because repetition multiplies the number of places where bugs can hide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameters and arguments
&lt;/h2&gt;

&lt;p&gt;In this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.21&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tax&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;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;price&lt;/code&gt; is a &lt;strong&gt;parameter&lt;/strong&gt;. It's the variable the function expects to receive.&lt;/p&gt;

&lt;p&gt;When you call the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;calculate_total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;25&lt;/code&gt; is an &lt;strong&gt;argument&lt;/strong&gt;. It's the actual value passed into that parameter.&lt;/p&gt;

&lt;p&gt;The distinction sounds like quiz material until you need to explain code precisely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parameter&lt;/strong&gt;: the name in the function definition&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argument&lt;/strong&gt;: the value sent when calling the function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like a reserved seat versus the person sitting in it. The seat is the parameter; the person is the argument. If you're like me when I started, you'll call everything a parameter for a while. You'll survive. Python won't confiscate your keyboard.&lt;/p&gt;

&lt;p&gt;You can define multiple parameters:&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;show_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 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; years old&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;show_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;show_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Luis&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&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;Ana is 34 years old
Luis is 28 years old
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Order matters: Python assigns the first argument to the first parameter, the second argument to the second parameter, and so on. In the next lesson we'll look at more flexible ways to call functions, but for now keep the simple rule: order wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  return: giving a value back
&lt;/h2&gt;

&lt;p&gt;So far our functions print things. That's fine for seeing output, but useful functions usually &lt;strong&gt;return&lt;/strong&gt; a value so the rest of your program can keep working with it.&lt;/p&gt;

&lt;p&gt;That's what &lt;code&gt;return&lt;/code&gt; is for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&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;8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between &lt;code&gt;print()&lt;/code&gt; and &lt;code&gt;return&lt;/code&gt; is massive:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;print()&lt;/code&gt; displays something on the screen&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return&lt;/code&gt; gives a value back to the code that called the function&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters. &lt;code&gt;print()&lt;/code&gt; is like saying something out loud. &lt;code&gt;return&lt;/code&gt; is like handing someone the tool they need to continue the job. One creates output; the other lets your program move forward.&lt;/p&gt;

&lt;p&gt;Look at this example:&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;create_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Marta&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HELLO, MARTA
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the function returns a string, you can store it, transform it, put it in a list, compare it, whatever you need. If it only printed the string, the value would disappear into the terminal like tears in production logs.&lt;/p&gt;

&lt;h2&gt;
  
  
  return exits the function
&lt;/h2&gt;

&lt;p&gt;When Python reaches a &lt;code&gt;return&lt;/code&gt;, it leaves the function immediately:&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;classify_age&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;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adult&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;classify_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;classify_age&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/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;minor
adult
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;age&lt;/code&gt; is less than &lt;code&gt;18&lt;/code&gt;, Python returns &lt;code&gt;"minor"&lt;/code&gt; and doesn't continue running the rest of the function. This lets you write clearer functions, especially when you want to handle simple cases early and leave the general case at the end.&lt;/p&gt;

&lt;p&gt;Don't stress about patterns yet. For now, understand this: &lt;code&gt;return&lt;/code&gt; doesn't just send a value back; it also marks the exit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions that return None
&lt;/h2&gt;

&lt;p&gt;In Python, every function returns something. If you don't write a &lt;code&gt;return&lt;/code&gt;, the function returns &lt;code&gt;None&lt;/code&gt; automatically.&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;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nora&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&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;Hello, Nora
None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Feeling like "wait, I didn't ask for that &lt;code&gt;None&lt;/code&gt;"? Fair. Python does it anyway.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;None&lt;/code&gt; means "no value here". It's not &lt;code&gt;0&lt;/code&gt;, not &lt;code&gt;""&lt;/code&gt;, not &lt;code&gt;False&lt;/code&gt;. It's its own thing: a special value that represents the absence of a useful result.&lt;/p&gt;

&lt;p&gt;This usually appears when a function performs an effect — like printing to the screen — but doesn't return data for the rest of the program to use. Later, when you start writing larger programs, the difference between "doing something" and "returning something" becomes extremely important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function names
&lt;/h2&gt;

&lt;p&gt;Function names follow the same convention as variables: &lt;code&gt;snake_case&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_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The name should explain what the function does. Don't be mysterious with your future self. Your future self is not an archaeologist looking for a side quest.&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;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&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;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ❌ Too cryptic
&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_discount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ✅ Clear intent
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good rule: if you have to read the function body to understand why the function exists, the name probably needs work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical case: simple calculator
&lt;/h2&gt;

&lt;p&gt;Let's build a tiny calculator using functions. No interface, no strange menus, no &lt;code&gt;eval()&lt;/code&gt; — let's not summon demons for sport.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&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;b&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cannot divide by zero&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;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="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;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="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;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="nf"&gt;divide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;15
5
50
2.0
Cannot divide by zero
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each function has a clear responsibility. &lt;code&gt;add()&lt;/code&gt; adds. &lt;code&gt;subtract()&lt;/code&gt; subtracts. &lt;code&gt;divide()&lt;/code&gt; divides and protects the problematic case. The code is simple, but it already has structure.&lt;/p&gt;

&lt;p&gt;Could you write all operations one after another instead? Of course. You could also store all your socks in the oven. The question isn't whether it can be done; the question is how much you'll suffer when the program grows.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;A function is a reusable block of code with a name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;def&lt;/code&gt; defines a function; calling the function executes its body&lt;/li&gt;
&lt;li&gt;Parameters live in the definition; arguments are the values sent when calling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;return&lt;/code&gt; gives a value back and exits the function&lt;/li&gt;
&lt;li&gt;If a function has no &lt;code&gt;return&lt;/code&gt;, it returns &lt;code&gt;None&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;print()&lt;/code&gt; displays information; &lt;code&gt;return&lt;/code&gt; lets you keep using the value&lt;/li&gt;
&lt;li&gt;Function names should use &lt;code&gt;snake_case&lt;/code&gt; and describe intention&lt;/li&gt;
&lt;li&gt;Functions reduce duplication and make code easier to change&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Create four functions: &lt;code&gt;add(a, b)&lt;/code&gt;, &lt;code&gt;subtract(a, b)&lt;/code&gt;, &lt;code&gt;multiply(a, b)&lt;/code&gt;, and &lt;code&gt;divide(a, b)&lt;/code&gt;. Then create a fifth function, &lt;code&gt;show_result(operation, result)&lt;/code&gt;, that prints a message like &lt;code&gt;"Addition: 15"&lt;/code&gt;. Test every operation with several numbers, including division by zero.&lt;/p&gt;

&lt;p&gt;Functions are the first serious step toward modular code. You're no longer writing an endless sequence of instructions; you're naming ideas, separating responsibilities, and building pieces you can reuse. In the next lesson we'll go deeper into default parameters, keyword arguments, &lt;code&gt;*args&lt;/code&gt;, and &lt;code&gt;**kwargs&lt;/code&gt; — the part where functions stop being a door and become a Swiss Army knife.&lt;/p&gt;

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

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Advanced Dockerfile instructions: ARG, ENV, ENTRYPOINT and more</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 02 Sep 2026 08:49:57 +0000</pubDate>
      <link>https://dev.to/fj_palacios/advanced-dockerfile-instructions-arg-env-entrypoint-and-more-4o7p</link>
      <guid>https://dev.to/fj_palacios/advanced-dockerfile-instructions-arg-env-entrypoint-and-more-4o7p</guid>
      <description>&lt;p&gt;Most Dockerfiles have a few lines that everyone copies from an example that worked once and never questions again. &lt;code&gt;ENTRYPOINT&lt;/code&gt; and &lt;code&gt;CMD&lt;/code&gt; near the bottom. &lt;code&gt;ARG&lt;/code&gt; and &lt;code&gt;ENV&lt;/code&gt; somewhere near the top. The container starts, the app runs, and nobody asks too many questions.&lt;/p&gt;

&lt;p&gt;Until it breaks. Or until someone on the team notices the API key getting passed through &lt;code&gt;ARG&lt;/code&gt; — which shows up in the image history for anyone to read — or asks why the container ignores the command you pass to &lt;code&gt;docker run&lt;/code&gt;. That conversation should have happened earlier. This lesson is it.&lt;/p&gt;

&lt;h2&gt;
  
  
  ARG vs ENV
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ARG&lt;/code&gt; or &lt;code&gt;ENV&lt;/code&gt;? Which one persists into the container? Which one disappears after the build? Can you combine them? Does the order in the Dockerfile matter? They look similar — both define variables, both accept a name and an optional default value — but they exist at completely different moments in the Docker lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ARG&lt;/code&gt;&lt;/strong&gt; defines a variable that only exists during the build process. Once the image is built, it's gone. The container that runs from that image never sees it. Use &lt;code&gt;ARG&lt;/code&gt; for things that affect how the image is built: the Node version, the app version string, a compile-time flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ENV&lt;/code&gt;&lt;/strong&gt; defines an environment variable that persists into the running container. Use &lt;code&gt;ENV&lt;/code&gt; for anything your application needs at runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; NODE_VERSION=20&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:${NODE_VERSION}-alpine&lt;/span&gt;

&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; APP_VERSION=1.0.0&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; APP_VERSION=${APP_VERSION}&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;NODE_VERSION&lt;/code&gt; only exists long enough to pick the base image. After that, it's gone. &lt;code&gt;APP_VERSION&lt;/code&gt; is explicitly bridged from &lt;code&gt;ARG&lt;/code&gt; to &lt;code&gt;ENV&lt;/code&gt; — that's how a build-time value makes it into the running container.&lt;/p&gt;

&lt;p&gt;You can override &lt;code&gt;ARG&lt;/code&gt; at build time, and &lt;code&gt;ENV&lt;/code&gt; at runtime:&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;# Override ARG during build&lt;/span&gt;
docker build &lt;span class="nt"&gt;--build-arg&lt;/span&gt; &lt;span class="nv"&gt;NODE_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22 &lt;span class="nt"&gt;-t&lt;/span&gt; my-app &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Override ENV when running&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;APP_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.0.0 my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The security gotcha you need to know about
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;ARG&lt;/code&gt; is not a safe place for secrets. Values passed through &lt;code&gt;ARG&lt;/code&gt; end up in the image history:&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;history &lt;/span&gt;my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;IMAGE         CREATED       CREATED BY
abc123def456  2 hours ago   CMD ["node", "index.js"]
...           ...           ARG API_KEY=s3cr3t          ← right there
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've passed an API key, token, or any credential through &lt;code&gt;ARG&lt;/code&gt;, it's recoverable from the image by anyone who can pull it. This is covered in detail in &lt;a href="https://dev.to/en/tutorials/dockerfile-best-practices-security"&gt;Dockerfile security best practices&lt;/a&gt; — if you skipped that one, it's worth going back.&lt;/p&gt;

&lt;p&gt;The rule: build-time secrets to BuildKit build secrets (next lesson). Runtime secrets to environment variables injected by your secrets management system, never hardcoded in the Dockerfile.&lt;/p&gt;

&lt;h2&gt;
  
  
  ENTRYPOINT vs CMD
&lt;/h2&gt;

&lt;p&gt;If you're like most people when they first encounter this, you've copied those lines from Stack Overflow, seen that sometimes both appear and sometimes only one, and assumed Docker has its quirks. It doesn't. It has concrete rules, and once you understand them everything clicks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CMD&lt;/code&gt;&lt;/strong&gt; defines the default command a container runs. It's fully replaceable — pass a different command to &lt;code&gt;docker run&lt;/code&gt; and &lt;code&gt;CMD&lt;/code&gt; is ignored entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt;&lt;/strong&gt; defines the executable that always runs when the container starts. You can't override it with a regular argument — you need &lt;code&gt;--entrypoint&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;p&gt;When you use both, &lt;code&gt;CMD&lt;/code&gt; provides the default arguments to &lt;code&gt;ENTRYPOINT&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# CMD only — fully replaceable command&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["echo", "hello world"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run my-image               &lt;span class="c"&gt;# → "hello world"&lt;/span&gt;
docker run my-image &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"bye"&lt;/span&gt;   &lt;span class="c"&gt;# → "bye"  (CMD replaced)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ENTRYPOINT only — fixed executable&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["echo"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run my-image               &lt;span class="c"&gt;# → ""  (nothing, no args)&lt;/span&gt;
docker run my-image &lt;span class="s2"&gt;"hello"&lt;/span&gt;      &lt;span class="c"&gt;# → "hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ENTRYPOINT + CMD — the most useful pattern&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; ubuntu:22.04&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["echo"]&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["hello world"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run my-image               &lt;span class="c"&gt;# → "hello world"  (default)&lt;/span&gt;
docker run my-image &lt;span class="s2"&gt;"other text"&lt;/span&gt; &lt;span class="c"&gt;# → "other text"  (CMD replaced)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ENTRYPOINT + CMD&lt;/code&gt; combination is what you want for CLI tools packaged as containers: the tool is fixed, the arguments are defaults you can override without knowing anything about the image internals.&lt;/p&gt;

&lt;p&gt;If you're following along but it's not fully clicking yet, that's fine — this is the combination that gets googled most often by people who've been using Docker for months. The 90% case is exec form in &lt;code&gt;ENTRYPOINT&lt;/code&gt; with &lt;code&gt;CMD&lt;/code&gt; as default arguments. The rest comes with practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell form vs exec form
&lt;/h3&gt;

&lt;p&gt;Both instructions come in two flavors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Shell form — runs via /bin/sh -c&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; echo "hello"&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; echo "hello"&lt;/span&gt;

&lt;span class="c"&gt;# Exec form — executes directly, no shell involved&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["echo", "hello"]&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["echo", "hello"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Use exec form for &lt;code&gt;ENTRYPOINT&lt;/code&gt;.&lt;/strong&gt; Shell form runs your process as a child of &lt;code&gt;/bin/sh&lt;/code&gt;, which has two annoying consequences: OS signals like &lt;code&gt;SIGTERM&lt;/code&gt; (sent when stopping a container) don't reach your process because the shell intercepts them, and on minimal images without a shell the container simply won't start. With exec form, your process is PID 1 and receives signals directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  HEALTHCHECK
&lt;/h2&gt;

&lt;p&gt;This is the instruction no one adds until a container fails silently in production and it takes longer than it should to notice. After that, they add it to everything.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;HEALTHCHECK&lt;/code&gt; tells Docker how to verify a container is functioning correctly. Docker runs the specified command on a schedule and updates the container's status: &lt;code&gt;starting&lt;/code&gt;, &lt;code&gt;healthy&lt;/code&gt;, or &lt;code&gt;unhealthy&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-alpine&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=10s --start-period=5s --retries=3 \&lt;/span&gt;
  CMD curl -f http://localhost:3000/health || exit 1

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--interval&lt;/code&gt;: how often the check runs (default: 30s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--timeout&lt;/code&gt;: if the command takes longer than this, it's a failure (default: 30s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--start-period&lt;/code&gt;: grace period at startup before failures start counting (default: 0s)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--retries&lt;/code&gt;: how many consecutive failures before marking as &lt;code&gt;unhealthy&lt;/code&gt; (default: 3)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The check command should return &lt;code&gt;0&lt;/code&gt; for healthy and &lt;code&gt;1&lt;/code&gt; for unhealthy. The &lt;code&gt;|| exit 1&lt;/code&gt; ensures that if &lt;code&gt;curl&lt;/code&gt; fails — because the server isn't responding or returns an HTTP error — the check reports failure too.&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 docker"&gt;&lt;code&gt;CONTAINER ID   IMAGE     STATUS
a1b2c3d4e5f6   my-app    Up 2 minutes (healthy)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;(healthy)&lt;/code&gt; in &lt;code&gt;docker ps&lt;/code&gt; means the HEALTHCHECK is running and passing. Docker Compose and orchestrators use this status to know whether a container is ready to accept traffic, whether it needs to be restarted, and whether to consider a deploy successful.&lt;/p&gt;

&lt;p&gt;If your image doesn't have &lt;code&gt;curl&lt;/code&gt; — Alpine or distroless images — use &lt;code&gt;wget&lt;/code&gt; or a small script in your app's runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# With wget (Alpine)&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s \&lt;/span&gt;
  CMD wget -q --spider http://localhost:3000/health || exit 1

&lt;span class="c"&gt;# With Node.js runtime&lt;/span&gt;
&lt;span class="k"&gt;HEALTHCHECK&lt;/span&gt;&lt;span class="s"&gt; --interval=30s --timeout=5s \&lt;/span&gt;
  CMD node -e "require('http').get('http://localhost:3000/health', r =&amp;gt; process.exit(r.statusCode === 200 ? 0 : 1))"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  VOLUME and EXPOSE
&lt;/h2&gt;

&lt;p&gt;Here's the part nobody documents well because it's genuinely a bit odd: &lt;code&gt;EXPOSE&lt;/code&gt; doesn't expose anything and &lt;code&gt;VOLUME&lt;/code&gt; doesn't mount anything. They're very formal comments that Docker decided to turn into instructions. The naming is optimistic.&lt;/p&gt;

&lt;h3&gt;
  
  
  EXPOSE
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;EXPOSE&lt;/code&gt; declares which port the container listens on. It does not open that port on the host. It does not make the container accessible from outside. It does nothing at runtime. It's executable documentation — it tells anyone reading the Dockerfile what port to publish, and tools like Docker Compose can discover it automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To actually publish the port to the host, you need &lt;code&gt;-p&lt;/code&gt; when 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 run &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:3000 my-app
&lt;span class="c"&gt;# Host port 8080 → container port 3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EXPOSE&lt;/code&gt; without &lt;code&gt;-p&lt;/code&gt; or &lt;code&gt;ports:&lt;/code&gt; in Compose is invisible from the outside. Add it anyway — it's good documentation practice and some orchestrators use it for service discovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  VOLUME
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;VOLUME&lt;/code&gt; declares a mount point inside the container. Docker will automatically create an anonymous volume for that path when the container starts, ensuring data persists even if the container is removed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;VOLUME&lt;/span&gt;&lt;span class="s"&gt; ["/app/data", "/app/logs"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it doesn't do: it doesn't define where on the host the data lives (that's &lt;code&gt;-v&lt;/code&gt; in &lt;code&gt;docker run&lt;/code&gt; or &lt;code&gt;volumes:&lt;/code&gt; in Compose), and it doesn't save you from having to specify the volume if you want control over 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;# Without specifying — Docker creates an anonymous volume&lt;/span&gt;
docker run my-app

&lt;span class="c"&gt;# Named volume — recommended&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; my-data:/app/data my-app

&lt;span class="c"&gt;# Bind mount — host directory&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/data:/app/data my-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The primary use of &lt;code&gt;VOLUME&lt;/code&gt; in a Dockerfile is to document which paths contain data that shouldn't be lost. It also has a practical side effect: any content copied into that path before the &lt;code&gt;VOLUME&lt;/code&gt; instruction is included in the initial volume state. Any &lt;code&gt;COPY&lt;/code&gt; or &lt;code&gt;RUN&lt;/code&gt; that writes to that path after &lt;code&gt;VOLUME&lt;/code&gt; won't behave as expected.&lt;/p&gt;

&lt;p&gt;⚠️ For this reason, put &lt;code&gt;VOLUME&lt;/code&gt; near the end of the Dockerfile, after copying everything that needs to be there.&lt;/p&gt;




&lt;p&gt;With &lt;code&gt;ARG&lt;/code&gt;, &lt;code&gt;ENV&lt;/code&gt;, &lt;code&gt;ENTRYPOINT&lt;/code&gt;, &lt;code&gt;CMD&lt;/code&gt;, &lt;code&gt;HEALTHCHECK&lt;/code&gt;, &lt;code&gt;VOLUME&lt;/code&gt;, and &lt;code&gt;EXPOSE&lt;/code&gt; in your vocabulary, you have all the tools to write Dockerfiles that aren't just functional — they're configurable, predictable, and observable.&lt;/p&gt;

&lt;p&gt;Next up is BuildKit: how to enable it, what real advantages it brings over the classic builder, and how to use it to handle build secrets and package caches without compromising security.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Open a Dockerfile from any project you have or from any open source repo. Check whether it uses shell form or exec form for &lt;code&gt;ENTRYPOINT&lt;/code&gt; and &lt;code&gt;CMD&lt;/code&gt;, whether it has a &lt;code&gt;HEALTHCHECK&lt;/code&gt;, and whether environment variables are in &lt;code&gt;ARG&lt;/code&gt; or &lt;code&gt;ENV&lt;/code&gt;. Is there anything you'd change now?&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Pull Requests and code review: how to make your code easy to review</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 01 Sep 2026 11:05:18 +0000</pubDate>
      <link>https://dev.to/fj_palacios/pull-requests-and-code-review-how-to-make-your-code-easy-to-review-4mch</link>
      <guid>https://dev.to/fj_palacios/pull-requests-and-code-review-how-to-make-your-code-easy-to-review-4mch</guid>
      <description>&lt;p&gt;I once approved a PR without reading it. Not because I was lazy — because the description was blank, the title was "fix", and the diff had six hundred lines across eleven files with no explanation of what any of it did. I ran out of patience somewhere around file four and clicked Approve. Three days later, it took down the checkout flow in production.&lt;/p&gt;

&lt;p&gt;That PR had a reviewer. It had an approval. It had zero useful review.&lt;/p&gt;

&lt;p&gt;The Pull Request as a concept is sound: before code reaches &lt;code&gt;main&lt;/code&gt;, another human looks at it. The breakdown happens when the PR is written as if review is a bureaucratic gate to get past, instead of a conversation worth having. Getting that distinction right changes everything — how fast your PRs get reviewed, how much friction the process creates, and whether code review makes your team better or just slower.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Pull Request is
&lt;/h2&gt;

&lt;p&gt;A Pull Request (PR) — or Merge Request if you're on GitLab — is a formal request to integrate the changes from one branch into another. That's the technical side. The human side is where a team looks at code together before it goes anywhere near production.&lt;/p&gt;

&lt;p&gt;The difference from a local &lt;code&gt;git merge&lt;/code&gt; is the social layer: a review process, a comment thread, a record of decisions. Not just what changed, but why, who questioned it, what alternatives were considered. That context has a value that's invisible until six months later, when you're in &lt;code&gt;git blame&lt;/code&gt; staring at a line you don't understand and there's a PR two clicks away with the entire discussion that led to it.&lt;/p&gt;

&lt;p&gt;GitHub, GitLab, Bitbucket, Azure DevOps — different implementations, same concept. I'll use GitHub terminology throughout since it's the most common, but everything translates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a PR people want to review
&lt;/h2&gt;

&lt;p&gt;The difference between a PR that gets reviewed the same day and one that sits open for three weeks is usually in how it's written, not what it changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The title
&lt;/h3&gt;

&lt;p&gt;The title is the first thing a reviewer sees. If it says &lt;code&gt;fix&lt;/code&gt; or &lt;code&gt;stuff&lt;/code&gt; or &lt;code&gt;update things&lt;/code&gt;, you've already made their job harder — they don't know if this is five minutes or two hours, and when they have four PRs queued up, they'll default to the one that gives them the least resistance.&lt;/p&gt;

&lt;p&gt;A good title says &lt;strong&gt;what the PR does&lt;/strong&gt;, in the imperative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ feat(auth): add JWT token refresh mechanism
✅ fix(cart): prevent duplicate items when clicking fast
✅ refactor: extract payment service from checkout controller

❌ fix
❌ stuff
❌ changes
❌ WIP do not merge  ← these always get merged
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you've been following &lt;a href="https://dev.to/en/tutorials/git-commit-best-practices"&gt;commit best practices&lt;/a&gt; throughout this course, your PR title can be the main commit message, or a summary that covers all the commits in the branch.&lt;/p&gt;

&lt;h3&gt;
  
  
  The description
&lt;/h3&gt;

&lt;p&gt;This is where you win or lose the reviewer. A good description answers three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Why?&lt;/strong&gt; — Context. What problem does this solve or what feature does it add? A link to the ticket or issue if there is one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What?&lt;/strong&gt; — Summary of the main changes. You don't need to detail every line — that's what the diff is for — but flag the parts that deserve special attention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How to test?&lt;/strong&gt; — Steps to verify it works. Especially important if the reviewer needs to run something locally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A simple template that holds up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## What does this PR do?&lt;/span&gt;

Adds JWT token refresh to prevent users from being logged out every hour.

&lt;span class="gu"&gt;## How to test&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; Log in with any test account
&lt;span class="p"&gt;2.&lt;/span&gt; Wait 15 minutes (or set JWT_EXPIRY=1m in .env.test)
&lt;span class="p"&gt;3.&lt;/span&gt; Verify the session is still active and the token has been renewed

&lt;span class="gu"&gt;## Notes&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Extracted refresh logic into AuthService to make it testable
&lt;span class="p"&gt;-&lt;/span&gt; /auth/refresh endpoint is now idempotent

Closes #342
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the change is visual, add screenshots. A screenshot beats two paragraphs describing what the new button looks like.&lt;/p&gt;

&lt;h3&gt;
  
  
  Size
&lt;/h3&gt;

&lt;p&gt;The size of a PR has a direct impact on the quality of review it gets — not as a moral judgment, but as a cognitive one.&lt;/p&gt;

&lt;p&gt;A reviewer can carefully read a 200-line diff. At 800 lines, they start looking for the Approve button to get out alive. At 2000 lines, "LGTM" arrives in four minutes and nothing real has been reviewed.&lt;/p&gt;

&lt;p&gt;The working rule: &lt;strong&gt;one PR, one thing&lt;/strong&gt;. If you're adding a feature and fixing three unrelated bugs on the side, create three PRs. They're easier to review, easier to revert if something breaks, and easier to understand in the history six months from now.&lt;/p&gt;

&lt;p&gt;Large feature? Break it down. Component first, business logic next, integration last. Or by layer: data model first, then services, then UI. Each PR should be reviewable in a sitting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a PR from the terminal
&lt;/h2&gt;

&lt;p&gt;Opening the web interface to create a PR works, but if your workflow is already in the terminal, switching to a browser breaks the flow. GitHub's official CLI — &lt;code&gt;gh&lt;/code&gt; — handles this without ceremony:&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;# macOS and Linux (Homebrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;gh

&lt;span class="c"&gt;# Ubuntu / Debian&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;gh

&lt;span class="c"&gt;# Arch Linux (AUR)&lt;/span&gt;
pacman &lt;span class="nt"&gt;-S&lt;/span&gt; github-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authenticate once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh auth login
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, creating a PR is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--title&lt;/span&gt; &lt;span class="s2"&gt;"feat(auth): add JWT token refresh"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Resolves #342. Adds automatic token refresh."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
             &lt;span class="nt"&gt;--reviewer&lt;/span&gt; teammate1,teammate2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or interactive mode, which walks you through title, description, and reviewers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check your PR status without leaving the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;list      &lt;span class="c"&gt;# All open PRs in the repo&lt;/span&gt;
gh &lt;span class="nb"&gt;pr &lt;/span&gt;status    &lt;span class="c"&gt;# Only the ones involving you&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The web interface makes sense for PRs heavy on screenshots or when your team has a description template configured in GitHub. The flow is the same either way: push the branch, go to GitHub, click "Compare &amp;amp; pull request."&lt;/p&gt;

&lt;h2&gt;
  
  
  Requesting review
&lt;/h2&gt;

&lt;p&gt;PR is open. Now what? Do you tell someone? Wait for it to show up in their list? Send a Slack message?&lt;/p&gt;

&lt;p&gt;Answer: assign reviewers explicitly. In GitHub's sidebar, or with &lt;code&gt;gh pr edit --add-reviewer&lt;/code&gt;. If your team has conventions about who reviews what, follow them. If you don't have those conventions, that's a conversation worth having.&lt;/p&gt;

&lt;p&gt;One detail that changes a lot: &lt;strong&gt;draft PRs&lt;/strong&gt;. If you have something half-done but want early feedback — on the direction, on whether the approach makes sense before you develop it fully — open it as a draft:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;create &lt;span class="nt"&gt;--draft&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A draft PR is an invitation to a conversation, not a request for approval. The advantage of opening it early is avoiding the opposite problem: two weeks working in the wrong direction, discovering it when the PR already has 800 lines and half of it needs to be thrown out.&lt;/p&gt;

&lt;p&gt;When it's ready for real review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh &lt;span class="nb"&gt;pr &lt;/span&gt;ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to review a PR (when you're the reviewer)
&lt;/h2&gt;

&lt;p&gt;Code review is a skill. Reading the code is the easy part — knowing what to look for and how to communicate it is what takes practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to look for
&lt;/h3&gt;

&lt;p&gt;Not just whether the code works. Also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Is it correct?&lt;/strong&gt; — Does it do what the title says? Are there edge cases not covered?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it readable?&lt;/strong&gt; — Will someone understand this six months from now without having to ask?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it consistent with the rest of the codebase?&lt;/strong&gt; — Does it follow established patterns, or introduce a new way of doing things without prior agreement?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Are the tests sufficient?&lt;/strong&gt; — Not "are there tests?" but "do they cover the cases that matter?"&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to say it
&lt;/h3&gt;

&lt;p&gt;This is where many code reviews go wrong — and where the difference between a review that builds trust and one that damages it lives.&lt;/p&gt;

&lt;p&gt;The gap is whether the comment sounds like an attack on the code (and by extension, the person who wrote it) or a conversation about the problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❌ "This is wrong"
❌ "Why would you do it this way?"
❌ "Never do this"

✅ "This could cause a race condition if two requests arrive simultaneously.
   What do you think about using a mutex here?"
✅ "Pattern X tends to be more readable for this case. Not blocking,
   just a suggestion — have you considered it?"
✅ "I don't follow why this is done this way. Is there context I'm missing?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Classify your comments by severity. GitHub doesn't do this natively, but a widely used convention is prefixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;[blocking]&lt;/code&gt; — Must be fixed before merging&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[nit]&lt;/code&gt; — Minor improvement, not blocking&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[question]&lt;/code&gt; — Genuinely asking, no judgment attached&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[suggestion]&lt;/code&gt; — Alternative worth considering&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the comments come out a bit blunt at first, don't stress — it's a learned skill. The intent carries: if you're trying to improve the code rather than demonstrate superiority, the tone usually follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  The four-second LGTM problem
&lt;/h3&gt;

&lt;p&gt;"LGTM" — &lt;em&gt;Looks Good To Me&lt;/em&gt; — is the most common approval in code review. Also the most meaningless when it shows up thirty seconds after a PR with nine hundred lines of diff.&lt;/p&gt;

&lt;p&gt;A four-second review doesn't protect &lt;code&gt;main&lt;/code&gt;. It doesn't catch the subtle bug in the edge case. It doesn't flag the pattern that will cause problems in two months. All it does is give the author the false impression that someone actually reviewed the code, and the reviewer the false impression that they've done their job.&lt;/p&gt;

&lt;p&gt;If you don't have time to review a PR properly, say so. It's more honest and more useful than a courtesy LGTM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responding to feedback
&lt;/h2&gt;

&lt;p&gt;You receive comments on your PR. Eighteen of them. This is not a sign that you did something wrong. It's code review working exactly as it should.&lt;/p&gt;

&lt;p&gt;First: read all the comments before responding or making changes. Several might be related, and the fix for one resolves others.&lt;/p&gt;

&lt;p&gt;Then there are two kinds:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ones that are right.&lt;/strong&gt; Fix it, add the commit, mark the conversation as resolved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add src/auth/token-service.ts
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix(auth): handle concurrent token refresh requests"&lt;/span&gt;
git push origin feature/jwt-refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new commit appears in the PR automatically. The reviewer sees it, verifies the issue is resolved, marks the conversation done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The ones you disagree with.&lt;/strong&gt; Explain why — not as a defense of your ego, but as a technical conversation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I understand the performance concern, but in our case the JWT payload
is always small (&amp;lt;1KB) and the overhead is negligible.
What if we add a comment explaining this decision?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a blocking comment isn't resolving itself in writing, move it to a five-minute call. Text threads in code review are the worst place to resolve technical disagreements — written tone strips nuance and what is a normal difference of opinion starts reading like a conflict.&lt;/p&gt;

&lt;p&gt;And if the reviewer was right and you weren't — which happens, no drama — just fix it. Code review isn't a test of how much you know. It's a process where the whole team pushes the code upward.&lt;/p&gt;




&lt;p&gt;The Pull Request is where individual work becomes team work. A good PR doesn't just facilitate review — it documents the decision, creates context for the future, and keeps &lt;code&gt;main&lt;/code&gt; in the state it needs to be in.&lt;/p&gt;

&lt;p&gt;This wraps up the Workflows and Best Practices module. The next module covers advanced Git features — we start with &lt;strong&gt;tags&lt;/strong&gt;, which are how Git marks specific points in history to signal releases and versions.&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>GitHub Flow: the workflow for teams that ship continuously</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Mon, 31 Aug 2026 13:25:33 +0000</pubDate>
      <link>https://dev.to/fj_palacios/github-flow-the-workflow-for-teams-that-ship-continuously-1nij</link>
      <guid>https://dev.to/fj_palacios/github-flow-the-workflow-for-teams-that-ship-continuously-1nij</guid>
      <description>&lt;p&gt;Last year I watched a team spend forty minutes in a retro debating whether to adopt Git Flow. Their product: a SaaS web app. Team size: four developers. Release strategy: merge to main, pipeline deploys in ten minutes, repeat several times a day. The correct answer was no — and it took forty minutes to get there because Git Flow sounds serious and professional, and GitHub Flow sounds like the simplified version for people who haven't figured it out yet.&lt;/p&gt;

&lt;p&gt;It isn't. They're different answers to different questions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/en/tutorials/git-flow-workflow"&gt;Git Flow&lt;/a&gt; asks: "how do I manage multiple versions of my software in production simultaneously?" GitHub Flow asks: "how do I get code to production as fast as possible without breaking things?" If your project lives in the second world, Git Flow is process you don't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  What GitHub Flow is
&lt;/h2&gt;

&lt;p&gt;GitHub Flow was designed by GitHub for teams that practice &lt;strong&gt;continuous deployment&lt;/strong&gt;: code that goes through review, lands on &lt;code&gt;main&lt;/code&gt;, and ships to production without waiting for a release window.&lt;/p&gt;

&lt;p&gt;The entire model fits in two types of branches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; — always stable, always deployable. Every commit on &lt;code&gt;main&lt;/code&gt; could go to production right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature branches&lt;/strong&gt; — short-lived, focused, named after the work they do. Days, not weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No &lt;code&gt;develop&lt;/code&gt;. No &lt;code&gt;release/&lt;/code&gt;. No &lt;code&gt;hotfix/&lt;/code&gt;. Not an oversight — a deliberate design choice. Every layer removed is a layer of coordination the team doesn't have to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The six steps
&lt;/h2&gt;

&lt;p&gt;Every change in GitHub Flow follows the same cycle. No exceptions, no shortcuts.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Branch off main
&lt;/h3&gt;

&lt;p&gt;Before touching anything, create a branch. Name it after the work — not the person doing it, not the date. If you need a refresher on &lt;a href="https://dev.to/en/tutorials/branch-naming-conventions-git"&gt;branch naming conventions&lt;/a&gt;, the previous tutorial covers exactly that.&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 pull origin main          &lt;span class="c"&gt;# Always start from the latest main&lt;/span&gt;
git switch &lt;span class="nt"&gt;-c&lt;/span&gt; feature/123-dark-mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One rule that sounds obvious and gets skipped more than it should: always start from an updated &lt;code&gt;main&lt;/code&gt;. Starting from a stale &lt;code&gt;main&lt;/code&gt; creates merge debt before you've written a line.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Commit often
&lt;/h3&gt;

&lt;p&gt;Work freely on the feature branch. Small, frequent commits — &lt;a href="https://dev.to/en/tutorials/git-commit-best-practices"&gt;commit best practices&lt;/a&gt; apply here especially, because those commits are what reviewers will read in the PR.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add src/components/ThemeToggle.tsx
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(ui): add ThemeToggle component"&lt;/span&gt;

git add src/hooks/useTheme.ts
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"feat(hooks): add useTheme hook with localStorage persistence"&lt;/span&gt;

git add src/styles/dark.css
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"style: add dark mode CSS variables"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test isn't "is the feature done?" — it's "is this commit coherent on its own?"&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Open the Pull Request
&lt;/h3&gt;

&lt;p&gt;This is where GitHub Flow diverges from just "using branches." The Pull Request is not only the mechanism to merge — it's the conversation about the code.&lt;/p&gt;

&lt;p&gt;Open it as soon as you have something to show. A draft PR is an invitation for early feedback, not a declaration that the work is ready.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feature/123-dark-mode
&lt;span class="c"&gt;# Then open the PR on GitHub, GitLab, or wherever your remote lives&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good PR has a title that says what it does (imperative: &lt;code&gt;Add dark mode support&lt;/code&gt;), a description that explains why and how to test it, and screenshots if the change is visual.&lt;/p&gt;

&lt;p&gt;If you're like me when I started, the first time someone comments on your PR it feels like an attack on your code. It isn't. It's your team thinking out loud about how the change fits the system. That friction is the process working, not failing.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Review and iterate
&lt;/h3&gt;

&lt;p&gt;While the PR is open, teammates review, comment, suggest. You respond: with more commits, with discussion, with explanations. No need to close and reopen — new commits appear in the PR automatically.&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;# Addressing review feedback&lt;/span&gt;
git add src/hooks/useTheme.ts
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"fix(hooks): persist theme preference across page reloads"&lt;/span&gt;
git push origin feature/123-dark-mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How many approvals before merging is a team decision. What matters is that someone with context looked at the code before it hit &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Deploy from the branch (optional, recommended)
&lt;/h3&gt;

&lt;p&gt;Before merging, some teams deploy the feature branch to a staging environment. If something breaks, the branch can be fixed without touching &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Platforms like Vercel, Netlify, and Railway generate a preview deployment automatically for every PR. If your infrastructure supports it, this is free validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Merge to main and deploy
&lt;/h3&gt;

&lt;p&gt;PR approved. Merge to &lt;code&gt;main&lt;/code&gt;. In GitHub Flow, this triggers the deploy — either automatically through CI/CD or manually, depending on the team's setup.&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;# On the platform: click "Merge pull request"&lt;/span&gt;
&lt;span class="c"&gt;# Or via CLI:&lt;/span&gt;
git switch main
git merge &lt;span class="nt"&gt;--no-ff&lt;/span&gt; feature/123-dark-mode
git push origin main
git branch &lt;span class="nt"&gt;-d&lt;/span&gt; feature/123-dark-mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branch is gone. The cycle closes. What was a three-day feature branch is now in production.&lt;/p&gt;

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

&lt;p&gt;The most important rule in GitHub Flow is also the hardest to hold: &lt;strong&gt;&lt;code&gt;main&lt;/code&gt; must always be deployable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Not "usually." Not "except when something's half-done." Always.&lt;/p&gt;

&lt;p&gt;This means nothing goes into &lt;code&gt;main&lt;/code&gt; without going through a PR. Not "tiny typo fixes." Not "quick config changes." Not "it's urgent, I'll push directly." That bypass is exactly what makes &lt;code&gt;main&lt;/code&gt; a branch your team can't trust.&lt;/p&gt;

&lt;p&gt;Urgent? Create the branch, open the PR, someone reviews it in ten minutes, merge. Same process. A critical bug in GitHub Flow doesn't have a special track — it's a feature branch born in urgency and merged fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Flow vs Git Flow
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Git Flow&lt;/th&gt;
&lt;th&gt;GitHub Flow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Long-lived branches&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;main&lt;/code&gt; + &lt;code&gt;develop&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;main&lt;/code&gt; only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Versioned releases&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous deployment&lt;/td&gt;
&lt;td&gt;Awkward&lt;/td&gt;
&lt;td&gt;Natural&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiple versions in production&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hotfix handling&lt;/td&gt;
&lt;td&gt;Dedicated branch type&lt;/td&gt;
&lt;td&gt;Regular feature branch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web apps / SaaS&lt;/td&gt;
&lt;td&gt;Overkill&lt;/td&gt;
&lt;td&gt;Ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Libraries / SDKs&lt;/td&gt;
&lt;td&gt;Fits well&lt;/td&gt;
&lt;td&gt;Insufficient&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The common mistake is assuming Git Flow is "the serious one" and GitHub Flow is for teams that haven't matured yet. It doesn't work that way. GitHub Flow is the right choice for continuous deployment projects regardless of team size. Git Flow is the right choice for versioned releases and multiple active versions in production.&lt;/p&gt;

&lt;p&gt;Most web apps don't have "version 2.1 in production while we develop 3.0." They have &lt;code&gt;main&lt;/code&gt;, which is what's in prod. For those projects, Git Flow adds process layers the team will pay for in friction for years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;lazygit&lt;/code&gt; from the terminal gives you a visual overview of branches and stashes without leaving the keyboard:&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;# macOS and Linux (Homebrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;lazygit

&lt;span class="c"&gt;# Ubuntu / Debian&lt;/span&gt;
apt &lt;span class="nb"&gt;install &lt;/span&gt;lazygit

&lt;span class="c"&gt;# Arch Linux (AUR)&lt;/span&gt;
pacman &lt;span class="nt"&gt;-S&lt;/span&gt; lazygit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arch users already know the drill. For those who prefer a mouse — GitHub Desktop, GitKraken, and SourceTree all visualize branch flow clearly enough. No Electron in the first one, full Electron experience in the other two.&lt;/p&gt;




&lt;p&gt;GitHub Flow won't fix your merge conflicts, won't make feature branches shorter if the team doesn't stay disciplined, and won't substitute for a real code review culture. What it does is remove process layers that, for most web projects, never should have been there.&lt;/p&gt;

&lt;p&gt;The next tutorial goes deep on &lt;strong&gt;Pull Requests&lt;/strong&gt;: how to write one people actually want to review, how to give feedback that doesn't read as a personal attack, and what makes a PR easy to merge.&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>The art of the prompt: core principles</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Fri, 28 Aug 2026 07:49:18 +0000</pubDate>
      <link>https://dev.to/fj_palacios/the-art-of-the-prompt-core-principles-2ja2</link>
      <guid>https://dev.to/fj_palacios/the-art-of-the-prompt-core-principles-2ja2</guid>
      <description>&lt;p&gt;There's a moment almost everyone hits in their first week using AI for code. You type something like "help me with this error," the AI gives you a generic answer that applies to roughly everything except your actual problem, and you close the tab convinced this whole AI thing is overhyped.&lt;/p&gt;

&lt;p&gt;Here's what no one tells you in that moment: the AI didn't fail. The prompt did.&lt;/p&gt;

&lt;p&gt;That same model, thirty seconds later, with a structured question, would have given you exactly what you needed. The difference between those two questions isn't advanced technical knowledge — it's structure. And structure can be learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the same model gives such different answers
&lt;/h2&gt;

&lt;p&gt;A quick callback to Module 1: the AI doesn't "think" in the human sense. It predicts tokens. That means if you give it little context, it fills the gaps with the most probable continuation — and for a vague question, the most probable answer is a vague one. If you want to dig into the mechanics behind that, &lt;a href="https://dev.to/en/tutorials/ai-limitations-detecting-hallucinations"&gt;the hallucinations tutorial&lt;/a&gt; covers exactly how this plays out.&lt;/p&gt;

&lt;p&gt;To make this concrete, same model, same day, two separate sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt A:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How do I handle errors in Python?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical response: a clear explanation of &lt;code&gt;try/except&lt;/code&gt; with a &lt;code&gt;ZeroDivisionError&lt;/code&gt; example. Correct. Complete. Perfect for a CS101 assignment. Not remotely useful for whatever you're actually building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prompt B:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Act as a senior Python developer.

Context: I'm building a CLI tool that makes HTTP requests to an external API.
I need errors to be informative for the end user (no traceback) but I also
need the full traceback logged to a file.

Task: explain how I should structure the error handling for this case.

Format: concise answer, one working code example, and the trade-offs between
the two or three main approaches.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typical response: a well-designed &lt;code&gt;AppError&lt;/code&gt; class, two handling patterns with their pros and cons, a complete example you can adapt directly. What you actually needed.&lt;/p&gt;

&lt;p&gt;Same model. Thirty seconds apart. All because of structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The RCTF framework
&lt;/h2&gt;

&lt;p&gt;If you're anything like I was when I started, you probably assumed that "asking good questions" was one of those fuzzy skills you pick up with experience — no rules, just intuition. And experience does help. But there's also a concrete framework that works from day one.&lt;/p&gt;

&lt;p&gt;It's called &lt;strong&gt;RCTF&lt;/strong&gt;: &lt;strong&gt;R&lt;/strong&gt;ole, &lt;strong&gt;C&lt;/strong&gt;ontext, &lt;strong&gt;T&lt;/strong&gt;ask, &lt;strong&gt;F&lt;/strong&gt;ormat.&lt;/p&gt;

&lt;p&gt;It's not magic — it's a mental checklist that guarantees you're giving the AI enough to produce a useful answer. Let's go through each part.&lt;/p&gt;

&lt;h3&gt;
  
  
  R — Role
&lt;/h3&gt;

&lt;p&gt;You tell the AI what perspective it should respond from. This isn't just cosmetic: it changes how it weighs and prioritizes everything it knows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Without a role — generic answer
What's the best folder structure for a Node.js project?

# With a role — calibrated answer
Act as a backend engineer with experience on team-scale Node.js projects.
What's the best folder structure for a Node.js project?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without a role, the AI assumes a neutral viewpoint — which in practice means "generic beginner." Not because it's trying to be unhelpful, but because when no instruction is given, it defaults to the lowest common denominator. With a concrete role, the level of the response shifts.&lt;/p&gt;

&lt;p&gt;The temptation is to write something elaborate: "you are a legendary engineer with thirty years of experience and three published papers on distributed systems..." Don't bother. "Act as a senior [language/technology] developer" covers 90% of cases. The important thing is that it's there.&lt;/p&gt;

&lt;h3&gt;
  
  
  C — Context
&lt;/h3&gt;

&lt;p&gt;This is where the quality of the response is won or lost. Context is everything the AI can't know on its own: what stack you're using, what constraints you're working under, what the real problem is, what you've already tried.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Without context
I have a bug in my authentication code

# With context
I have a bug in my authentication code. Stack: Express + JWT + bcrypt.
The issue: when a token expires, the middleware returns 500 instead of 401.
I've already checked the validation middleware and the try/catch looks correct.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The more context you provide, the less the AI fills in with assumptions. A dead giveaway that your context was too thin: when the response starts with "To help you better, I'd need to know..." — that's the AI politely telling you it's working with nothing.&lt;/p&gt;

&lt;p&gt;A useful mental model: think about what you'd tell a colleague who knows nothing about your project but is an expert in the technology. Skip the onboarding deck, skip the backstory — just the minimum they need to understand your problem right now. That's context.&lt;/p&gt;

&lt;h3&gt;
  
  
  T — Task
&lt;/h3&gt;

&lt;p&gt;The specific action you want performed. Not "help me with this" — the exact verb: &lt;em&gt;review&lt;/em&gt;, &lt;em&gt;refactor&lt;/em&gt;, &lt;em&gt;explain&lt;/em&gt;, &lt;em&gt;generate&lt;/em&gt;, &lt;em&gt;compare&lt;/em&gt;, &lt;em&gt;identify&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ambiguous task
Help me with this function

# Concrete task
Review this function and identify potential memory leaks. Don't refactor it
yet — just list the issues with an explanation of why each one is a problem.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference between "help me" and "review and identify" is the difference between a response that does everything (poorly) and one that does exactly what you asked.&lt;/p&gt;

&lt;p&gt;Negative constraints work too. The AI has a natural tendency toward over-engineering — leave it unchecked and your three-line function comes back as a module with its own logging system and two interfaces. "Don't refactor it yet" stops that from happening.&lt;/p&gt;

&lt;h3&gt;
  
  
  F — Format
&lt;/h3&gt;

&lt;p&gt;How you want the response structured. Without a format, the AI guesses — and sometimes it guesses right, but often you get three paragraphs when you needed code, or code without comments when you needed an explanation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Without format — free-form response
Explain how the Node.js event loop works

# With format — exactly what you can use:
Explain how the Node.js event loop works.
Format: three paragraphs max, an ASCII diagram if it helps visualize the flow,
and a code example showing the difference between synchronous and async execution.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most useful formats for development work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code with comments&lt;/strong&gt;: for implementations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numbered list&lt;/strong&gt;: for steps, procedures, priorities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comparison table&lt;/strong&gt;: for choosing between options&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before / after&lt;/strong&gt;: for refactors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code only, no preamble&lt;/strong&gt;: when you understand the pattern and just need the output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The F is what most people skip because it feels optional. Skip it and the AI picks whatever format it feels like. Spoiler: it rarely matches what you needed. It's not optional when you have to actually use the response for something.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt B, revisited
&lt;/h3&gt;

&lt;p&gt;With the framework in mind, Prompt B from earlier doesn't look long or complicated anymore — it's just the four parts in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[R] Act as a senior Python developer.

[C] I'm building a CLI tool that makes HTTP requests to an external API.
    I need errors to be informative for the end user (no traceback) but I also
    need the full traceback logged to a file.

[T] Explain how I should structure the error handling for this case.

[F] Concise answer, one working code example, and the trade-offs between
    the two or three main approaches.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't worry if your prompts feel long at first. Length isn't the problem — lack of structure is. Over time, writing in RCTF becomes automatic. You stop thinking of it as a framework and it's just how you write.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common anti-patterns
&lt;/h2&gt;

&lt;p&gt;Three prompt failure modes show up over and over. Worth naming them directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Too vague
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Help me with my authentication function"
"This isn't working"
"How do I improve this code?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which function? What exactly isn't working? Improve it in what direction — performance, readability, security, test coverage? The AI will try to answer, but it'll be guessing. The practical rule: if your prompt doesn't have at least one concrete verb and one specific piece of context, it's too vague.&lt;/p&gt;

&lt;h3&gt;
  
  
  No constraints
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ❌ No constraints
"Write a function to manage users"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What language? What operations does it need? Is there a database? What fields does a user have? Is this a full CRUD or just the authentication piece?&lt;/p&gt;

&lt;p&gt;Without constraints, the AI generates something plausible that probably isn't what you need. Constraints don't limit the response — they focus it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# ✅ With constraints
&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a Python function that takes a user_id, queries PostgreSQL, and returns
the user or None if not found. Use psycopg2. No ORM.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  No format specification
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# ❌ No format → wall of text
"Explain the differences between SQL and NoSQL"

# ✅ Format specified → something you can actually use
"Explain the differences between SQL and NoSQL.
Format: a comparison table with the most relevant selection criteria for a
backend developer, followed by three concrete use cases."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The no-format version gives you a correct answer. The with-format version gives you a correct answer you can process and use in the next two minutes.&lt;/p&gt;




&lt;p&gt;RCTF is, at its core, a four-item checklist. Not glamorous. But the difference in response quality when all four are present is significant enough that it's worth making it a habit before this module is done. Most bad prompts don't fail on all four at once — they fail on one, and one is enough to make the answer useless.&lt;/p&gt;

&lt;p&gt;In the next tutorial, we go deep on the &lt;strong&gt;C&lt;/strong&gt; in RCTF: what rich context actually looks like, how iterative prompting works, and how to specify complex formats so the response connects directly to your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Challenge:&lt;/strong&gt; Take three questions you've asked an AI recently (or invent them if you haven't) and rewrite each one using RCTF. For each rewrite, identify which of the four parts changes the result the most. The answer tends to be surprising.&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>List methods in Python: append, remove, and list comprehensions</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Thu, 27 Aug 2026 09:52:43 +0000</pubDate>
      <link>https://dev.to/fj_palacios/list-methods-in-python-append-remove-and-list-comprehensions-543p</link>
      <guid>https://dev.to/fj_palacios/list-methods-in-python-append-remove-and-list-comprehensions-543p</guid>
      <description>&lt;p&gt;You have the list. The one from the previous tutorial — shopping cart items, player names, search results. You created it, iterated over it with &lt;code&gt;for&lt;/code&gt;, checked membership with &lt;code&gt;in&lt;/code&gt;. All good. Then the user adds a product, removes another, and you want to know how many times "t-shirt" appears in their order history. And that's when you realize: knowing how to create a list isn't the same as knowing how to work with one.&lt;/p&gt;

&lt;p&gt;Methods are where lists stop being a static container and become actually useful.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding elements
&lt;/h2&gt;

&lt;p&gt;The most common operation by far is &lt;code&gt;append()&lt;/code&gt; — it adds one element to the end:&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;cart&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;t-shirt&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;jeans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sneakers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["t-shirt", "jeans", "sneakers"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need to insert at a specific position, &lt;code&gt;insert(index, value)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;socks&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# Insert at index 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;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["t-shirt", "socks", "jeans", "sneakers"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the trap that catches everyone exactly once: &lt;code&gt;append()&lt;/code&gt; vs &lt;code&gt;extend()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;extend()&lt;/code&gt; adds every element of another iterable, one by one. &lt;code&gt;append()&lt;/code&gt; adds the object as a single element — if you pass it a list, it inserts that entire list as one nested element:&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;cart&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;t-shirt&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;jeans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sneakers&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;cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["t-shirt", "jeans", "sneakers", "cap"] ✅
&lt;/span&gt;
&lt;span class="n"&gt;cart2&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;t-shirt&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;jeans&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cart2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sneakers&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;cap&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ["t-shirt", "jeans", ["sneakers", "cap"]] ❌ — nested list
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple rule: adding one item? &lt;code&gt;append()&lt;/code&gt;. Merging two lists? &lt;code&gt;extend()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removing elements
&lt;/h2&gt;

&lt;p&gt;Python gives you four ways to remove elements, and each one covers a slightly different case. It's not that the language is broken — the use cases are genuinely different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;remove(value)&lt;/code&gt;&lt;/strong&gt; deletes the first occurrence of a specific value:&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;fruits&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;apple&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;pear&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;grape&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;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["apple", "grape", "pear"] — only the first one removed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ If the value doesn't exist, &lt;code&gt;remove()&lt;/code&gt; raises a &lt;code&gt;ValueError&lt;/code&gt;. Guard with &lt;code&gt;in&lt;/code&gt; if you're not sure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kiwi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kiwi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;pop(index)&lt;/code&gt;&lt;/strong&gt; removes by position and &lt;strong&gt;returns the removed element&lt;/strong&gt;. Without an index, it removes the last one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;fruits&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;apple&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;pear&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;grape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      &lt;span class="c1"&gt;# Removes "grape" and returns it
&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;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# "grape"
&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;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ["apple", "pear"]
&lt;/span&gt;
&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# Removes "apple"
&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;first&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# "apple"
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key difference from &lt;code&gt;remove()&lt;/code&gt;: &lt;code&gt;pop()&lt;/code&gt; works with indexes and gives back what it removed. Useful when you need the element before discarding it — process and delete in one step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;del&lt;/code&gt;&lt;/strong&gt; is a language statement (not a method). It removes by index or slice, returning nothing:&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;fruits&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;apple&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;pear&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;grape&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;kiwi&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;orange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;fruits&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="c1"&gt;# Removes "pear"
&lt;/span&gt;&lt;span class="k"&gt;del&lt;/span&gt; &lt;span class="n"&gt;fruits&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;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;     &lt;span class="c1"&gt;# Removes a slice — "grape" and "kiwi" after the previous del
&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;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;       &lt;span class="c1"&gt;# ["apple", "orange"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when you need to empty a list entirely while keeping the same reference, &lt;code&gt;clear()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;cart&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;t-shirt&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;jeans&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;sneakers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&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;cart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# []
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't the same as &lt;code&gt;cart = []&lt;/code&gt;. With &lt;code&gt;cart = []&lt;/code&gt; you create a brand-new list and orphan the old one — anything else pointing to the original won't see the change. With &lt;code&gt;clear()&lt;/code&gt; you empty the one you already have, in place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modifying elements directly
&lt;/h2&gt;

&lt;p&gt;Access an index and assign a new value:&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;fruits&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;apple&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;pear&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;grape&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;peach&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["peach", "pear", "grape"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also works with slices — replace an entire range at once:&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;fruits&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;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mango&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;papaya&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# ["peach", "mango", "papaya"]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Searching inside a list
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;index(value)&lt;/code&gt; returns the position of the first occurrence:&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;fruits&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;apple&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;pear&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;grape&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;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fruits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 1 — first occurrence
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️ Like &lt;code&gt;remove()&lt;/code&gt;, it raises &lt;code&gt;ValueError&lt;/code&gt; if the element doesn't exist. Protect yourself with &lt;code&gt;in&lt;/code&gt; before calling it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;count(value)&lt;/code&gt; counts how many times an element appears:&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;votes&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;yes&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;no&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;yes&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;yes&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;no&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;yes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;votes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;yes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="c1"&gt;# 4
&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;votes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  List comprehensions: the first time Python surprises you
&lt;/h2&gt;

&lt;p&gt;This is the part where Python does something that, the first time you see it, looks too clean to be real — but it works, and after five minutes you'll wonder why every language doesn't do this.&lt;/p&gt;

&lt;p&gt;Say you have a list of numbers and you want a new list with all of them squared. The long way:&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;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# [1, 4, 9, 16, 25]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works. Four lines. Now the same operation with a &lt;strong&gt;list comprehension&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# [1, 4, 9, 16, 25]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line. Same result. Read it left to right: "give me &lt;code&gt;n ** 2&lt;/code&gt; for every &lt;code&gt;n&lt;/code&gt; in &lt;code&gt;numbers&lt;/code&gt;." If that reads almost like plain English, it's because Python designed it exactly that way.&lt;/p&gt;

&lt;p&gt;The syntax is &lt;code&gt;[expression for variable in iterable]&lt;/code&gt;. Add a condition to filter:&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;evens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# [2, 4]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;[expression for variable in iterable if condition]&lt;/code&gt;. Only elements that satisfy the condition make it into the result.&lt;/p&gt;

&lt;p&gt;Feeling like this shouldn't be this comfortable? Fair. Developers coming from Java or C++ stare at this with a mix of confusion and mild jealousy. JavaScript folks have &lt;code&gt;map()&lt;/code&gt; and &lt;code&gt;filter()&lt;/code&gt; that do the same thing, just with more ceremony. Don't stress if it feels like magic at first — write it slowly, read it aloud, and in a week you'll wonder how you lived without it.&lt;/p&gt;

&lt;p&gt;For now, stick to the simple cases: transforming elements and filtering lists. Comprehensions can get more complex, but the pattern is always the same.&lt;/p&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;append(value)&lt;/code&gt; adds one element to the end; &lt;code&gt;extend(iterable)&lt;/code&gt; merges element by element&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;remove(value)&lt;/code&gt; deletes by value (first occurrence); raises &lt;code&gt;ValueError&lt;/code&gt; if not found&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pop(index)&lt;/code&gt; removes by position and returns the element; without index, removes the last&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;del list[index]&lt;/code&gt; removes by position without returning anything; also accepts slices&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;clear()&lt;/code&gt; empties the list keeping the same reference; different from &lt;code&gt;list = []&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;index(value)&lt;/code&gt; returns the position of the first occurrence&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;count(value)&lt;/code&gt; counts how many times an element appears&lt;/li&gt;
&lt;li&gt;List comprehension: &lt;code&gt;[expression for variable in iterable if condition]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That wraps up the Control Flow module. You've gone from conditionals to loops, from basic lists to full method coverage and list comprehensions — you have the tools to handle real collections of data. Next up: functions. You'll learn to define reusable blocks of logic with &lt;code&gt;def&lt;/code&gt;, pass parameters, return values, and write code that doesn't need to be read from top to bottom every time something breaks.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Start with this task list: &lt;code&gt;["email", "call", "email", "meeting", "email"]&lt;/code&gt;. Work through it step by step: (1) add &lt;code&gt;"report"&lt;/code&gt; to the end with &lt;code&gt;append()&lt;/code&gt;; (2) remove the first task using &lt;code&gt;pop(0)&lt;/code&gt; and store it in a variable; (3) remove all occurrences of &lt;code&gt;"email"&lt;/code&gt; using a &lt;code&gt;while&lt;/code&gt; loop and &lt;code&gt;remove()&lt;/code&gt;; (4) create a new list containing only tasks with more than 4 letters, using a list comprehension. Print the result after each step.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
    <item>
      <title>Multi-stage builds in Docker: smaller, cleaner images</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Wed, 26 Aug 2026 08:53:52 +0000</pubDate>
      <link>https://dev.to/fj_palacios/multi-stage-builds-in-docker-smaller-cleaner-images-14dn</link>
      <guid>https://dev.to/fj_palacios/multi-stage-builds-in-docker-smaller-cleaner-images-14dn</guid>
      <description>&lt;p&gt;There's a particular kind of waste that happens in Docker images and that everyone accepts as normal until someone points it out: shipping the compiler to production.&lt;/p&gt;

&lt;p&gt;You need the Go toolchain to build your binary. You don't need it to run it. You need TypeScript, all your dev dependencies, and a working &lt;code&gt;tsc&lt;/code&gt; setup to compile your app. You don't need any of that once the JavaScript exists. And yet, in a single-stage Dockerfile, all of it ends up in the image that gets pushed, pulled, and deployed.&lt;/p&gt;

&lt;p&gt;Multi-stage builds are Docker's answer to this: use as many environments as you need to build your application, then carry only what's needed to run it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The single-stage problem
&lt;/h2&gt;

&lt;p&gt;The most natural Dockerfile for a Go application looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Single-stage — drags the entire toolchain to production&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; golang:1.22&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go build &lt;span class="nt"&gt;-o&lt;/span&gt; server .
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker images my-app
&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;REPOSITORY   TAG       IMAGE ID       SIZE
my-app       latest    a1b2c3d4e5f6   823MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;823 MB. Your application binary is 12 MB. The other 811 MB is the Go compiler, the complete toolchain, Debian system libraries, and everything the build process touched — none of which runs a single instruction in production.&lt;/p&gt;

&lt;p&gt;The same pattern plays out with TypeScript. You need &lt;code&gt;typescript&lt;/code&gt;, &lt;code&gt;ts-node&lt;/code&gt;, type definitions, and dev tooling to compile. Once you have the output JavaScript, none of that is needed. But without multi-stage builds, all of it rides along.&lt;/p&gt;

&lt;h2&gt;
  
  
  How multi-stage builds work
&lt;/h2&gt;

&lt;p&gt;The mechanism is simple: use multiple &lt;code&gt;FROM&lt;/code&gt; instructions in a single Dockerfile. Each &lt;code&gt;FROM&lt;/code&gt; starts a new stage with its own isolated filesystem. The &lt;code&gt;COPY --from=&amp;lt;stage&amp;gt;&lt;/code&gt; instruction lets you pull specific files from a previous stage into the current one.&lt;/p&gt;

&lt;p&gt;The last &lt;code&gt;FROM&lt;/code&gt; in the file defines the final image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: build&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;golang:1.22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; go.mod go.sum ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;go mod download
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nv"&gt;CGO_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;GOOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;linux go build &lt;span class="nt"&gt;-ldflags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"-w -s"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; server .

&lt;span class="c"&gt;# Stage 2: run&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/server /server&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; my-app &lt;span class="nb"&gt;.&lt;/span&gt;
docker images my-app
&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;REPOSITORY   TAG       IMAGE ID       SIZE
my-app       latest    f7e8d9c0b1a2   11.2MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;823 MB to 11 MB. The &lt;code&gt;builder&lt;/code&gt; stage uses the full Go toolchain to compile. The final image starts from &lt;code&gt;scratch&lt;/code&gt; — literally an empty filesystem — and contains only the compiled binary.&lt;/p&gt;

&lt;p&gt;A few things worth noting about the build command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CGO_ENABLED=0&lt;/code&gt;&lt;/strong&gt;: disables CGo and produces a fully statically linked binary. It doesn't depend on any system libraries, which is what makes it work from &lt;code&gt;scratch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;-ldflags="-w -s"&lt;/code&gt;&lt;/strong&gt;: strips the symbol table and debug information. Not needed in production, reduces binary size noticeably.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;AS builder&lt;/code&gt;&lt;/strong&gt;: names the stage so you can reference it in &lt;code&gt;COPY --from=builder&lt;/code&gt;. Name it whatever makes sense.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  FROM scratch and its implications
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;FROM scratch&lt;/code&gt; is the smallest possible base: an empty filesystem. Ideal for self-contained Go binaries, but with one consequence — there's nothing else there. No shell, no SSL certificates, no system utilities.&lt;/p&gt;

&lt;p&gt;If your application makes HTTPS requests, you need root certificates. Copy them from the build stage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; scratch&lt;/span&gt;
&lt;span class="c"&gt;# Copy SSL certificates for outbound HTTPS&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/server /server&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you need more than a bare binary but less than a full OS, &lt;strong&gt;distroless&lt;/strong&gt; images from Google are the middle ground: no shell, no package manager, but with the essential runtime libraries, timezone data, and certificates already in place. They're also a meaningful security improvement — less surface area, fewer things to patch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; gcr.io/distroless/static-debian12&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/server /server&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["/server"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real example: Node.js with TypeScript
&lt;/h2&gt;

&lt;p&gt;The same pattern delivers similar results for JavaScript projects. The gains come from two directions: keeping dev dependencies out of the final image, and not shipping TypeScript source when only the compiled output is needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Stage 1: compile TypeScript&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json tsconfig.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src/ ./src/&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Stage 2: production&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; appgroup appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/index.js"]&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;REPOSITORY   TAG          SIZE
my-app-ts    single       687MB   (typescript, dev deps, source files — all of it)
my-app-ts    multi-stage  198MB   (node, prod deps, compiled dist only)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;builder&lt;/code&gt; stage installs everything — TypeScript, type definitions, linters, whatever the build needs — compiles, and its job is done. The &lt;code&gt;production&lt;/code&gt; stage starts fresh, installs only runtime dependencies, and copies just the compiled JavaScript from &lt;code&gt;dist&lt;/code&gt;. The TypeScript source never makes it into the final image.&lt;/p&gt;

&lt;p&gt;Notice the non-root user setup from &lt;a href="https://dev.to/en/tutorials/dockerfile-best-practices-security"&gt;the previous lesson&lt;/a&gt; is in the production stage. Security practices apply to the final image regardless of how many stages preceded it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multiple targets in one Dockerfile
&lt;/h2&gt;

&lt;p&gt;This is where multi-stage builds get genuinely powerful: instead of maintaining separate Dockerfiles for development, testing, and production, you can have them all in one file and build whichever target you need with &lt;code&gt;--target&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Shared base&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:20-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;

&lt;span class="c"&gt;# Target: development (with hot reload)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "run", "dev"]&lt;/span&gt;

&lt;span class="c"&gt;# Target: test&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;test&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["npm", "test"]&lt;/span&gt;

&lt;span class="c"&gt;# Target: builder (intermediate, not a final target)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="c"&gt;# Target: production&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;production&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /app/dist ./dist&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "dist/index.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building each environment:&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;# Development server with hot reload&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; development &lt;span class="nt"&gt;-t&lt;/span&gt; my-app:dev &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;/src:/app/src my-app:dev

&lt;span class="c"&gt;# Run the test suite&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; my-app:test &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; my-app:test

&lt;span class="c"&gt;# Production image&lt;/span&gt;
docker build &lt;span class="nt"&gt;--target&lt;/span&gt; production &lt;span class="nt"&gt;-t&lt;/span&gt; my-app:prod &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you don't specify &lt;code&gt;--target&lt;/code&gt;, Docker builds the last stage in the file — in this case &lt;code&gt;production&lt;/code&gt;. That means &lt;code&gt;docker build .&lt;/code&gt; in CI produces the right image without extra flags, and the other targets are available whenever you need them.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;base&lt;/code&gt; stage is a shared starting point. Change the Node version or add a global package once, and all four targets inherit it.&lt;/p&gt;




&lt;p&gt;Multi-stage builds are one of those features that, once you understand them, make you wonder how you were shipping images before. One Dockerfile, multiple environments, only what's needed at runtime.&lt;/p&gt;

&lt;p&gt;Next up is Docker Compose: how to orchestrate multiple containers that need to work together — database, backend, frontend — with a single configuration file and one command.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Take any Go or Node.js/TypeScript project you have. Write a single-stage Dockerfile and measure the image size. Then convert it to multi-stage and compare. If you don't have a project handy, the minimal HTTP server from the Go example works perfectly.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Dockerfile Best Practices: Security</title>
      <dc:creator>Javi Palacios</dc:creator>
      <pubDate>Tue, 25 Aug 2026 10:35:55 +0000</pubDate>
      <link>https://dev.to/fj_palacios/dockerfile-best-practices-security-2p5g</link>
      <guid>https://dev.to/fj_palacios/dockerfile-best-practices-security-2p5g</guid>
      <description>&lt;p&gt;Run this against your current containers:&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 &lt;span class="si"&gt;$(&lt;/span&gt;docker ps &lt;span class="nt"&gt;-q&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; &lt;span class="s1"&gt;'{{ .Name }}: {{ .Config.User }}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most of them will return a blank after the colon. Blank means root. You're running production workloads as the most privileged user on the system, and nobody sent you a warning email about it.&lt;/p&gt;

&lt;p&gt;Security in Dockerfiles isn't glamorous. It doesn't make it into conference talks the way Kubernetes migrations do. But these four practices are the difference between an image you can deploy to production with confidence and one that's a liability waiting to be discovered. The &lt;a href="https://dev.to/en/tutorials/dockerfile-best-practices-efficiency"&gt;last lesson covered efficiency&lt;/a&gt; — fast builds, lean images. This one covers what keeps you employed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't run as root
&lt;/h2&gt;

&lt;p&gt;Every Docker container runs as &lt;code&gt;root&lt;/code&gt; by default. Not a sandboxed root, not an elevated user — actual UID 0, the same root that owns your filesystem. The container is isolated, yes, but isolation has failure modes.&lt;/p&gt;

&lt;p&gt;If your application has a vulnerability (and every application does), an attacker who compromises the process gets root-level access to everything in the container. If there's a container escape bug — rare, but they exist and get discovered — that root inside becomes root outside. The principle of least privilege isn't a DevSecOps buzzword; it's the reason you create separate database users instead of using &lt;code&gt;postgres&lt;/code&gt; for everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Default — process runs as root&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ Non-root user&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# Create non-root user and group&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;--system&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; appgroup appuser

&lt;span class="c"&gt;# Transfer ownership, then switch&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The order matters. &lt;code&gt;WORKDIR&lt;/code&gt; creates the directory as root before your user exists. Create the user, transfer ownership, then switch — everything after &lt;code&gt;USER appuser&lt;/code&gt; runs as that user, including the final &lt;code&gt;CMD&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can compress the setup into one layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;--system&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; appgroup appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Official Node.js images include a &lt;code&gt;node&lt;/code&gt; user out of the box, which saves a step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:20-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;production
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# node user is already there in official Node.js images&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; node:node /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scan for vulnerabilities
&lt;/h2&gt;

&lt;p&gt;Your image inherits every vulnerability in its base image and installed packages. A fresh &lt;code&gt;python:3.12-slim&lt;/code&gt; can carry dozens of CVEs that have nothing to do with your code. Most developers have no idea what's in their images because they've never looked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://trivy.dev" rel="noopener noreferrer"&gt;Trivy&lt;/a&gt;&lt;/strong&gt; is the de facto standard for image scanning — open source, fast, zero configuration to get started:&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;# macOS and Linux (Homebrew)&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;trivy

&lt;span class="c"&gt;# Ubuntu / Debian&lt;/span&gt;
wget &lt;span class="nt"&gt;-qO&lt;/span&gt; - https://aquasecurity.github.io/trivy-repo/deb/public.key | &lt;span class="se"&gt;\&lt;/span&gt;
  gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/share/keyrings/trivy.gpg &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/trivy.gpg] &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
  https://aquasecurity.github.io/trivy-repo/deb generic main"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/trivy.list
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; trivy

&lt;span class="c"&gt;# Arch Linux (AUR)&lt;/span&gt;
paru &lt;span class="nt"&gt;-S&lt;/span&gt; trivy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scan any image with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image python:3.12-slim
&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;python:3.12-slim (debian 12.10)
===============================
Total: 47 (UNKNOWN: 0, LOW: 28, MEDIUM: 14, HIGH: 4, CRITICAL: 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a contrived example — base images carry OS-level CVEs from Debian, OpenSSL, and system libraries. Most are LOW or MEDIUM: background noise you live with. HIGH and CRITICAL are what you act on. Filter to see only those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL my-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can scan your own built images too, not just bases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;trivy image my-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're on Docker Desktop or Docker &amp;gt;= 24, &lt;strong&gt;Docker Scout&lt;/strong&gt; is already installed and covers similar ground:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker scout cves my-app:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trivy tends to be more thorough and is what you'll see in most CI/CD pipelines. Scout is convenient if you prefer the Docker Desktop dashboard and don't want another CLI tool — for those who like their metrics with a GUI and don't mind clicking around (no judgment, it works).&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing vulnerabilities by rebuilding
&lt;/h3&gt;

&lt;p&gt;Scanning tells you what's there. The fix is often simpler than expected: rebuild against an updated base image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull python:3.12-slim      &lt;span class="c"&gt;# Pull the latest base&lt;/span&gt;
docker build &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;         &lt;span class="c"&gt;# Rebuild without cache to pick up updates&lt;/span&gt;
trivy image &lt;span class="nt"&gt;--severity&lt;/span&gt; HIGH,CRITICAL my-app:latest   &lt;span class="c"&gt;# Check again&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A surprising number of CVEs in "old" images disappear just from rebuilding. The maintainers of official images patch continuously. If you never rebuild, you never get those patches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pin versions correctly
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://dev.to/en/tutorials/dockerfile-best-practices-efficiency"&gt;last lesson&lt;/a&gt; covered pinning your base image tag — &lt;code&gt;python:3.12.10-slim&lt;/code&gt; instead of &lt;code&gt;python:3.12-slim&lt;/code&gt; — for reproducibility. But tags have a security problem: &lt;strong&gt;tags are mutable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Anyone with push access to a registry can overwrite a tag with different content. For official Docker Hub images this essentially never happens, but in CI/CD pipelines pulling from private registries or third-party sources, it's a real supply chain attack vector.&lt;/p&gt;

&lt;p&gt;The immutable alternative is the &lt;strong&gt;SHA256 digest&lt;/strong&gt;:&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;# Get the digest of an image&lt;/span&gt;
docker inspect python:3.12-slim &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{{index .RepoDigests 0}}'&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;python@sha256:f4f0ef4e6a9a7bbd4954d6e5f4cd89b21f483d39beee63c3a17c840c10f5dd96
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Floating tag — can change without warning&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="c"&gt;# ✅ Pinned tag — better, but still mutable&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12.10-slim-bookworm&lt;/span&gt;

&lt;span class="c"&gt;# ✅✅ SHA256 digest — immutable by definition&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim@sha256:f4f0ef4e6a9a7bbd4954d6e5f4cd89b21f483d39beee63c3a17c840c10f5dd96&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A digest is the hash of the image content. That image is exactly that image, forever, on any machine. If someone replaces the tag, the digest still points to the original content.&lt;/p&gt;

&lt;p&gt;The same logic applies to system packages. Unpinned installs grab whatever's available today:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ Unpinned — installs whatever version is current&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; curl

&lt;span class="c"&gt;# ✅ Pinned version — reproducible&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nv"&gt;curl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8.5.0-2ubuntu1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To see what versions are available:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-cache madison curl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you need digest pinning for every project? Probably not for a personal side project. Yes for production images handling sensitive data or sitting in regulated environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secrets: what must never be in your image
&lt;/h2&gt;

&lt;p&gt;This is where most people trip up. And it makes sense: you need credentials during development. The obvious solution is &lt;code&gt;ENV&lt;/code&gt; or &lt;code&gt;ARG&lt;/code&gt; in the Dockerfile. It's convenient, it works, and it's a ticking clock.&lt;/p&gt;

&lt;p&gt;The obvious problem is "it ends up in git." The less obvious problem is worse: &lt;strong&gt;even if it never touches git, every Docker layer is permanently recorded in the image&lt;/strong&gt;. Delete an &lt;code&gt;ENV&lt;/code&gt; in a later layer and the value is still there, visible in the build history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ❌ The "cleanup" layer changes nothing&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; API_KEY=super-secret-key-12345&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;make some-api-call-using-the-key
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;unset &lt;/span&gt;API_KEY   &lt;span class="c"&gt;# Completely useless — still in the previous layer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;history &lt;/span&gt;my-image &lt;span class="nt"&gt;--no-trunc&lt;/span&gt;
&lt;span class="c"&gt;# IMAGE          CREATED BY&lt;/span&gt;
&lt;span class="c"&gt;# ...            ENV API_KEY=super-secret-key-12345  ← visible forever&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ARG&lt;/code&gt; has the same problem, despite what the docs imply. It doesn't persist as an environment variable in the final image — but it does persist in the layer of the &lt;code&gt;RUN&lt;/code&gt; that used it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; API_KEY&lt;/span&gt;
&lt;span class="c"&gt;# ❌ The value is baked into this layer's metadata&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://api.example.com/setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right solution for build-time secrets is &lt;strong&gt;BuildKit's &lt;code&gt;--mount=type=secret&lt;/code&gt;&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# syntax=docker/dockerfile:1&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="c"&gt;# The secret is available only during this RUN — never stored in any layer&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nt"&gt;--mount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret,id&lt;span class="o"&gt;=&lt;/span&gt;api_key &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /run/secrets/api_key&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://api.example.com/setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass the secret at build 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 build &lt;span class="nt"&gt;--secret&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api_key,src&lt;span class="o"&gt;=&lt;/span&gt;./secrets/api_key.txt &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The secret is mounted in a tmpfs during that specific &lt;code&gt;RUN&lt;/code&gt; and never written to any layer. The &lt;code&gt;# syntax=docker/dockerfile:1&lt;/code&gt; line at the top enables BuildKit's extended syntax — without it, Docker won't recognize the &lt;code&gt;--mount&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;runtime secrets&lt;/strong&gt; — database passwords, API tokens your application needs while running — the answer is straightforward: &lt;strong&gt;don't put them in the image&lt;/strong&gt;. Runtime secrets are injected from outside, at deploy time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# ✅ The image contains no credentials&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;groupadd &lt;span class="nt"&gt;--system&lt;/span&gt; appgroup &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    useradd &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="nt"&gt;--gid&lt;/span&gt; appgroup appuser &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; appuser:appgroup /app
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; appuser&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;span class="c"&gt;# The app reads DATABASE_URL from the environment at runtime — never from the image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# In development&lt;/span&gt;
docker run &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;postgres://... my-app

&lt;span class="c"&gt;# In production with Docker Compose&lt;/span&gt;
services:
  app:
    image: my-app:latest
    environment:
      DATABASE_URL: &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;  &lt;span class="c"&gt;# From the host environment, never in the Dockerfile&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more rigorous setups: Docker Secrets in Swarm, Kubernetes Secrets with proper RBAC, or a dedicated secrets manager like HashiCorp Vault. But even the basic pattern of injecting at runtime is infinitely better than credentials baked into a layer that follows the image everywhere it goes.&lt;/p&gt;




&lt;p&gt;Non-root user, vulnerability scanning with Trivy, immutable digests instead of mutable tags, secrets out of the image. Four practices that aren't complicated — they just require actually doing them.&lt;/p&gt;

&lt;p&gt;Next up is one of the most powerful Docker features for production images: &lt;strong&gt;multi-stage builds&lt;/strong&gt;. You'll learn how to separate the build environment from the runtime environment, with real examples in Go and Node.js that shrink final image sizes from hundreds of megabytes down to a fraction.&lt;/p&gt;

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




&lt;p&gt;&lt;strong&gt;💡 Challenge&lt;/strong&gt;: Take your image from the previous lesson's challenge. Add a non-root user and scan it with &lt;code&gt;trivy image --severity HIGH,CRITICAL&lt;/code&gt;. Then compare against &lt;code&gt;python:3.12-alpine&lt;/code&gt; — there's a surprise waiting for you there.&lt;/p&gt;

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