Skip to content

Python Project: Number Guessing Game

Updated 11 min read Aimerscrew

The Python number guessing game is the perfect first project: it's small, it's fun to play, and it uses almost everything you learned in the first half of this tutorial. The computer picks a secret number, you try to guess it, and it tells you "too high" or "too low" until you get it right. In this project, you'll build it one small step at a time, then put everything together into a polished game.

🌍 Real-life example

You've probably played this at a party or on a long road trip: "I'm thinking of a number between 1 and 100." Each answer of "higher" or "lower" cuts the possibilities down. TV game shows use the same idea when contestants guess the price of a product. Your program will play the host.

What you'll build

By the end of this lesson, you'll have a game that:

  • picks a random secret number between 1 and 100,
  • gives the player 7 attempts and counts every guess,
  • rejects input that isn't a whole number or is out of range, without crashing,
  • says "Too low!" or "Too high!" after each guess,
  • asks "Play again?" and keeps score across rounds.

Here's a short game. The player typed the values after each Your guess (1-100): prompt, and n at the end. The secret number is random, so your numbers will differ.

=== Number Guessing Game ===

I'm thinking of a number between 1 and 100.
You have 7 attempts. Good luck!
Your guess (1-100): 50
Too high!
Attempts left: 6
Your guess (1-100): 25
Too low!
Attempts left: 5
Your guess (1-100): 37
Too low!
Attempts left: 4
Your guess (1-100): 43
Too high!
Attempts left: 3
Your guess (1-100): 42
Correct! You found 42 in 5 attempts.

Play again? (y/n): n
Thanks for playing! Final score: 1 won, 0 lost.

Concepts used in this project

Every piece of this game is something you've already met. If a step feels unfamiliar, jump back to the lesson linked here:

ConceptWhere it's usedLesson
random.randint()Picking the secret numbermath and random
input() and int()Reading the player's guessuser input
if / elif / elseToo low, too high or correctif-else
while loopsRepeating guesses and roundswhile loop
break and continueLeaving or restarting a loopbreak, continue, pass
Functions and returnOrganizing the codefunctions, return
f-stringsFriendly messagesf-strings

How the game works (program flow)

Before writing code, it helps to picture the flow. Each round repeats a simple cycle: ask, compare, give a hint. The round ends when the player guesses right or runs out of attempts.

Flowchart of the number guessing game: pick a secret number, ask for a guess, compare; a wrong guess loops back while attempts are left, a correct guess wins, then the game asks to play again or shows the final score
The guessing loop sits inside a bigger "play again" loop.

Step 1: Pick a secret number and check one guess

Start with the smallest version that works: pick a number, read one guess, and compare. random.randint(1, 100) returns a random whole number from 1 to 100, including both ends.

import random

secret = random.randint(1, 100)
guess = int(input("Guess a number between 1 and 100: "))

if guess < secret:
    print("Too low!")
elif guess > secret:
    print("Too high!")
else:
    print("Correct!")

print(f"The number was {secret}.")

In this run, the player typed 30 (your secret number will differ):

Guess a number between 1 and 100: 30
Too low!
The number was 42.

It works, but one guess isn't much of a game. Let's add a loop.

Step 2: Keep guessing with a while loop

A while True loop repeats forever, so the player can keep guessing. When the guess is correct, break jumps out of the loop. A counter variable, attempts, goes up by one on every guess.

import random

secret = random.randint(1, 100)
attempts = 0

while True:
    guess = int(input("Your guess: "))
    attempts += 1

    if guess < secret:
        print("Too low!")
    elif guess > secret:
        print("Too high!")
    else:
        print(f"Correct! You needed {attempts} attempts.")
        break

Here the player typed 50, 25, 40, 45 and 42:

Your guess: 50
Too high!
Your guess: 25
Too low!
Your guess: 40
Too low!
Your guess: 45
Too high!
Your guess: 42
Correct! You needed 5 attempts.

💡 Tip: the smartest strategy

Always guess the middle of the range that's left: 50, then 25 or 75, and so on. Each guess cuts the possibilities in half, so you can find any number from 1 to 100 in at most 7 guesses. That's why our finished game gives exactly 7 attempts. Computer scientists call this strategy binary search.

Step 3: How do you validate the player's input?

Try typing ten into the Step 2 program. It crashes:

ValueError: invalid literal for int() with base 10: 'ten'

Players make typos, so a good game checks the input first. Input validation means checking that what the user typed makes sense before you use it. Let's move this job into its own function, get_guess(), that keeps asking until it gets a whole number inside the range:

def get_guess(low, high):
    """Keep asking until the player types a whole number in range."""
    while True:
        text = input(f"Your guess ({low}-{high}): ").strip()
        if not text.isdigit():
            print("Please type a whole number, like 42.")
            continue
        guess = int(text)
        if guess < low or guess > high:
            print(f"Out of range! Pick a number from {low} to {high}.")
            continue
        return guess


