Day 009 of the 100 Days of Python challenge is a command-line Secret Auction. Multiple bidders enter names and bids, then the program determines the highest bidder after the auction closes.

This project is the series’ first real introduction to dictionaries — Python’s built-in way of pairing two related pieces of data, like a bidder’s name and their bid amount. It also layers in more thorough input validation than earlier days, checking both that a name looks like a name and that a bid is a valid positive number.

This guide walks through the complete Day 009 source code, explains how the dictionary and winner-finding logic work, and traces through a real bug in how tied bids get displayed.

Project Overview

Bids are stored in a dictionary with the bidder’s name as the key and the bid amount as the value:

bids[name] = price

A loop collects bidders until the user answers n when asked whether another bidder should continue. The find_highest_bidder() function then loops over the dictionary, tracks the largest amount, and prints a sorted view of all submitted bids before announcing the winner.

Source Code

The Day 009 project is contained in day009/main.py in the 100daysofpython repository:

from art import LOGO

print(LOGO)

print("*******************************************")
print("Welcome to The Secret Auction!")
print("*******************************************")


def find_highest_bidder(bidding_dictionary):
    winner = ""
    highest_bid = 0

    for bidder in bidding_dictionary:
        bid_amount = bidding_dictionary[bidder]
        if bid_amount > highest_bid:
            highest_bid = bid_amount
            winner = bidder

    print("\n************* Auction Results *************")
    print(f"Total bids placed: {len(bidding_dictionary)}\n")
    print("\nName   |  Bid Amount")
    print("-------------------------------------------")

    sorted_amounts = sorted(bidding_dictionary.values(), reverse=True)

    for amount in sorted_amounts:
        for bidder in bidding_dictionary:
            if bidding_dictionary[bidder] == amount:
                print(f"{bidder} - ${amount}")

    print("\n-------------------------------------------")
    print(f"The winner is {winner} with a bid of ${highest_bid}")
    print("---------------------------------------------")


def secret_auction():
    bids = {}
    continue_bidding = True

    while continue_bidding:
        while True:
            name = input("What is your name?: ").strip()
            if name == "":
                print("Name cannot be empty.")
            elif not name.replace(" ", "").isalpha():
                print("Name must contain only letters.")
            else:
                break

        while True:
            try:
                price = int(input("What is your bid?: $"))
                if price <= 0:
                    print("Bid must be greater than 0.")
                else:
                    break
            except ValueError:
                print("Please enter a valid whole number.")

        bids[name] = price

        should_continue = input(
            "Are there any other bidders? Type 'y' if there other bidders. Otherwise type 'n'.\n"
        ).lower()

        if should_continue == "n":
            continue_bidding = False
            find_highest_bidder(bids)
        elif should_continue == "y":
            print("\n")


secret_auction()

How the Program Works

Storing Bids in a Dictionary

bids[name] = price

A dictionary pairs each bidder’s name (the key) with their bid amount (the value). This is a natural fit here, since every bidder’s name and bid need to travel together as one unit — a plain list of names and a separate list of bids would make it much easier to accidentally mismatch them.

Two-Layer Input Validation

if name == "":
    print("Name cannot be empty.")
elif not name.replace(" ", "").isalpha():
    print("Name must contain only letters.")
else:
    break

The name check strips spaces before calling .isalpha(), so a name like "Mary Jane" passes, while an empty string or one containing digits or symbols is rejected and the loop asks again.

try:
    price = int(input("What is your bid?: $"))
    if price <= 0:
        print("Bid must be greater than 0.")
    else:
        break
except ValueError:
    print("Please enter a valid whole number.")

The bid check combines both techniques seen in earlier days: a try/except catches non-numeric input, and an if price <= 0: check rejects zero or negative bids once the input is confirmed to be a valid number.

Finding the Highest Bidder

for bidder in bidding_dictionary:
    bid_amount = bidding_dictionary[bidder]
    if bid_amount > highest_bid:
        highest_bid = bid_amount
        winner = bidder

This loop is a classic “running maximum” pattern: start with a baseline (highest_bid = 0), and update it — along with the associated winner — every time a larger value is found while looping through the dictionary.

A Real Bug: Duplicate Rows on Tied Bids

Look closely at how the results are printed:

sorted_amounts = sorted(bidding_dictionary.values(), reverse=True)

for amount in sorted_amounts:
    for bidder in bidding_dictionary:
        if bidding_dictionary[bidder] == amount:
            print(f"{bidder} - ${amount}")

sorted_amounts is a sorted list of every bid value, including duplicates — if two bidders both bid $100, 100 appears twice in that list. For each occurrence of 100, the inner loop searches the entire dictionary and prints every bidder whose bid equals 100.

Trace through an example with bids = {"Alice": 100, "Bob": 100, "Carol": 50}:

  1. sorted_amounts is [100, 100, 50].
  2. On the first 100, the inner loop finds both Alice and Bob, printing both.
  3. On the second 100, the inner loop finds Alice and Bob again, printing both a second time.
  4. On 50, the inner loop finds Carol once.

The result is five printed lines — Alice and Bob each appear twice — instead of the three actual bidders. Any tie in bid amounts causes this duplication, and the more bidders share the same value, the worse it gets.

What This Project Teaches

Day 009 provides practice with:

  • Dictionaries for pairing two related pieces of data
  • Multi-layer input validation combining string checks and try/except
  • The “running maximum” pattern for finding the largest value in a collection
  • Why a nested loop over duplicate values needs extra care to avoid duplicate output

The project also keeps the auction in memory, so results disappear when the program exits — adding file or database storage would be a natural next step for a more complete version.

Running the Project

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

Possible Next Steps

Once you understand the existing code, try extending it:

  • Fix the tied-bid duplication by sorting (bidder, amount) pairs directly, e.g. sorted(bidding_dictionary.items(), key=lambda item: item[1], reverse=True), instead of looking up matching amounts separately
  • Allow names with hyphens or apostrophes (like “Jean-Luc” or “O’Brien”), which the current .isalpha() check rejects
  • Prevent the same name from overwriting an earlier bid — since bids is keyed by name, entering the same name twice silently replaces the previous bid instead of tracking a second one
  • Save the auction results to a file so they persist after the program exits

Conclusion

Day 009 of the 100 Days of Python challenge combines dictionaries, layered input validation, and a running-maximum search into a working auction program — while also serving as a reminder that sorting values separately from their associated keys, as the results display does here, can silently produce duplicate output when ties exist. Spotting and fixing that kind of bug is exactly the skill this challenge is designed to build.

View the Day 009 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.