MoreRSS

site iconThe Practical DeveloperModify

A constructive and inclusive social network for software developers.
Please copy the RSS to your reader, or quickly subscribe to:

Inoreader Feedly Follow Feedbin Local Reader

Rss preview of Blog of The Practical Developer

Building a secure password reset system in Django

2026-02-15 12:11:58

If you're building a Django application with user authentication, you'll need a way for users to reset their passwords when they forget them.

Fortunately, Django comes with a built-in password reset system that handles the heavy lifting for you.

In this tutorial, I'll show you how to implement a complete password reset workflow in your Django application.

By the end of this guide, you'll have a fully functional password reset system that sends secure emails to users and allows them to reset their passwords safely.

Table of contents

  • Understanding Django's built-in password reset system
  • How to implement password reset in Django
    • Step 1: Configure email backend for password reset
    • Step 2: Set up Django password reset URLs
    • Step 3: Create custom templates
    • Step 4: Test your password reset implementation
  • Troubleshoot common issues
  • Django password reset security best practices
  • Frequently asked questions

Understanding Django's built-in password reset system

Django's django.contrib.auth module provides everything you need for password reset functionality. The system uses four main class-based views that handle the entire workflow:

  • PasswordResetView – Displays the form where users enter their email address.
  • PasswordResetDoneView – Confirms that the reset email has been sent.
  • PasswordResetConfirmView – Validates the reset link and displays the new password form.
  • PasswordResetCompleteView – Confirms successful password change.

Here's how the workflow works from a user's perspective:

Django password reset flow

The beauty of this system is that Django handles token generation, validation, and security for you. The tokens are time-sensitive (valid for 3 days by default) and can only be used once.

Note: Django doesn't reveal whether an email exists in the system when a reset is requested. This security feature prevents potential attackers from discovering valid email addresses.

How to implement password reset in Django

Prerequisites