number = get_guess(1, 100)
print("You picked", number)

The player typed ten, -5, 250 and finally 73:

Your guess (1-100): ten
Please type a whole number, like 42.
Your guess (1-100): -5
Please type a whole number, like 42.
Your guess (1-100): 250
Out of range! Pick a number from 1 to 100.
Your guess (1-100): 73
You picked 73

How it works:

  • .strip() removes accidental spaces around the input.
  • .isdigit() returns True only if every character is a digit, so "ten", "4.5" and "-5" all fail. (Negative numbers aren't allowed in our game anyway.)
  • continue skips the rest of the loop body and asks again.
  • return guess hands back a valid number and ends the function, which also ends its loop.

📝 Note

You could also use try/except ValueError around int(text), which you learned in try-except. Both approaches are fine. isdigit() is a simple choice here because we only accept positive whole numbers.

Step 4: Limit the number of attempts

A game with unlimited guesses has no tension. Let's wrap one round in a function called play_round(). Instead of while True, the loop now runs only while attempts < max_attempts. If the loop finishes without a correct guess, the player loses.

def play_round(low=1, high=100, max_attempts=7):
    """Play one round. Return True if the player wins."""
    secret = random.randint(low, high)
    attempts = 0

    print(f"\nI'm thinking of a number between {low} and {high}.")
    print(f"You have {max_attempts} attempts. Good luck!")

    while attempts < max_attempts:
        guess = get_guess(low, high)
        attempts += 1

        if guess < secret:
            print("Too low!")
        elif guess > secret:
            print("Too high!")
        else:
            word = "attempt" if attempts == 1 else "attempts"
            print(f"Correct! You found {secret} in {attempts} {word}.")
            return True

        if attempts < max_attempts:
            print(f"Attempts left: {max_attempts - attempts}")

    print(f"Out of attempts! The number was {secret}.")
    return False

A few details worth noticing:

  • Default parameters (low=1, high=100, max_attempts=7) let you call play_round() with no arguments, or make a harder game with play_round(1, 1000, 10). See function arguments.
  • return True inside the loop ends the round immediately when the player wins.
  • The line with "attempt" if attempts == 1 else "attempts" is a conditional expression. It picks the right word, so a lucky first guess says "1 attempt", not "1 attempts".
  • Returning True or False lets the caller keep score without knowing how the round works inside.

Step 5: Add "play again" and keep score

The last piece is an outer loop. The main() function plays rounds until the player answers anything other than y, counting wins along the way:

def main():
    print("=== Number Guessing Game ===")
    wins = 0
    rounds = 0

    while True:
        rounds += 1
        if play_round():
            wins += 1

        again = input("\nPlay again? (y/n): ").strip().lower()
        if again != "y":
            break

    print(f"Thanks for playing! Final score: {wins} won, {rounds - wins} lost.")

.lower() means Y and y both work. Because play_round() returns a boolean, if play_round(): reads almost like English: "if the player wins the round".

The complete Python number guessing game

Here's the full program with all five steps combined. Save it as guessing_game.py and run it with python guessing_game.py.

import random


def get_guess(low, high):
    """Keep asking until the player types a whole number in range."""
    while True:
        text = input(f"Your guess ({low}-{high}): ").strip()
        if not text.isdigit():
            print("Please type a whole number, like 42.")
            continue
        guess = int(text)
        if guess < low or guess > high:
            print(f"Out of range! Pick a number from {low} to {high}.")
            continue
        return guess


def play_round(low=1, high=100, max_attempts=7):
    """Play one round. Return True if the player wins."""
    secret = random.randint(low, high)
    attempts = 0

    print(f"\nI'm thinking of a number between {low} and {high}.")
    print(f"You have {max_attempts} attempts. Good luck!")

    while attempts < max_attempts:
        guess = get_guess(low, high)
        attempts += 1

        if guess < secret:
            print("Too low!")
        elif guess > secret:
            print("Too high!")
        else:
            word = "attempt" if attempts == 1 else "attempts"
            print(f"Correct! You found {secret} in {attempts} {word}.")
            return True

        if attempts < max_attempts:
            print(f"Attempts left: {max_attempts - attempts}")

    print(f"Out of attempts! The number was {secret}.")
    return False


def main():
    print("=== Number Guessing Game ===")
    wins = 0
    rounds = 0

    while True:
        rounds += 1
        if play_round():
            wins += 1

        again = input("\nPlay again? (y/n): ").strip().lower()
        if again != "y":
            break

    print(f"Thanks for playing! Final score: {wins} won, {rounds - wins} lost.")


main()

Sample run

This run plays two rounds: a win (with a typo and an out-of-range guess along the way) and a loss. Everything after a prompt's colon was typed by the player. Your secret numbers will be different.

=== Number Guessing Game ===

I'm thinking of a number between 1 and 100.
You have 7 attempts. Good luck!
Your guess (1-100): 50
Too high!
Attempts left: 6
Your guess (1-100): abc
Please type a whole number, like 42.
Your guess (1-100): 25
Too low!
Attempts left: 5
Your guess (1-100): 150
Out of range! Pick a number from 1 to 100.
Your guess (1-100): 37
Too low!
Attempts left: 4
Your guess (1-100): 43
Too high!
Attempts left: 3
Your guess (1-100): 42
Correct! You found 42 in 5 attempts.

Play again? (y/n): y

I'm thinking of a number between 1 and 100.
You have 7 attempts. Good luck!
Your guess (1-100): 50
Too high!
Attempts left: 6
Your guess (1-100): 25
Too high!
Attempts left: 5
Your guess (1-100): 12
Too low!
Attempts left: 4
Your guess (1-100): 18
Too low!
Attempts left: 3
Your guess (1-100): 22
Too high!
Attempts left: 2
Your guess (1-100): 21
Too high!
Attempts left: 1
Your guess (1-100): 19
Too low!
Out of attempts! The number was 20.

Play again? (y/n): n
Thanks for playing! Final score: 1 won, 1 lost.

💡 Testing tip: make "random" repeatable

Random numbers make testing hard, because every run is different. While you're testing, add random.seed(7) right after import random. A seed is a starting value for the random number generator: the same seed always produces the same sequence. With seed 7 in Python 3.12, the first two secret numbers are 42 and 20, exactly as in the sample run above. Remove the line when you're done, or every game will have the same answers!

Common mistakes in this project

1. Comparing a string with a number

input() always returns a string. If you forget int(), the comparison fails:

secret = 42
guess = "50"
print(guess < secret)
TypeError: '<' not supported between instances of 'str' and 'int'

2. Picking a new secret number inside the loop

If secret = random.randint(1, 100) sits inside the guessing loop, the answer changes after every guess and the hints become useless. Pick the number once, before the loop starts.

3. Forgetting to update the counter

If attempts += 1 is missing, attempts < max_attempts stays True forever and the round never ends. Whenever a while loop depends on a variable, make sure something inside the loop changes it.

4. Using random.randrange() by mistake

random.randrange(1, 100) stops before 100, so 100 can never be the secret. random.randint(1, 100) includes both ends, which is what a "1 to 100" game needs.

Ideas to extend it

🏋️ Challenges

  1. Difficulty levels. Before each round, ask the player to choose Easy (1-50, 10 attempts), Medium (1-100, 7 attempts) or Hard (1-1000, 10 attempts), then call play_round() with the matching arguments.
  2. "Getting warmer" hints. If the guess is within 5 of the secret, print "Very close!" in addition to too high/too low. Use abs(guess - secret).
  3. Remember repeated guesses. Store guesses in a set and warn the player (without using an attempt) if they type the same number twice.
  4. Best score. Track the fewest attempts needed to win across all rounds and show it at the end.
  5. Swap roles. Write a version where you think of a number and the computer guesses it, using the binary search strategy from the tip in Step 2.
Show a hint for challenge 2
secret = 42
for guess in [30, 39, 47]:
    hint = "Too low!" if guess < secret else "Too high!"
    if abs(guess - secret) <= 5:
        hint += " Very close!"
    print(guess, hint)
30 Too low!
39 Too low! Very close!
47 Too high! Very close!

What's next?

You've built your first complete game. Next, you'll build something useful for everyday life: a command-line to-do list app that saves your tasks to a JSON file, so they're still there tomorrow.

Key takeaways

  • random.randint(a, b) returns a random whole number from a to b, including both ends.
  • A while loop repeats the guessing until the player wins or runs out of attempts.
  • Validate input with strip() and isdigit() so a typo never crashes the game.
  • Splitting the game into get_guess(), play_round() and main() makes each part small and easy to test.
  • Functions that return True or False make the calling code read like plain English.
  • Use random.seed() while testing to make random results repeatable.

Frequently asked questions

How do you make a number guessing game in Python?

Import random, pick a secret with random.randint(1, 100), then use a while loop that reads a guess with input(), converts it with int(), and prints "Too low" or "Too high" until the guess matches the secret.

How many guesses does it take to find a number from 1 to 100?

At most 7, if you always guess the middle of the remaining range. Each guess halves the possibilities, and 2 to the power of 7 is 128, which is more than 100.

How do I stop my guessing game from crashing on bad input?

Check the input before converting it. Use text.isdigit() to make sure it contains only digits, or wrap int(text) in try/except ValueError, and ask again inside a loop until the input is valid.

How do I add a play again option in Python?

Put the round inside an outer while True loop. After each round, ask "Play again? (y/n)" and break out of the loop if the answer is not "y".

Why does my guessing game give the same number every time?

You probably left random.seed() with a fixed value in your code. A fixed seed always produces the same sequence. Remove it, or call random.seed() with no argument, to get different numbers each run.