Python Practice Exercises
40 hands-on problems with full solutions
A curated set of beginner-friendly exercises that build fluency with Python's core language features. Every exercise has a clear task description, starter code, expected output, and a fully explained solution — try it yourself before expanding the answer.
Exercise 1 — Greet the user name = "Ana" print(f"Hello, {name}!")
📚 What This Exercise Set Covers
40 exercises grouped by topic — do them in order or jump to any section
🧭 How to Use These Exercises
A repeatable process that turns solving into learning
The recommended workflow
1. Read the task. Understand what the exercise asks before looking at any code.
2. Try it yourself. Give yourself 15 minutes of genuine effort — write your own version in the starter code, run it, see what happens.
3. Only then check the solution. Compare your approach to the provided one. Both may be valid — the point is what you learned in the gap.
4. Re-implement the solution from memory. Close the solution panel, delete your code, and write the whole thing again. This step is where the learning really happens.
🟢 Basics (Exercises 1–10)
Variables, arithmetic, conditionals, and simple loops
Greet the User
Print a greeting that includes the user's name.
● EasyTask: Create a variable name with any value. Print a greeting in the form Hello, [name]!.
# Your code here name = "Ana" print(f"Hello, {name}!")
Show solution
- 1Line 1 is a comment — a note for human readers, ignored by Python.
- 2
name = "Ana"creates a variable holding the string"Ana". - 3The f-string
f"Hello, {name}!"substitutes the value ofnameinto the string, then prints it.
Sum of Two Numbers
Add two integers and print the result.
● EasyTask: Create two variables a and b, each holding an integer. Compute their sum and print it.
a = 7 b = 3 print(a + b)
Show solution
- 1
a = 7stores the integer 7. - 2
b = 3stores the integer 3. - 3
print(a + b)computes 7 + 3 = 10 and prints it.
Even or Odd
Determine whether a number is even or odd.
● EasyTask: Given a variable n, print "even" if it's divisible by 2, otherwise print "odd".
n = 17 if n % 2 == 0: print("even") else: print("odd")
Show solution
- 1
n = 17— the value to test. - 2
n % 2 == 0— the modulo operator%returns the remainder. If it's 0, the number is even. - 3Branch taken when
nis even. - 4The
elsebranch runs when the condition is false. - 5Prints
"odd"— 17 divided by 2 leaves a remainder of 1.
Count to Ten
Print the numbers from 1 to 10, one per line.
● EasyTask: Use a loop to print each number from 1 to 10 on its own line.
for i in range(1, 11): print(i)
Show solution
- 1
range(1, 11)yields 1 through 10. Remember: the stop value (11) is excluded. - 2Each iteration prints the current value of
i.
Sum from 1 to N
Compute the sum of the first N positive integers.
● EasyTask: Given n = 100, compute and print the sum 1 + 2 + … + 100.
n = 100 total = 0 for i in range(1, n + 1): total += i print(total)
Show solution
- 1
n = 100— the upper limit. - 2
totalis the accumulator, starting at 0. - 3
range(1, n + 1)yields 1 through 100 inclusive. - 4
total += iadds each number to the running total. - 5Prints the final sum:
5050.
Shorter version: print(sum(range(1, 101))) does the same thing in one line — but the loop version makes the mechanism visible, which is the point of the exercise.
Largest of Three
Find the maximum among three numbers.
● EasyTask: Given three variables a, b, c, print the largest one.
a = 12 b = 45 c = 23 print(max(a, b, c))
Show solution
- 1–3Three variables with different values.
- 5
max()accepts any number of arguments and returns the largest.
Manual alternative: if a >= b and a >= c: print(a), and so on — instructive to write once, but max() is what you'd use in real code.
FizzBuzz (Mini)
The classic coding exercise, simplified to a single number.
● MediumTask: Given n, print "Fizz" if divisible by 3, "Buzz" if divisible by 5, "FizzBuzz" if both, otherwise the number itself.
n = 15 if n % 15 == 0: print("FizzBuzz") elif n % 3 == 0: print("Fizz") elif n % 5 == 0: print("Buzz") else: print(n)
Show solution
- 1
n = 15— the value to classify. - 3Check the "both" case first — otherwise
FizzorBuzzwould win and we'd never reachFizzBuzz. The order of theif/elifchain matters. - 4–7Individual cases for divisible by 3 or by 5.
- 8–9The
elsebranch prints the number itself.
Multiplication Table
Print the 7× table.
● EasyTask: Print the multiplication table for 7, from 7×1 through 7×10.
for i in range(1, 11): print(f"7 × {i} = {7 * i}")
Show solution
- 1Loop from 1 to 10.
- 2F-string computes
7 * iand formats the whole line.
Countdown
Print numbers from 10 down to 1, then "Liftoff!".
● EasyTask: Use a loop to count down from 10, then print "Liftoff!".
for i in range(10, 0, -1): print(i) print("Liftoff!")
Show solution
- 1
range(10, 0, -1)— start at 10, stop before 0, step −1. Yields 10, 9, …, 1. - 2Prints each countdown number.
- 3Runs once after the loop finishes — not inside it.
Factorial
Compute 5! (the factorial of 5).
● MediumTask: Compute and print 5!, which is 5 × 4 × 3 × 2 × 1.
n = 5 result = 1 for i in range(1, n + 1): result *= i print(result)
Show solution
- 1
n = 5— the number whose factorial we want. - 2
result = 1— the identity for multiplication. Must start at 1, not 0. - 3Loop 1 through 5.
- 4
result *= imultiplies the running product by each value. - 5Prints
120— 5 × 4 × 3 × 2 × 1.
📝 Strings (Exercises 11–18)
Indexing, slicing, methods, and f-strings
Reverse a String
Print a string backwards.
● EasyTask: Given s = "Python", print the string in reverse.
s = "Python" print(s[::-1])
Show solution
- 1The string to reverse.
- 2
s[::-1]is a slice with step −1. It walks the string from end to start, producing a reversed copy. The originalsis unchanged (strings are immutable).
Count Vowels
Count how many vowels appear in a word.
● MediumTask: Given a word, count how many of its characters are vowels (aeiou, case-insensitive).
word = "Programming" vowels = "aeiou" count = 0 for ch in word.lower(): if ch in vowels: count += 1 print(count)
Show solution
- 1The word to inspect.
- 2The set of vowel characters — lowercase only, so we lower() the input.
- 4Accumulator starting at 0.
- 5
word.lower()makes the check case-insensitive — "Programming" contains "P" and "o", which lowercase to "p" and "o". - 6–7Increments
countwhen the character is a vowel. "Programming" → o, a, i = 3. - 8Prints
3.
Palindrome Check
Check if a word reads the same forwards and backwards.
● MediumTask: Given a word, print True if it's a palindrome (reads the same forwards and backwards), otherwise False.
word = "racecar" cleaned = word.lower() print(cleaned == cleaned[::-1])
Show solution
- 1The word to test.
- 2Lowercase copy — makes the comparison case-insensitive.
- 3Compares the cleaned string to its reversed version. If they're equal, it's a palindrome. "racecar" reversed is "racecar" — same.
Title Case a Sentence
Capitalize the first letter of every word.
● EasyTask: Given a sentence, capitalize each word's first letter and lowercase the rest.
sentence = "the quick brown fox" print(sentence.title())
Show solution
- 1The input string.
- 2
.title()uppercases the first character of each word and lowercases the rest. Simple and idiomatic.
Word Lengths
Print the length of every word in a sentence.
● MediumTask: For each word in a sentence, print the word and its length.
sentence = "Python is fun to learn" for word in sentence.split(): print(f"{word}: {len(word)}")
Show solution
- 1The sentence to analyse.
- 3
.split()with no argument splits on whitespace —['Python', 'is', 'fun', 'to', 'learn']. - 4Print each word with its length.
Replace and Clean
Normalize a string of phone numbers.
● MediumTask: Given " (555) 123-4567 ", remove spaces, parentheses, and dashes, leaving just the digits.
phone = " (555) 123-4567 " cleaned = "" for ch in phone: if ch.isdigit(): cleaned += ch print(cleaned)
Show solution
- 1The raw phone string with spaces and punctuation.
- 2Accumulator for the cleaned digits.
- 3Iterates over each character.
- 4
.isdigit()returns True only for 0–9. - 5Appends digits, skips everything else.
- 6Result:
"5551234567".
Shorter: print("".join(c for c in phone if c.isdigit())) — uses a generator expression instead of an explicit accumulator.
First Non-Repeating Character
Find the first character that appears only once.
● HardTask: Given "swiss", find the first character that appears exactly once.
word = "swiss" counts = {} for ch in word: counts[ch] = counts.get(ch, 0) + 1 for ch in word: if counts[ch] == 1: print(ch) break
Show solution
- 1The input word.
- 2Dictionary to store character counts.
- 3–4First pass: build the count map.
.get(ch, 0) + 1handles missing keys without a separate check. - 6–9Second pass: iterate in original order. The first character with count 1 is the answer.
breakstops the loop early.
Result: "w" — "s" appears 3 times, "i" appears once, but "w" appears first among the unique characters.
String Compression (Basic)
Compress runs of repeated characters.
● HardTask: Given "aaabbbcc", print "a3b3c2" — each character followed by the count of consecutive repeats.
s = "aaabbbcc" result = [] prev = s[0] count = 1 for ch in s[1:]: if ch == prev: count += 1 else: result.append(f"{prev}{count}") prev = ch count = 1 result.append(f"{prev}{count}") print("".join(result))
Show solution
- 1The input string.
- 3Accumulator for the output chunks.
- 4–5
prevtracks the current run's character;countcounts its length. Start with the first character. - 7Iterate from the second character onward.
- 8–9If the character matches
prev, increment the count. - 10–12Otherwise, the run has ended — emit
prev + count, then start a new run. - 14After the loop, emit the final run — it has no following character to trigger the else-branch.
- 15Join the chunks into one string.
📦 Collections (Exercises 19–28)
Lists, dicts, sets, and comprehensions
Sum a List
Add up all numbers in a list.
● EasyTask: Given a list of numbers, print their sum.
numbers = [4, 7, 2, 9, 1] print(sum(numbers))
Show solution
- 1The list of numbers.
- 2
sum()is a built-in that adds all elements. Fast and idiomatic.
Filter Even Numbers
Build a new list containing only the even numbers.
● EasyTask: Given a list of integers, produce a new list with only the even values.
nums = [1, 2, 3, 4, 5, 6] evens = [n for n in nums if n % 2 == 0] print(evens)
Show solution
- 1The input list.
- 2List comprehension with an
iffilter after thefor. - 3Prints
[2, 4, 6].
Find the Maximum Without max()
Manually find the largest element.
● MediumTask: Given a non-empty list, find the largest value using a loop (don't use max()).
nums = [3, 8, 1, 9, 4] largest = nums[0] for n in nums[1:]: if n > largest: largest = n print(largest)
Show solution
- 1The list to inspect.
- 3Initialise with the first element — must be an actual list value, not
0. - 4–6Iterate over the rest. Whenever a bigger value is found, update
largest. - 7Prints 9.
Count Word Frequency
Count how many times each word appears.
● MediumTask: Given a list of words, build a dict mapping each word to its count.
words = ["apple", "banana", "apple", "cherry", "banana", "apple"] counts = {} for w in words: counts[w] = counts.get(w, 0) + 1 print(counts)
Show solution
- 1List of words with duplicates.
- 3Empty dict for counts.
- 4–5
.get(w, 0) + 1— returns the current count (or 0) plus one. Assigns back to the same key. - 7Prints the frequency map.
Shortcut: from collections import Counter; Counter(words) does the same in one line — and the result is a dict subclass.
Merge Two Lists
Combine two lists into one, alternating items.
● MediumTask: Given two equal-length lists, interleave their items.
a = [1, 3, 5] b = [2, 4, 6] merged = [item for pair in zip(a, b) for item in pair] print(merged)
Show solution
- 1–2Two input lists.
- 4
zip(a, b)produces pairs(1, 2),(3, 4),(5, 6). The outer comprehension iterates over pairs; the inner iterates over the two items within each pair. - 5Prints the interleaved list.
Deduplicate Preserving Order
Remove duplicates but keep the original order.
● MediumTask: Given a list with repeated values, produce a new list where each value appears once — in the order it first appeared.
nums = [3, 1, 2, 3, 1, 4, 2] unique = list(dict.fromkeys(nums)) print(unique)
Show solution
- 1The input list with duplicates.
- 2
dict.fromkeys(nums)creates a dict with each element as a key. Since Python 3.7, dicts preserve insertion order — duplicates are dropped but the first occurrence order is kept.list(...)converts the dict's keys back to a list. - 3Prints
[3, 1, 2, 4].
A plain set(nums) would also deduplicate, but the order would be lost. This is why the dict.fromkeys trick is the preferred idiom when order matters.
List of Squares (Comprehension)
Build a list of squares using a comprehension.
● EasyTask: Using a single expression, build a list [1, 4, 9, 16, 25].
squares = [n * n for n in range(1, 6)] print(squares)
Show solution
- 1
range(1, 6)yields 1–5. Each is squared in the expression. - 2Prints the list.
Total Per Category
Sum amounts grouped by category.
● HardTask: Given a list of (category, amount) tuples, build a dict of total amount per category.
transactions = [
("food", 12.50),
("transport", 3.00),
("food", 8.00),
("entertainment", 15.00),
("food", 5.00),
]
totals = {}
for category, amount in transactions:
totals[category] = totals.get(category, 0) + amount
print(totals)Show solution
- 1–7The transaction list — each entry is a (category, amount) tuple.
- 9Empty dict for accumulating totals.
- 10–11Unpack each tuple into
categoryandamount. Addamountto the running total for that category..get(category, 0)initialises new categories at 0. - 13Prints the totals dict.
Find Common Elements
Find the intersection of two lists.
● MediumTask: Given two lists, print the elements they have in common.
a = [1, 2, 3, 4] b = [3, 4, 5, 6] common = list(set(a) & set(b)) print(sorted(common))
Show solution
- 1–2Two input lists.
- 4
set(a) & set(b)— the&operator on sets produces the intersection.list(...)converts back to a list. - 5
sorted()ensures predictable output order (sets are unordered).
Transpose a Grid
Swap rows and columns of a 2D list.
● HardTask: Given a 3×3 grid, produce its transpose (rows become columns).
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
]
transposed = [list(row) for row in zip(*grid)]
for row in transposed:
print(row)Show solution
- 1–5The input grid — a list of three rows.
- 7
zip(*grid)unpacks the three rows into arguments tozip, producing three column-tuples.list(row)converts each tuple to a list for consistency with the input type. - 8–9Prints each transposed row.
🔧 Functions (Exercises 29–35)
Defining reusable functions with parameters and return values
Basic Function
Write a function that returns a greeting.
● EasyTask: Define a function greet(name) that returns "Hello, [name]!".
def greet(name): return f"Hello, {name}!" print(greet("Ana")) print(greet("Bob"))
Show solution
- 1
def greet(name):defines a function namedgreetthat accepts one parameter. - 2
returnsends back the greeting string — the function's output. - 4–5Two calls with different arguments prove the parameter works.
Function with Default Parameter
Add an optional argument with a sensible default.
● EasyTask: Define greet(name, greeting="Hello") where greeting can be overridden.
def greet(name, greeting="Hello"): return f"{greeting}, {name}!" print(greet("Ana")) print(greet("Bob", greeting="Hi"))
Show solution
- 1
greeting="Hello"is a default parameter value — used when the caller doesn't supply the argument. - 2The f-string interpolates both.
- 4Called without
greeting, the default"Hello"is used. - 5Called with an explicit
greeting="Hi", the default is overridden.
Function Returning Multiple Values
Return several values at once.
● MediumTask: Define min_max(nums) that returns both the smallest and largest values in a list.
def min_max(nums): return min(nums), max(nums) nums = [4, 1, 8, 2, 9] small, large = min_max(nums) print(f"min={small} max={large}")
Show solution
- 1–2
return min(nums), max(nums)— the comma creates a tuple. Python returns it as a single tuple object. - 4Input list.
- 5Tuple unpacking: the returned tuple is split into two variables.
- 6Prints both values.
Variable Number of Arguments
Accept any number of numbers and sum them.
● MediumTask: Define sum_all(*args) that adds any number of arguments.
def sum_all(*args): return sum(args) print(sum_all(1, 2, 3)) print(sum_all(10)) print(sum_all())
Show solution
- 1
*argscollects all positional arguments into a tuple. - 2
sum(args)adds them. For an empty tuple,sum()returns0. - 4–6Three calls with different numbers of arguments — the same function handles all cases.
Lambda Function
Sort a list of dictionaries by a key.
● MediumTask: Given a list of student dicts, sort them by score (descending).
students = [
{"name": "Ana", "score": 95},
{"name": "Bob", "score": 88},
{"name": "Cara", "score": 92},
]
sorted_students = sorted(students, key=lambda s: s["score"], reverse=True)
for s in sorted_students:
print(f"{s['name']}: {s['score']}")Show solution
- 1–5A list of student dicts.
- 7
key=lambda s: s["score"]— the lambda tellssortedwhich field to sort by. Without it,sortedwould try to compare dicts, which raisesTypeError. - 8
reverse=Truesorts highest first. - 9–10Prints the sorted list.
Recursive Factorial
Rewrite Exercise 10 using recursion.
● HardTask: Define factorial(n) recursively. factorial(0) is 1; factorial(n) is n × factorial(n-1).
def factorial(n): if n <= 1: return 1 return n * factorial(n - 1) print(factorial(5)) print(factorial(0))
Show solution
- 1Function definition.
- 2–3Base case — for
n ≤ 1, factorial is 1. Without this, the recursion would never terminate. - 4Recursive case — calls itself with
n - 1. - 6
factorial(5)→5 × factorial(4)→ … →5 × 4 × 3 × 2 × 1 = 120. - 7Base case returns 1 for n=0.
Recursion is elegant for small inputs but can hit Python's recursion limit (~1000 by default) for large n. For anything beyond toy sizes, the iterative version from Exercise 10 or math.factorial is the right choice.
Higher-Order Function
Write a function that takes another function as an argument.
● HardTask: Define apply_twice(func, x) that applies func to x twice.
def apply_twice(func, x): return func(func(x)) print(apply_twice(lambda n: n + 10, 5)) print(apply_twice(lambda n: n * 2, 3))
Show solution
- 1
apply_twicetakes a function and a value. - 2
func(func(x))appliesfunctox, then applies it again to the result. - 4With
+10: 5 + 10 = 15, then 15 + 10 = 25. - 5With
*2: 3 * 2 = 6, then 6 * 2 = 12.
🏛️ Classes (Exercises 36–40)
Object-oriented Python — defining classes, methods, and properties
A Simple Class
Define a class with attributes and a method.
● MediumTask: Define a Dog class with name and breed attributes, and a bark() method that returns "Woof!".
class Dog: def __init__(self, name, breed): self.name = name self.breed = breed def bark(self): return "Woof!" d = Dog("Rex", "Labrador") print(f"{d.name} says {d.bark()}")
Show solution
- 1
class Dog:starts the class definition. - 2
__init__is the constructor — runs when a new instance is created. - 3–4Store the arguments as instance attributes.
- 6–7
barktakesselfand returns a fixed string. - 9Creates an instance:
dis aDog. - 10Access attributes and call a method. Result:
"Rex says Woof!".
Class with @property
Expose a computed value as an attribute.
● MediumTask: Define a Rectangle class with width and height, and an area property that returns width × height.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property def area(self): return self.width * self.height r = Rectangle(4, 5) print(f"Area: {r.area}")
Show solution
- 1Class definition.
- 2–4Constructor stores width and height.
- 6
@propertyturns the following method into an attribute-like accessor. - 7–8
areacomputes and returns the product — but callers use it asr.area, notr.area(). - 10–11Creates a rectangle and prints its area using property access.
Inheritance
Extend a base class with a subclass.
● HardTask: Define Animal with speak(), then a Dog subclass that overrides speak().
class Animal: def speak(self): return "..." class Dog(Animal): def speak(self): return "Woof!" for animal in [Animal(), Dog()]: print(animal.speak())
Show solution
- 1–3Base class with a generic
speak. - 5
class Dog(Animal):—Doginherits fromAnimal. - 6–7
speakoverrides the base implementation. - 9–10Both objects respond to
.speak()— this is polymorphism. The base class returns"..."; the override returns"Woof!".
Custom Exception
Define a custom exception class and raise it.
● MediumTask: Define ValidationError (a subclass of Exception) and a function validate_age(age) that raises it for negative values.
class ValidationError(Exception): pass def validate_age(age): if age < 0: raise ValidationError("Age cannot be negative") return age try: validate_age(-5) except ValidationError as e: print(f"Caught: {e}")
Show solution
- 1–2
ValidationErrorinherits fromException. The body ispassbecause the class name itself is the message — it's meaningful to callers reading the code. - 4–7Raises the exception for negative ages, otherwise returns the value.
- 9–12
try/exceptcatches the custom exception and prints its message.
Dataclass
Define a data-holding class with minimal boilerplate.
● MediumTask: Use @dataclass to define a Point with x and y. Verify that two identical points compare equal.
from dataclasses import dataclass @dataclass class Point: x: int y: int p1 = Point(3, 4) p2 = Point(3, 4) print(p1) print(p1 == p2)
Show solution
- 1Import the decorator from the standard library.
- 3
@dataclassauto-generates__init__,__repr__, and__eq__from the annotated fields. - 4–6Class with two fields and no explicit methods — that's the point.
- 8–9Two separate instances with identical data.
- 11Auto-generated
__repr__gives a readable form:Point(x=3, y=4). - 12Auto-generated
__eq__makes==compare fields. PrintsTrue.
❓ Frequently Asked Questions
Common questions about using these exercises
Two to three per day is a good rhythm for beginners. The point of practice is understanding, not speed — it is far better to fully grasp three exercises than to skim through ten without absorbing them. Aim for 15–20 minutes on each exercise, including time spent reading the solution if you get stuck.
Give yourself 15 minutes of genuine effort — try different approaches, print intermediate values, and read any error message slowly. If you're still stuck after that, read the solution and then re-implement it from memory without looking. Re-implementing is where the learning happens; simply reading code is not the same as writing it.
No. These exercises build fluency with Python's core language features, but real jobs require project work, testing, version control, reading other people's code, and domain knowledge. Treat these exercises as your foundation, then move on to building projects: a CLI tool, a web scraper, a small API, a data visualisation. The exercises prepare you for those projects.
Yes — the exercises are free to use and share. Attribution back to Poetic Bytes is appreciated but not required for educational use. If you're teaching, the numbering makes it easy to assign specific problems (e.g. "do exercises 1–10 tonight").
Educational Use Disclaimer
This page is part of Poetic Bytes, a free educational resource for learning Python programming. The code examples are provided for instruction and demonstration. They are not production-audited and should not be used as-is in security-sensitive, financial, or safety-critical systems without independent review. See our full disclaimer and terms of use.
📬 Python, weekly
Short lessons, real code, and ecosystem news — sent about once a week
🗣️ Learner Voices
No comments yet — your voice starts the conversation.