Before we dive in, make sure you have:

  • Django installed on your machine. I recommend using v5.x or higher.
  • Basic knowledge of Django's URL routing and templates.
  • A working Django project (if you need to create one, check Django's official documentation)
  • An email service for sending emails. I'll use SendLayer for this tutorial.

SendLayer offers reliable email delivery for your Django applications. If you're looking to test your emails, SendLayer offers a generous free plan that lets you send up to 200 emails.

They also offer an affordable pricing plan that scales depending on your business needs.

Start your free trial at SendLayer

Step 1: Configure email backend for password reset

Before getting started, you'll need to configure Django to send emails. There are two main approaches: a console backend for development and SMTP for production.

Development setup: Console email backend

For local development, the console backend prints emails to your terminal rather than sending them. This is perfect for testing.

To use the console backend, open your settings.py file, and add the following snippet to it:

# settings.py

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'

With this configuration, when you trigger a password change, the email content (including the reset link) will appear in your terminal.

Production setup: SMTP configuration

For production, you'll need an actual email service. I'll show you how to configure SendLayer's SMTP server, but the process is similar for other providers.

First, add your SMTP settings to settings.py:

# settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.sendlayer.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'your-smtp-username'
EMAIL_HOST_PASSWORD = 'your-smtp-password'
DEFAULT_FROM_EMAIL = '[email protected]'

Pro Tip: Do not store sensitive information such as usernames and passwords in your codebase. Use environment variables instead.

Here's how to secure sensitive credentials on your project:

Start by creating a .env file in your project's root directory. Then add your SMTP credentials.

EMAIL_HOST_USER = 'your-sendlayer-username'
EMAIL_HOST_PASSWORD = 'your-sendlayer-password'

Important: Add .env to your .gitignore file to prevent committing sensitive credentials to version control.

After that, you'll need to install a third-party library using the command below:

pip install python-decouple

Next, return to your settings.py file and import the config module from decouple.

from decouple import config

The config method lets you access the SMTP username and password you specified in the .env file. Here's the updated email configuration settings:

# settings.py

EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.sendlayer.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = config('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD')
DEFAULT_FROM_EMAIL = '[email protected]'

Note: If you're using SendLayer, your sender email must match the domain you've authorized in your SendLayer account. For example, if you authorized example.com, your sender email should be [email protected].

Getting your SendLayer SMTP credentials

Here's how to retrieve your SMTP server details on SendLayer. Start by logging into your account area. Once there, navigate to Settings » SMTP Credentials tab.

SMTP credentials

You'll find your SMTP credentials. The SMTP host and port number are the same for all users.

Go ahead and copy the username and password. Then return to the .env file and update the dummy details with your actual credentials.

Step 2: Set up Django password reset URLs

Now, let's configure the URL patterns for our password change flow. Django's built-in views make this straightforward.

In your urls.py, add the following patterns:

# urls.py
from django.contrib.auth import views as auth_views
from django.urls import path

urlpatterns = [
    path('password-reset/', 
         auth_views.PasswordResetView.as_view(
             template_name='registration/password_reset_form.html'
         ),
         name='password_reset'),

    path('password-reset/done/', 
         auth_views.PasswordResetDoneView.as_view(
             template_name='registration/password_reset_done.html'
         ),
         name='password_reset_done'),

    path('reset/<uidb64>/<token>/', 
         auth_views.PasswordResetConfirmView.as_view(
             template_name='registration/password_reset_confirm.html'
         ),
         name='password_reset_confirm'),

    path('reset/done/', 
         auth_views.PasswordResetCompleteView.as_view(
             template_name='registration/password_reset_complete.html'
         ),
         name='password_reset_complete'),
]

Code breakdown

In the code above, we first import the authentication views from django.contrib.auth. Then we define the URL patterns for each password reset view.

The line auth_views.PasswordResetView.as_view() contains the logic for the password reset form and email notification. It accepts template_name as a parameter. This is where you specify the path to your password change form.

Each reset URL pattern follows the same format. First, you define the path, then specify the specific view, and map the template to handle it.

Understanding the URL parameters

The reset confirmation URL includes two parameters:

  • uidb64: The user's ID encoded in base64 format
  • token: A secure, time-sensitive token that validates the reset request

These parameters ensure that only the intended user can reset their password, and only within the valid timeframe.

Step 3: Create custom templates

For a Django custom password reset experience that matches your application's branding, you'll need to create custom templates.

Based on the URL pattern we defined above, Django will look for the password change templates in a registration folder within your templates directory. Let's create all the necessary templates.

First, make sure your TEMPLATES setting in settings.py includes your templates directory:

# settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Now create the following directory structure:

project/
├── templates/
│   └── registration/
│       ├── password_reset_form.html
│       ├── password_reset_email.html
│       ├── password_reset_subject.txt
│       ├── password_reset_done.html
│       ├── password_reset_confirm.html
│       └── password_reset_complete.html

Template 1: Password reset request form

File: templates/registration/password_reset_form.html

Note: This section assumes you already have a base.html template.

{% extends 'base.html' %}

{% block content %}
<div class="container">
    <h2>Forgot Your Password?</h2>
    <p>Enter your email address below, and we'll send you a link to reset your password.</p>

    <form method="post">
        {% csrf_token %}

        {% if form.errors %}
        <div class="alert alert-danger">
            {% for field in form %}
                {% for error in field.errors %}
                    <p>{{ error }}</p>
                {% endfor %}
            {% endfor %}
        </div>
        {% endif %}

        <div class="form-group">
            <label for="{{ form.email.id_for_label }}">Email Address</label>
            {{ form.email }}
        </div>

        <button type="submit" class="btn btn-primary">Send Reset Link</button>
    </form>

    <p class="mt-3">
        <a href="{% url 'login' %}">Back to Login</a>
    </p>
</div>
{% endblock %}

This template displays the initial password reset request form. It includes {% csrf_token %} for security and loops through any form errors to display validation messages. The form renders Django's email field and submits via POST to trigger the reset link email.

Template 2: Email content

This is the actual email content that's sent to users when they request a password reset. You can customize the content to match your brand.

File: templates/registration/password_reset_email.html

{% autoescape off %}
Hello,

You recently requested to reset your password for your account. Click the link below to reset it:

{{ protocol }}://{{ domain }}{% url 'password_reset_confirm' uidb64=uid token=token %}

If you didn't request a password reset, you can safely ignore this email. Your password won't be changed unless you click the link above and create a new one.

This link will expire in 3 days.

Thanks,
The {{ site_name }} Team
{% endautoescape %}

This template generates the password reset email body. The {% autoescape off %} tag prevents HTML encoding since this is a plain-text email. Django automatically provides variables like {{ protocol }}, {{ domain }}, {{ uid }}, and {{ token }} to construct the secure reset URL.

Template 3: Email subject

File: templates/registration/password_reset_subject.txt

Password Reset Request

This is a simple one-line file that sets the email subject. Django uses this template to determine what appears in the reset email's subject line.

Template 4: Confirmation page

File: templates/registration/password_reset_done.html

{% extends 'base.html' %}

{% block content %}
<div class="container">
    <h2>Password Reset Email Sent</h2>
    <p>
        We've sent you instructions for resetting your password to the email address you submitted.
    </p>
    <p>
        You should receive the email shortly. If you don't see it, please check your spam folder.
    </p>
    <p>
        If you don't receive an email, make sure you've entered the address you registered with.
    </p>
    <p class="mt-3">
        <a href="{% url 'login' %}">Return to Login</a>
    </p>
</div>
{% endblock %}

This template displays after a user submits the reset request form. It confirms that the email was sent and provides helpful tips about checking spam folders. For security, it shows the same message whether the email exists or not.

Template 5: New password form

File: templates/registration/password_reset_confirm.html

{% extends 'base.html' %}

{% block content %}
<div class="container">
    {% if validlink %}
        <h2>Enter Your New Password</h2>
        <form method="post">
            {% csrf_token %}

            {% if form.errors %}
            <div class="alert alert-danger">
                {% for field in form %}
                    {% for error in field.errors %}
                        <p>{{ error }}</p>
                    {% endfor %}
                {% endfor %}
            </div>
            {% endif %}

            <div class="form-group">
                <label for="{{ form.new_password1.id_for_label }}">New Password</label>
                {{ form.new_password1 }}
            </div>

            <div class="form-group">
                <label for="{{ form.new_password2.id_for_label }}">Confirm Password</label>
                {{ form.new_password2 }}
            </div>

            <button type="submit" class="btn btn-primary">Reset Password</button>
        </form>
    {% else %}
        <h2>Invalid Reset Link</h2>
        <p>
            The password reset link was invalid, possibly because it has already been used or has expired.
        </p>
        <p>
            Please request a new password reset.
        </p>
        <p class="mt-3">
            <a href="{% url 'password_reset' %}">Request New Reset Link</a>
        </p>
    {% endif %}
</div>
{% endblock %}

This template displays when users click the reset link in their email. The {% if validlink %} check verifies the token is still valid and hasn't been used. If valid, it shows the new password form; otherwise, it displays an error message with a link to request a new reset.

Template 6: Success message

File: templates/registration/password_reset_complete.html

{% extends 'base.html' %}

{% block content %}
<div class="container">
    <h2>Password Reset Complete</h2>
    <p>
        Your password has been successfully reset. You can now log in with your new password.
    </p>
    <p class="mt-3">
        <a href="{% url 'login' %}" class="btn btn-primary">Log In</a>
    </p>
</div>
{% endblock %}

This template displays after a successful password reset. It confirms the password was changed and provides a direct link to the login page so users can sign in with their new credentials.

Step 4: Test your password reset implementation

Now that everything is set up, let's test the complete flow.

Testing locally with console backend

Here's how to test your password reset implementation if you're using the console backend. Start by running your Django development server:

python manage.py runserver

After that, open your browser and navigate to http://localhost:8000/password-reset/

Then enter a valid email address from your database and submit the Django forgot password form.

Django password reset form frontend display

After submitting the form, check your terminal. You should see the email content printed there.

Password reset email in Django console

Go ahead and copy the reset link from the terminal output and paste it into your browser. You'll be redirected to the new password form if the token is valid.

Enter new password

After completing the password reset, you'll be directed to the success page. From here, you'll be able to log back in to your account using the new password.

Django password reset complete

Testing with real SMTP

Once you've verified the flow works locally, switch to your SMTP backend in settings.py. If you were using the console backend for testing, you'll need to update the EMAIL_BACKEND to use an SMTP server.

After updating the settings, navigate to the password reset route and request a password reset for a real email address.

You'll see a generic password request email sent notification.

Django password reset email sent

Go ahead and check your inbox for the password change email.

Password reset email Gmail inbox

If you don't find it in your main inbox, check the spam folder.

Pro Tip: Using an API-based email provider like SendLayer improves your email's deliverability and protects your domain's reputation. This ensures transactional emails get delivered to the user's inbox.

You can then use the reset link in the email to update your password.

Congratulations! You now have a fully functional password reset system in your Django app with email notification.

Troubleshoot common issues

These are answers to some of the issues I encountered and how to resolve them.

Email not sending

The Django password reset email not sending issue often occurs due to missing settings or invalid SMTP credentials.

If you're not receiving reset emails:

  • Review your SMTP settings and verify that your SMTP username, password, host, and port are correct.
  • Check your spam folder. Sometimes reset emails end up in spam, especially during development.
  • Verify your firewall settings. Make sure port 587 (or your SMTP port) isn't blocked.
  • If you're testing locally, try setting EMAIL_USE_TLS to False to allow sending emails from non-HTTPS domains.

Pro Tip: Make sure to update the EMAIL_USE_TLS settings when moving to production.

Template not found error

This error indicates Django was unable to find the password reset template at the location you set when configuring the URL pattern.

If you see TemplateDoesNotExist: registration/password_reset_form.html:

  • Verify your TEMPLATES setting includes the templates directory:
# settings.py
'DIRS': [BASE_DIR / 'templates'],
  • Check your directory structure. The templates must be in templates/registration/.
  • Verify your app order in INSTALLED_APPS. Your app should come before django.contrib.admin so Django finds your templates first.

Invalid or expired token

If users see "The password reset link was invalid", it often indicates:

  • The link has been used. Reset tokens are single-use only.
  • The link expired. By default, tokens expire after 3 days. You can customize this in your settings.py file:
# settings.py
PASSWORD_RESET_TIMEOUT = 86400  # 24 hours in seconds
  • URL configuration issue. Make sure your URL patterns match the structure in the email template.

Django password reset security best practices

Django's password reset system is secure by default, but here are some additional best practices:

Enforce strong passwords

Configure password validators in your settings.py:

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
        'OPTIONS': {'min_length': 8,}
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

Use HTTPS in production

Always use HTTPS for password reset links in production:

# settings.py (production)
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

Monitor reset attempts

Consider logging password reset attempts to detect suspicious activity:

import logging

logger = logging.getLogger(__name__)

# In your custom view
logger.info(f"Password reset requested for email: {email}")

Frequently asked questions

These are answers to some of the top questions we see about implementing password change in Django.

Can I implement password reset with Django Rest Framework?

While this tutorial covers Django's template-based password reset, you can implement password reset Django Rest Framework functionality using DRF's APIViews. You'll need to create API endpoints that trigger the same password reset flow, but return JSON responses instead of rendering templates.

Alternatively, consider using the dj-rest-auth package, which provides ready-made password reset endpoints for REST APIs. The token generation and validation logic remains the same.

Does this work with custom user models?

Yes, Django's built-in password reset system works with custom user models as long as they inherit from AbstractBaseUser and include an email field. The password reset views automatically detect your custom user model through the AUTH_USER_MODEL setting. No additional configuration is needed—the Django password reset token generator will work with any properly configured user model.

Should I use Django allauth for password reset?

This depends on your use case. If you need additional features, such as social authentication, Django-allauth is an excellent choice and handles password resets automatically.

However, if you only need basic password reset functionality, Django's built-in system is simpler and requires no additional dependencies.

Wrapping up

In this comprehensive tutorial, we've walked through everything you need to implement a secure password reset system in Django.

Have questions or want to share your Django password reset implementation? Drop a comment below—I'd love to hear how you're handling authentication in your Django projects!

Ready to implement this in your application? Start your free trial at SendLayer and send your first password reset email in minutes.

The Soul of an Agent: Why You Should Stop Worrying and Start Building

2026-02-15 12:10:14

Why You Should Stop Worrying and Start Building?

Hey there, young hustlers and code curious! 🚀 Tired of all the doom-scrolling on social media? "AI agents are gonna steal your job!" "Everything's automating away!" Chill out. These thoughts belong to U G Murthy, paraphrased by AI – and here's the real tea: AI agents aren't the villain in your story. They're your sidekick, ready to level up your game. The future isn't about fearing them; it's about mastering how to work with them. That's the superpower skill that'll keep you ahead as AI models get smarter and cheaper every day. Think Clawdbot or Moltbot going viral, or Claude CoWork shaking up stocks – more game-changers are coming. Time to gear up and build!

What's an AI Agent, Anyway? (The Fun Spectrum)

Picture agents like levels in your favorite video game – from noob to boss mode:

  • Simple Agents: Just a quick chat or a chain of convos. Like texting a buddy for advice.
  • Moderate Agents: They search the web, pull out info, analyze it. Boom, instant research ninja!
  • Epic Agents: All that plus tools, workflows, and magic. They plan, execute, and adapt on their own.

Agents don't judge – they don't know you're "stupid" (your words, not mine!). They're always down to help, explain stuff, brainstorm wild ideas, teach you new tricks, or consult like a pro. Bonus: They mutate and evolve solo, and you can make 'em play any role – chef, coder, storyteller, you name it.

Your Secret Sauce: Help Your Agent Win (And Watch Magic Happen)

Agents are smart, but they're not mind-readers. Your success = their success. Here's how to team up like pros:

  • Build the vibe: Show 'em relevant context. "Hey, check this data..."
  • Steer the ship: "Consider this angle or that one." Give direction!
  • Spill your goals: "Do this exact thing for me."
  • Spark options: "What if we try A, B, or C?"
  • Get reflective: Ask questions like, "Why this over that?" to make 'em think deeper.

Nail this, and you're unstoppable.

Why Bother? The Perks Will Blow Your Mind

Working with agents? It's a cheat code for life:

  • Fresh ideas galore: Exposure to solutions you never dreamed of.
  • Ultimate sounding board: Bounce thoughts, reflect, and grow sharper.
  • Learn at warp speed: Pick up skills faster than binge-watching tutorials.
  • Playtime vibes: The journey's the real win – experiment, fail fast, laugh it off.
  • Unlock new worlds: Dive into stuff you never knew existed.
  • Bravery boost: With an agent in your corner, take risks, build the unknown. No fear!

My Wild Ride (U G Murthy's Story – Straight Fire)

I retired in 2016 with dusty programming skills from my youth – off coding for 20+ years. But hey, time on my hands? I dove back in, learned fresh, and loved it. Fast-forward to 2025: Coding agents popped up. I tried 'em all – CoPilot, Bolt, Cursor, Windsurf, Kilo Code (solid for a bit), then boom, AmpCode stole my heart.

Got hyped from blogs by Lex Fridman and Dwarfish, but my fave? The Latent Space podcast The AI Engineer Podcast – that's where agents hooked me. By 2026, I was building one without a plan. Every step sparked new ideas, like prizes for grinding the last level. Sleepless nights? Sure. But waking up buzzing with vibes? Priceless. Slowly, I shipped desiAgent, an SDK for agents – mostly powered by AmpCode, which I used to paraphrase this post.

This Lex Fridman Podcast hit me mid-walk, and was the inspiration for doing this post.

My thoughts 💭 - the prompt used for this post

The following is a list of thoughts for an essay - I want you convert this to a an essay jovial to natural tone easily readable and targeted mostly to youngster. Write the final essay to soul-of-an-agent-v1.md

The inspiration for writing this has come from  a podcast: https://podcasts.apple.com/in/podcast/lex-fridman-podcast/id1434243584?i=1000749366733  and lesson from my personal jouney 

Ensure you mention somewhere : that the though are mine (U G Murthy) Paraphrased by AI

### Soul of an Agent

### Key Message to drive home

AI Agents are not all gloom and doom as being discussed on most social media. Jobs will vanish etc. Gear your self develop a very important skill -  How work with agent and this requires us to understand what an agent is and how should I go about build my skill to use Agent. The AI models landscape is changing fast both in terms of capability and reducing cost. But its core is till the same allowing individuals to creating interesting use cases the one to go most viral is Clawdbot or Moltbot now and the other that crashed tech stocks is Claude CoWork and there will be more.

### Outline:

Following is an outline that needs to paraphrase and arrange in a logical flow

- Spectrum of agents
        - Simple - 1 chat conversation, chain of conversations 
        - Moderate - Search, extract, Analyse 
        - All of above + more tools + Agent workflows
    - Agent does not know I am stupid
    - Always there to help, explain, brainstorm, teach, consult
    - Agent can be mutate on its own
    - Agent can play different roles
- Every Agent needs help to succeed - your success lies in there
    - Show the the agent - build relevant context
    - Consider this and that - give it some direction
    - Do this… - expose your goal
    - Provide options - make it think about alternatives
    - Ask questions - Help the agent reflect
- How does it benefit me?
    - Exposure to new solutions
    - You get a sounding board making you reflect
    - Learn on the way and that too very quickly
    - Play - journey is more imp that end goal
    - Learn new things
     -  Don’t forget you have an agent to help you - be brave , take risks in building the unknown

- Personal journey 
    - I have been building ever since I retired in 2016. Though I had programming experience when I was younger, I was off coding for more than 2 decades. I had all the time in the world to learn - so I did
    - Glad I pursued learning to code. 2025, coding agents started to appear on the horizon and I embraced it -built and  Ditched many projects. Tried  Co Pilot, bolt, cursor, windsurf, settled on kilo code for a couple months and then I discovered ampCode   
    - Got inspired by many blog posts from Lex Freidman, Dwarfish,
    - favrouite podcast remains https://www.latent.space/podcast This is where I got curious about agents
    - 2026 I just started building an agent without a plan or a direction - every step in this direction gave birth to new ideas, it almost felt like a prize for the effort of previous step - there were sleepless nights but the journey was fun. I woke up every day with new ideas buzzing, it was hard to implement but slowly and steadily I build it
    - Here is desiAgent an SDK for agents : Most of the work was Ampcode, I review and understand its core.
        - Sharing my high level learnings of working fearlessly with agents by understanding their Soul
    - I got to writing this outline for this article while on a walk listening to Lex Friedman’s pod OpenClaw - https://podcasts.apple.com/in/podcast/lex-fridman-podcast/id1434243584?i=1000749366733


- Health warning:
    - AI Agents can consume you - beware of how much time and money you spend here
    - Do not take your eye of from - if you want to enjoy the future
        - The number of hours you sleep
        - Taking action to stay fit
        - 

- Conclusion
    - If you are young or retired dev wakeup / shapeup and start building stuff - 

Health Warning: Don't Let It Own You ⚠️

Agents are addictive AF – time and cash can vanish. Keep your eyes on the prize for that epic future:

  • Sleep those 8 hours, no excuses.
  • Hit the gym, stay fit – action over screens.

Balance or burn out, fam.

Wrap-Up: Wake Up, Shape Up, Build Now!

Young guns or retired devs – this is your call to action. Stop scrolling, start shipping. Grab an agent, team up, and build cool stuff. The world's your playground. What's your first project? Go wild! 🌟

The Secret Life of Python: Manual List Iteration with While and Reverse Loops

2026-02-15 12:06:12

Why your 'while' loop skips items—and two ways to fix it without copying.

Timothy was feeling confident. He had spent the morning cleaning up his lists using Margaret’s "Snapshot" method. But as he sipped his coffee, a thought bothered him.

"Margaret," he called out, "the snapshot copy works great for my small task list. But what if I had a list with ten million items? Creating a full copy just to delete a few things feels… expensive."

Margaret nodded, impressed. "You’re thinking about memory efficiency, Timothy. That’s the mark of a growing engineer. If each item in that list was a pointer, a snapshot would cost you about 80 megabytes of extra RAM. The manual way? It costs zero."

"I wonder if we could use a while loop," Timothy continued, showing her his screen. "It’s not as elegant as a list comprehension, but it should work without a copy. I tried to write one, but I think I broke Python."

He pointed to his console, where the cursor was blinking frantically in a sea of never-ending output.

The Infinite Loop of Doom

"I tried to use a while loop to manually walk through the list," Timothy explained. "But it just keeps printing the same thing forever."

He showed her his attempt:

# Timothy's Infinite Loop
tasks = ["done", "todo", "done", "done", "todo"]
i = 0

while i < len(tasks):
    if tasks[i] == "done":
        tasks.remove("done")

    # Timothy's logic: Just keep moving!
    i += 1 

print(f"End: {tasks}")

"Look at the logic," Margaret said gently. "You are moving your index forward (i += 1) every single time. But when you remove an item, the list shifts toward you. You’re stepping forward while the sidewalk is moving backward."

The "Stay-in-Place" Strategy

"If I remove the item at index 0," Timothy realized, "the item that was at index 1 is now the new index 0. If I move to index 1, I’ve jumped over it!"

"Exactly," Margaret said. "In a for loop, the 'driver' is automatic—it always steps forward. In a while loop, you are the driver. You only shift gears when it’s safe."

She helped him rewrite the logic, replacing .remove() with .pop().

tasks = ["done", "todo", "done", "done", "todo"]
i = 0

while i < len(tasks):
    if tasks[i] == "done":
        # We use .pop(i) because we already know the exact index.
        # It's faster than searching the whole list again with .remove()
        tasks.pop(i)  

        # CRITICAL: We do NOT increment 'i' here.
        # The next item just slid into our current position!
    else:
        i += 1  # Only move forward if we didn't delete anything

"Now," Margaret explained, "if you find a 'done' task, you delete it and stay exactly where you are. You look at the same spot again to see what slid into it. You only move the pointer i when you're sure the current item is one you want to keep."

The Professional Backstep

"Is there a way to use a for loop without a copy?" Timothy asked.

"There is one trick," Margaret smiled. "The Reverse Commute. If you start at the end of the list and walk toward the beginning, the shifting doesn't matter."

She wrote out a range that looked like a secret code: range(len(tasks) - 1, -1, -1).

"That's range(start, stop, step)," she explained. "We start at the last index, stop just before -1, and step backward by 1."

# Walking backwards
tasks = ["done", "todo", "done", "done", "todo"]

for i in range(len(tasks) - 1, -1, -1):
    if tasks[i] == "done":
        tasks.pop(i)

"Think about it," Margaret said. "When you remove an item at the end, the items before it—the ones you haven't visited yet—stay exactly where they are. You're removing the rug from behind you, not from under your feet."

Margaret’s Cheat Sheet: Manual Iteration

Margaret flipped to a new page in her notebook.

  • Small list, readability matters: Use a Snapshot Copy (tasks[:]).
  • Huge list, memory is tight: Use a Reverse Loop. It uses zero extra memory.
  • Complex logic needed: Use a While Loop with manual index control.
  • The Golden Rule: If you delete, stay still. If you keep, move forward.

Timothy closed his laptop. "I used to think loops were just about repeating things. Now I realize they’re about navigating a changing world."

"That," Margaret said, "is the difference between a coder and an engineer."

In the next episode, Margaret and Timothy will face "The Lying Truth"—where Timothy learns that in Python, even a 'Zero' can be a lie, and 'Nothing' can be quite something.

Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like a Genius.

playwright-repl: Browser Automation From Your Terminal, No Code Required

2026-02-15 11:56:40

I've been using playwright-cli — Microsoft's command-line tool that gives AI agents browser automation skills via the Playwright MCP daemon. It's brilliant for agents: one command, one process, one result. But for humans, every command spawns a brand-new Node.js process — connect to the daemon, send one message, disconnect, exit. That's 50–100ms overhead per command.

I wanted something faster. Something interactive. Something designed for humans instead of AI agents — a persistent session with instant feedback.

So I built playwright-repl — a REPL that reuses playwright-cli's command vocabulary and MCP daemon architecture, but replaces the one-shot client with a persistent socket connection. Same wire protocol, same daemon, same browser commands — just a better interface for interactive use.

What does it look like?

$ playwright-repl --headed

pw> goto https://demo.playwright.dev/todomvc/
pw> fill "What needs to be done?" "Buy groceries"
pw> press Enter
pw> fill "What needs to be done?" "Write tests"
pw> press Enter
pw> check "Buy groceries"
pw> verify-text "1 item left"
✓ Text "1 item left" is visible

No imports. No async/await. No page.locator(). Just type what you want the browser to do.

Why not just use Playwright's codegen?

Playwright's codegen is great for generating test code. But sometimes you don't want code — you want to explore.

  • Debugging a login flow? Type commands and see what happens.
  • Building a demo? Record the session and replay it.
  • Running a smoke test in CI? Pipe a .pw file and check the exit code.

playwright-repl sits between "clicking around manually" and "writing a full test suite." It's the exploratory middle ground.

Text locators: just say what you see

The thing I'm most proud of is text locators. Instead of inspecting elements for CSS selectors or waiting for a snapshot to get element refs, you just use the text you see on screen:

pw> click "Get Started"
pw> fill "Email" "[email protected]"
pw> fill "Password" "secret123"
pw> click "Sign In"
pw> check "Remember me"
pw> select "Country" "Japan"

Under the hood, it tries multiple strategies — getByText, getByRole('button'), getByRole('link'), getByLabel, getByPlaceholder — with a fallback chain. Case differences don't matter because role matching is case-insensitive.

You can also use element refs from snapshot output if you prefer precision:

pw> snapshot
- heading "todos" [ref=e1]
- textbox "What needs to be done?" [ref=e8]

pw> click e8
pw> fill e8 "Buy groceries"

Record and replay

This is where it gets really useful. Record your browser session as a .pw file:

pw> .record smoke-test
⏺ Recording to smoke-test.pw

pw> goto https://demo.playwright.dev/todomvc/
pw> fill "What needs to be done?" "Buy groceries"
pw> press Enter
pw> verify-text "1 item left"
pw> .save
✓ Saved 4 commands to smoke-test.pw

The .pw file is just plain text:

# CI smoke test
goto https://demo.playwright.dev/todomvc/
fill "What needs to be done?" "Buy groceries"
press Enter
verify-text "1 item left"

Replay it any time:

# Headless (CI mode)
playwright-repl --replay smoke-test.pw --silent

# With a visible browser
playwright-repl --replay smoke-test.pw --headed

# Step through interactively
playwright-repl --replay smoke-test.pw --step --headed

These .pw files are human-readable, diffable, and version-controllable. Commit them alongside your code. Run them in CI. Share them with teammates who don't write JavaScript.

Assertions built in

No test framework needed. Verify state inline:

pw> verify-text "1 item left"
✓ Text "1 item left" is visible

pw> verify-element heading "todos"
✓ Element heading "todos" is visible

pw> verify-value "Email" "[email protected]"
✓ Value matches

If an assertion fails, you get a clear error — and in replay mode, the process exits with code 1. That's all CI needs.

50+ commands, short aliases

Every command has a short alias for quick typing:

You type It does
g https://example.com Navigate to URL
s Accessibility tree snapshot
c e5 Click element ref e5
f "Email" "[email protected]" Fill a form field
p Enter Press a key
ss Take a screenshot
vt "hello" Verify text is visible
back Go back in history

The full list includes interaction (click, fill, type, press, hover, drag), inspection (snapshot, screenshot, eval, console, network), storage (cookies, localStorage, sessionStorage), tabs, dialogs, network routing, and more.

How it works under the hood

Architecture

playwright-repl stands on the shoulders of playwright-cli and the Playwright MCP architecture. It reuses:

  • The MCP daemon from [email protected]+ — browser launch, CDP communication, all 50+ tool handlers
  • The command vocabulary from playwright-cli — click, fill, snapshot, screenshot, etc.
  • The wire protocol — newline-delimited JSON over Unix socket / Windows named pipe

The REPL is just a thin, persistent client that:

  1. Parses your input (alias resolution + argument parsing)
  2. Sends a JSON message over the socket (identical format to playwright-cli)
  3. Displays the result

Because the socket stays open, there's zero startup overhead per command. The daemon doesn't care whether the message came from playwright-cli, the REPL, or an AI agent — the wire messages are identical. playwright-repl adds the human-friendly layer on top: text locators, recording, replay, assertions, and aliases.

Use it in CI

# .github/workflows/smoke.yml
jobs:
  smoke-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright install chromium
      - run: npx playwright-repl --replay tests/smoke.pw --silent

The --silent flag suppresses the banner. The process exits 0 on success, 1 on failure. That's it.

Get started

npm install -g playwright-repl
npx playwright install
playwright-repl --headed

Then type goto https://demo.playwright.dev/todomvc/ and start exploring.

Chrome DevTools extension

We're also building playwright-repl-extension — a Chrome DevTools panel that brings the same REPL experience into the browser. Open DevTools, switch to the "Playwright REPL" tab, and type commands directly. It also includes an action recorder that captures your clicks and form fills as .pw commands.

The extension is standalone (no Node.js daemon needed) — it uses Chrome's chrome.debugger API to drive the inspected page via CDP.

Links

playwright-repl is MIT licensed and works on Linux, macOS, and Windows. Contributions welcome!

Get yo A$$ Outside!

2026-02-15 11:51:14

This is a submission for the GitHub Copilot CLI Challenge

What I Built

Mobile-first web app that lovingly drags you off the couch, finds nearby parks, and points you toward sunlight.

Demo

Live Demo: https://app.screencastify.com/watch/LJx6ZcoYuuHvdLD0Ar11?checkOrg=0c9695fb-f3e1-4712-b556-91e2a96b7f39

Try it yourself:
https://get-outside.pages.dev/

My Experience with GitHub Copilot CLI

I utilized the .github directory and copilot-instructions.md along with the /agents directory with specific agents such as planning-agent.md, which hands off to either the frontend-agent.md or the backend-agent.md. I also utilized a CLAUDE.md file to have a "master/main scope" for the entire project. This type of flow helped me to not only execute "fast," but also keep a structured flow while developing this MVP.

I built an open-source workflow kit that turns AI agents into structured data analysis partners

2026-02-15 11:48:17

🔗 GitHub repo: https://github.com/with-geun/alive-analysis

Over the past year, I’ve been using AI coding agents (Claude Code, Cursor, etc.) heavily for data analysis work.

They’re incredibly helpful — but I kept running into the same problem.

Every analysis was a throwaway conversation.

No structure.

No tracking.

No way to revisit why I reached a conclusion.

A month later, I’d remember what we decided, but not how we got there.

So I built alive-analysis — an open-source workflow kit that adds structure, versioning, and quality checks to AI-assisted analysis.

The problem

When you ask an AI to “analyze this data,” you usually get:

  • a one-shot answer
  • reasoning that’s hard to trace later
  • no shared artifact for your team

In practice, analysis becomes:

  • inconsistent
  • hard to review
  • impossible to learn from over time

I wanted something closer to how real analysis work actually happens — iterative, documented, and revisitable.

The idea: treat analysis like a repeatable workflow

alive-analysis structures every analysis using a simple loop:

ASK → LOOK → INVESTIGATE → VOICE → EVOLVE

ASK

Define the real question, scope, and success criteria.

LOOK

Check the data first — quality, segmentation, outliers.

INVESTIGATE

Form hypotheses, test them, and eliminate possibilities.

VOICE

Document conclusions with confidence levels and audience context.

EVOLVE

Capture follow-ups and track impact over time.

Instead of generating answers immediately,

the AI guides you through these stages by asking questions.

That small change alone dramatically improved the rigor of my analyses.

What it actually does

alive-analysis is not a BI tool or dashboard replacement.

You still use:

  • SQL
  • notebooks
  • dashboards
  • your existing data stack

It simply adds a workflow and documentation layer on top.

Key features

  • Structured analysis stages with checklists
  • Versioned markdown files (Git-friendly)
  • Quick mode (single file) and Full mode (multi-stage)
  • A/B experiment workflows
  • Metric monitoring with alert logic
  • Search across past analyses
  • Impact tracking (recommendation → outcome)

Why I built it

After using it for a while, I noticed a few unexpected benefits:

  • I can reopen an analysis months later and understand the reasoning instantly
  • Checklists catch things I used to skip (confounders, counter-metrics)
  • PMs and engineers started running their own quick analyses
  • Decisions feel more defensible because assumptions are explicit

It basically turned AI from an “answer generator” into a thinking partner.

How it works in practice

Typical workflow:

  1. Initialize in your repo
  2. Start a new analysis
  3. Move through the ALIVE stages
  4. Archive when complete
  5. Search or review later

Everything lives as markdown in your project, so it becomes a long-term knowledge base instead of lost chat history.

Who this is for

  • Data analysts who want more rigor
  • Engineers and PMs doing lightweight analysis
  • Teams using AI agents for decision support
  • Anyone who wants a traceable reasoning process

What I’m looking for feedback on

I’d love to hear from people doing real analysis work:

  • Does this workflow match how you actually think?
  • What steps feel missing or unnecessary?
  • Would you use something like this in a team setting?

Brutally honest feedback is very welcome 🙏

Project

👉 GitHub: https://github.com/with-geun/alive-analysis

Quick start, examples, and templates are all available in the repo.

If you’ve been using AI for analysis, I’d especially love to know:

👉 What’s the biggest friction you still feel in your workflow?