Day 004 of the 100 Days of Python challenge creates passwords from user-selected quantities of letters, symbols, and numbers. It’s a great next step after Day 003’s random number generation, this time combining random.choice() with for loops and list indexing to build something character by character.

This guide walks through the complete Day 004 source code, explains how the password is assembled, and — importantly — traces through exactly what the loops actually do, since the real behavior here is a little different from what the character counts might suggest.

Project Overview

The program keeps three character lists: upper- and lowercase letters, symbols, and digits. It asks how many characters of each type the user wants, builds an initial list of random letters, then overwrites parts of that list with random symbols and numbers before shuffling the result.

Source Code

The Day 004 project is contained in day004/main.py in the 100daysofpython repository:

import random

def main():
    letters = ['a','b','c','d','e','f','g','h','i','j','k','l','m',
               'n','o','p','q','r','s','t','u','v','w','x','y','z',
               'A','B','C','D','E','F','G','H','I','J','K','L','M',
               'N','O','P','Q','R','S','T','U','V','W','X','Y','Z']

    symbols = ['!', '@', '#', '$', '%', '^', '&', '*', '+']
    numbers = ['0','1','2','3','4','5','6','7','8','9']

    print("Welcome to the Python Password Generator!")

    pwd_letters = int(input("How many characters would you like in your password? "))
    pwd_symbols = int(input("How many symbols would you like? "))
    pwd_numbers = int(input("How many numbers would you like? "))

    password_chars = []

    # pick random letters
    for char in range(pwd_letters):
        password_chars.append(random.choice(letters))

    # pick random symbols
    for symbol in range(pwd_symbols):
        password_chars[symbol] = random.choice(symbols)

    # pick random numbers
    for number in range(pwd_numbers):
        password_chars[pwd_symbols + number] = random.choice(numbers)

    # shuffle so it's not predictable like letters→symbols→numbers
    random.shuffle(password_chars)
    password = "".join(password_chars)

    print(f"Your password is: {password}")


if __name__ == "__main__":
    main()

How the Program Works

Building the Initial List

for char in range(pwd_letters):
    password_chars.append(random.choice(letters))

This loop runs pwd_letters times, each time using random.choice(letters) to pick one random letter and .append() it to password_chars. After this loop, the list’s length equals pwd_letters.

Overwriting Positions, Not Appending

for symbol in range(pwd_symbols):
    password_chars[symbol] = random.choice(symbols)

for number in range(pwd_numbers):
    password_chars[pwd_symbols + number] = random.choice(numbers)

This is the part worth reading carefully. These loops use password_chars[symbol] = ..., not .append() — meaning they overwrite existing positions in the list rather than adding new characters to it. The symbols loop replaces indices 0 through pwd_symbols - 1, and the numbers loop replaces the next pwd_numbers positions after that.

Shuffling and Joining

random.shuffle(password_chars)
password = "".join(password_chars)

random.shuffle() reorders the list in place, so the final password doesn’t follow a predictable letters-then-symbols-then-numbers pattern. "".join(password_chars) then combines every character in the list into a single string.

Tracing Through the Actual Behavior

Because the symbols and numbers loops overwrite rather than extend the list, the final password’s length always equals pwd_letters — not the sum of all three inputs, even though the program asks for each one separately.

Walk through an example: if a user enters 8 for letters, 2 for symbols, and 2 for numbers, here’s what happens:

  1. The list starts with 8 random letters at indices 0–7.
  2. The symbols loop overwrites indices 0 and 1 with random symbols.
  3. The numbers loop overwrites indices 2 and 3 with random numbers.
  4. Indices 4–7 are still the original random letters.
  5. The final password is 8 characters long — 2 symbols, 2 numbers, and 4 letters — not 12 characters as the three separate prompts might suggest.

It gets more serious if pwd_symbols + pwd_numbers is greater than pwd_letters. In that case, the numbers loop tries to assign to an index that doesn’t exist yet, and Python raises an IndexError: list assignment index is out of range, crashing the program.

What This Project Teaches

Day 004 practices:

  • Working with the random module, specifically random.choice() and random.shuffle()
  • Building and mutating a list with a for loop
  • The difference between .append() (adding a new item) and index assignment (overwriting an existing item)
  • Joining a list of characters into a single string with "".join()

It also introduces a practical security idea: variety and unpredictable ordering make generated passwords harder to guess. For production password generation, Python’s secrets module would be preferable to random, since it’s specifically designed for security-sensitive randomness — this challenge project is focused on learning Python fundamentals, not building a production-ready security tool.

Running the Project

git clone https://github.com/victorhugo81/100daysofpython.git
cd 100daysofpython/day004
python main.py

Possible Next Steps

Once you understand the existing behavior, try fixing and extending it:

  • Change the symbols and numbers loops to use .append() instead of index assignment, so the final password length actually equals the sum of all three inputs
  • Add validation so the program gives a clear error message instead of crashing when symbols + numbers exceeds letters
  • Swap random for the secrets module to make the generator safe for real-world use
  • Let the user set a minimum total password length regardless of how the character types are split

Conclusion

Day 004 of the 100 Days of Python challenge combines loops, list mutation, and the random module to build a password generator — and its overwrite-based approach to combining letters, symbols, and numbers is a useful reminder that a program can run without errors while still not doing exactly what its prompts imply. Tracing through the code line by line, the way this guide just did, is one of the most valuable debugging habits you can build as a beginner.

View the Day 004 source code on GitHub, then continue through the repository’s project index as you build more Python programs, one day and one small project at a time.