Number Guessing Game

Build a complete, replayable guessing game and learn the “keep asking until the input is valid” pattern that powers every interactive program.

Beginner 30–45 minutes

About this project

The computer picks a secret number; you guess, and it tells you to go higher or lower until you crack it. Simple to describe — but building it well brings together loops, conditionals, input, error handling, and functions into one working whole.

Why it’s worth building: the heart of this game is a loop that keeps asking until it gets a valid answer. That exact pattern shows up everywhere real software lives — login prompts, menus, “are you sure?” confirmations, retrying a flaky download. Get it right here and you’ll reuse it for the rest of your Python life.

Build it step by step

We’ll grow the game one idea at a time. Each step adds a single concept and leaves you with something that runs, so you’re never staring at a wall of code wondering where to start.

Step 1 — Pick a secret number

The random module ships with Python. randint(1, 100) returns a whole number from 1 to 100, including both ends. We print it for now just so we can see it while testing — we’ll remove that line later.

Example
import random

secret = random.randint(1, 100)   # 1 to 100, both ends included
print("(testing) the secret is:", secret)
Your number will differ every run — that randomness is the whole point.

Step 2 — Read a guess you can trust

input() always hands back a string, even when the user types digits, so we convert with int(). But people fat-finger things — they’ll type fifty or hit enter by accident. Wrapping the conversion in try/except lets us catch that and ask again instead of crashing. Putting it in a function means we can reuse this “pester until valid” logic anywhere.

Example
def read_guess(low, high):
    """Ask until the player types a whole number within range."""
    while True:
        raw = input(f"Guess ({low}-{high}): ")
        try:
            guess = int(raw)
        except ValueError:
            print("Please type a whole number.")
            continue
        if guess < low or guess > high:
            print(f"Stay between {low} and {high}.")
            continue
        return guess
A reusable input-validation loop — the real workhorse of the project.

Step 3 — Compare and give a hint

With a trustworthy guess in hand, one if/elif/else covers every case: too low, too high, or spot on. Returning a flag from this round lets the caller know whether to keep going.

Example
secret = 42
guess = 30
if guess < secret:
    print("Higher!")
elif guess > secret:
    print("Lower!")
else:
    print("You got it!")
Output
Higher!
Three branches, three outcomes — the game’s feedback in a nutshell.

Step 4 — Loop until they win, counting tries

Now we stitch it together: keep reading guesses until one matches, and tick up a counter each time so we can report how many attempts it took. A while True loop with a return inside is the cleanest way to say “go until you win.”

Example
def play_round(low=1, high=100):
    secret = random.randint(low, high)
    attempts = 0
    while True:
        guess = read_guess(low, high)
        attempts += 1
        if guess < secret:
            print("Higher!")
        elif guess > secret:
            print("Lower!")
        else:
            print(f"You got it in {attempts} tries!")
            return attempts
One full round, start to win, with a tally of attempts.

The finished game

Here’s everything assembled, plus a replay loop and a “best score” tracker so it feels like a real game. Notice how each function does one job — that separation is what keeps a growing program understandable.

Example · guessing_game.py
import random


def read_guess(low, high):
    """Ask until the player types a whole number within range."""
    while True:
        raw = input(f"Guess ({low}-{high}): ")
        try:
            guess = int(raw)
        except ValueError:
            print("Please type a whole number.")
            continue
        if guess < low or guess > high:
            print(f"Stay between {low} and {high}.")
            continue
        return guess


def play_round(low=1, high=100):
    secret = random.randint(low, high)
    attempts = 0
    while True:
        guess = read_guess(low, high)
        attempts += 1
        if guess < secret:
            print("Higher!")
        elif guess > secret:
            print("Lower!")
        else:
            print(f"You got it in {attempts} tries!")
            return attempts


def main():
    print("I'm thinking of a number between 1 and 100.")
    best = None
    while True:
        attempts = play_round()
        if best is None or attempts < best:
            best = attempts
        print(f"Best so far: {best} tries.")
        again = input("Play again? (y/n): ").strip().lower()
        if again != "y":
            print("Thanks for playing!")
            break


if __name__ == "__main__":
    main()
Output
I'm thinking of a number between 1 and 100.
Guess (1-100): 50
Higher!
Guess (1-100): 75
Lower!
Guess (1-100): sixty
Please type a whole number.
Guess (1-100): 62
You got it in 3 tries!
Best so far: 3 tries.
Play again? (y/n): n
Thanks for playing!
A sample play-through — yours will vary with the random number and your guesses.

Keep going — make it your own

You’ve got a real game. Now make it yours — each idea below adds one concept and a reason to use it. Start with whichever sounds most fun.

Give the player a limited number of tries. Swap the endless while for a counted for loop over range(1, MAX_TRIES + 1). If it runs out before they win, reveal the answer — instant tension.

Example
MAX_TRIES = 7


def play_round(low=1, high=100):
    secret = random.randint(low, high)
    for attempts in range(1, MAX_TRIES + 1):
        guess = read_guess(low, high)
        if guess == secret:
            print(f"You got it in {attempts} tries!")
            return attempts
        print("Higher!" if guess < secret else "Lower!")
    print(f"Out of tries! The number was {secret}.")
    return MAX_TRIES
A counted for-loop caps the guesses; running out means a loss.

Add difficulty levels. Let the player pick a range (1–10, 1–100, 1–1000). Because play_round already takes low and high, this is mostly a menu in front of what you’ve built — a great example of why parameters beat hard-coded numbers.

Example
ranges = {"easy": (1, 10), "normal": (1, 100), "hard": (1, 1000)}
choice = input("easy / normal / hard? ").strip().lower()
low, high = ranges.get(choice, (1, 100))   # default to normal
play_round(low, high)
A dictionary maps a word to a (low, high) pair — no if/elif ladder needed.

Choose the difficulty from the command line. Reading sys.argv lets you run python guessing_game.py 1000 and jump straight in — your first taste of a script that takes arguments like a real tool.

Example
import sys

# python guessing_game.py 1000  ->  guess between 1 and 1000
high = int(sys.argv[1]) if len(sys.argv) > 1 else 100
play_round(1, high)
sys.argv[0] is the script name; anything after it is your argument.

Remember the all-time best score between runs. Save it to a small file so it survives closing the program — your first bit of persistence. Read it on startup, write it whenever it’s beaten.

Example
from pathlib import Path

scores = Path("best_score.txt")
record = int(scores.read_text()) if scores.exists() else None

# after a round, if you beat the record:
if record is None or attempts < record:
    scores.write_text(str(attempts))
    print("New all-time best!")
pathlib makes reading and writing a tiny file a one-liner each.

Mini exercise (easy)

Write the game’s core feedback: a function hint(guess, secret) that returns 'Higher!' when the guess is too low, 'Lower!' when it’s too high, and 'You got it!' when they match.

Do it here. Finish the code below and press Run to try your answer right in the browser — no setup needed.

def hint(guess, secret):
    # return "Higher!", "Lower!", or "You got it!"
    return "?"

print(hint(30, 42))
print(hint(50, 42))
print(hint(42, 42))

Where to go next

This game leans hardest on while loops and functions — revisit those if any step felt shaky. When you’re ready for file handling and structure, the To-Do List CLI is the natural next build.