Programming and IT

Python: practice problems with solutions

A set of problems in order of difficulty — from reversing a string to a generator and a call-counting decorator. Solve each one yourself first and only then open the walkthrough: comparing your code with someone else's teaches more than reading a finished answer. All solutions are tested on Python 3.

Updated
In this article

How to solve them#

The routine is always the same: read the statement, write down two or three "input → expected output" examples, write a solution, run it on the examples, and only then look at the walkthrough. If you cannot get a solution down in ten minutes, go back to the theory linked at the start of the section and try again: peeking at the answer before that point is of little use.

A useful habit: after every solution, ask yourself "what breaks if the input is empty?" Half the problems below have such an edge case, and the walkthrough points it out.

Strings#

Theory: Python strings.

Problem 1. Palindrome. Check whether a phrase reads the same both ways, ignoring spaces, punctuation and case. "A man, a plan, a canal: Panama" → True, "hello" → False.

def is_palindrome(s):
    clean = "".join(ch.lower() for ch in s if ch.isalpha())
    return clean == clean[::-1]

Walkthrough: the generator expression inside join keeps only letters and lowercases them, and [::-1] reverses the string. Compare the cleaned string with its reverse, not the original. By this code an empty string is a palindrome, which is reasonable.

Problem 2. Vowels. Count the vowels in an English word. "Programming" → 3.

def count_vowels(s):
    return sum(1 for ch in s.lower() if ch in "aeiou")

Walkthrough: sum over a generator of ones is the idiom for "count how many items match". Decide up front what to do with y: in "rhythm" it acts as a vowel, and this code counts zero vowels there. Whatever you choose, write it down as part of the problem.

Problem 3. Initials. Turn the string "john william smith" into "J. W. Smith".

def short_name(full):
    parts = full.split()
    return f"{parts[0][0].upper()}. {parts[1][0].upper()}. {parts[2].capitalize()}"

Walkthrough: split() with no argument splits on any whitespace and leaves no empty items, even if there are several spaces between words. Edge case: with fewer than three parts the code fails with IndexError — a production version should check len(parts) == 3.

Lists and slices#

Theory: Python lists and Python slicing.

Problem 4. Second largest. Find the second-largest distinct value in a list. [5, 1, 5, 3, 9, 9] → 5.

def second_largest(nums):
    unique = sorted(set(nums), reverse=True)
    return unique[1] if len(unique) > 1 else None

Walkthrough: set removes duplicates, so the two nines count as one value and the answer is 5, not 9. The length check covers the case where all items are equal.

Problem 5. Chunks of n. Split a list into sublists of n items; the last chunk may be shorter. ([1..7], 3) → [[1, 2, 3], [4, 5, 6], [7]].

def chunks(items, n):
    return [items[i:i + n] for i in range(0, len(items), n)]

Walkthrough: range with a step gives the starting indexes, and a slice stops at the end of the list on its own — so the last chunk comes out shorter without any conditions. That is the main advantage of a slice: it does not raise an exception past the boundary.

Problem 6. Common items in order. Return the values found in both lists, in the order of the first list and without duplicates. ([3, 1, 2, 3, 4], [4, 3, 5]) → [3, 4].

def common(a, b):
    seen = set(b)
    result = []
    for x in a:
        if x in seen and x not in result:
            result.append(x)
    return result

Walkthrough: a set made from the second list gives fast membership checks — on a list they are linear, on a set constant. A plain set(a) & set(b) also gives the right values but loses the order.

Dictionaries#

Theory: Python dictionaries.

Problem 7. The three most common words. Count word frequencies in a string and print the three most common; on a tie, sort alphabetically.

counts = {}
for word in text.split():
    counts[word] = counts.get(word, 0) + 1
top = sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:3]

Walkthrough: get(word, 0) saves you the "is the key there" check. The sort key is a tuple: the minus in front of the count gives descending order by number, and the second field sorts equally frequent words alphabetically. For the string "cat and dog and cat and mouse cat dog" the result is [('and', 3), ('cat', 3), ('dog', 2)].

Problem 8. Group by first letter. Build a "letter → list of words" dictionary from a list of words.

groups = {}
for word in words:
    groups.setdefault(word[0], []).append(word)

Walkthrough: setdefault returns the existing list or stores a new empty one and returns it — one line instead of three with a check. Note that "A" and "a" are different keys; lowercase the words first if that matters.

Problem 9. Totals by category. From a list of pairs like ("tea", 120), build a dictionary with the total for each name.

totals = {}
for name, amount in sales:
    totals[name] = totals.get(name, 0) + amount

Walkthrough: unpacking the pair right in the loop header reads better than indexing with pair[0] and pair[1]. For [("tea", 120), ("coffee", 250), ("tea", 80)] you get {'tea': 200, 'coffee': 250}.

Functions, generators, decorators#

Theory: Python functions, Python generators and Python decorators.

Problem 10. Mean without the extremes. A function takes any number of values and returns the mean without one minimum and one maximum. (1, 5, 6, 7, 100) → 6.0.

def trimmed_mean(*values):
    if len(values) < 3:
        raise ValueError("at least three values are needed")
    kept = sorted(values)[1:-1]
    return sum(kept) / len(kept)

Walkthrough: *values collects the arguments into a tuple, and sorted(...)[1:-1] drops the extremes. The length check is required: with two values the slice would be empty and you would divide by zero.

Problem 11. Fibonacci up to a limit. A generator that yields Fibonacci numbers not exceeding a given value. 50 → 0 1 1 2 3 5 8 13 21 34.

def fib_upto(limit):
    a, b = 0, 1
    while a <= limit:
        yield a
        a, b = b, a + b

Walkthrough: the simultaneous assignment a, b = b, a + b evaluates the whole right-hand side before writing, so no temporary variable is needed. The generator does not build a list — values arrive one at a time, and the limit can be anything.

Problem 12. Call counter. A decorator that counts how many times a function has been called and stores the number in a calls attribute.

import functools

def counted(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        wrapper.calls += 1
        return func(*args, **kwargs)
    wrapper.calls = 0
    return wrapper

Walkthrough: the counter lives as an attribute of the wrapper itself, so it is visible from outside as ping.calls. The line wrapper.calls = 0 comes after the wrapper is defined but before it is returned: by the time of the first call, the attribute already exists.

Files and exceptions#

Theory: reading files in Python and Python exceptions.

Problem 13. Sum of numbers in a file. Read a file line by line, add up the numbers, skip non-numeric lines and count them separately.

def read_numbers(path):
    total, skipped = 0, 0
    with open(path, encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line:
                continue
            try:
                total += int(line)
            except ValueError:
                skipped += 1
    return total, skipped

Walkthrough: try wraps exactly one risky line, and empty lines are filtered out beforehand so they do not count as skipped. For a file with the lines 10, abc, an empty line, 20, 30 the result is (60, 1).

Problem 14. Your own exception. A function takes a square root and raises a custom error for a negative argument.

class NegativeError(ValueError):
    """A negative value is not allowed."""

def sqrt_int(n):
    if n < 0:
        raise NegativeError(f"square root of a negative number: {n}")
    return round(n ** 0.5, 4)

Walkthrough: inheriting from ValueError rather than directly from Exception lets calling code catch it either narrowly (except NegativeError) or broadly (except ValueError) — older code keeps working. The class body is a single docstring, and that is enough.

Step-by-step plan

  1. Strings — problems 1–3Solve the palindrome, vowel count and initials problems; test each solution on two examples.
  2. Lists and slices — problems 4–6Second largest, splitting into chunks, common items in order.
  3. Dictionaries — problems 7–9Word frequencies, grouping by first letter, totals by category.
  4. Functions and generators — problems 10–12Mean without extremes, Fibonacci up to a limit, a call-counting decorator.
  5. Files and errors — problems 13–14Summing numbers from a file while skipping junk, and your own exception class.

Start learning this in your own space

The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.

Start the plan

Check yourself

1.What is second_largest([5, 1, 5, 3, 9, 9]) from problem 4?

2.How many chunks does chunks([1, 2, 3, 4, 5, 6, 7], 3) from problem 5 return?

3.What is trimmed_mean(1, 5, 6, 7, 100) from problem 10?

4.How many Fibonacci numbers not exceeding 50 does the generator from problem 11 yield?

Sources

Was this helpful?

More articles

Programming and IT How to learn Python from scratch Python is a good first programming language: code reads almost like text, and the standard library covers most everyday tasks. This plan takes you from installing the interpreter to your own scripts covered by tests in about four months, at roughly an hour a day. Programming and IT Python: strings A string in Python is an immutable sequence of characters. Almost all of its quirks follow from that: methods do not change a string but return a new one, and fast text building goes through join, not through adding strings in a loop. Below is the working minimum with examples and output. Programming and IT Python: decorators A decorator is a function that takes another function and returns a new one with extra behaviour. The `@` sign above a definition is just a shorthand for an assignment. Keep that in mind and the whole topic takes one evening. Programming and IT Arduino for beginners Arduino is a microcontroller board you can tell when to light an LED and when to read a sensor. You write the program in simplified C++, upload it over USB, and it runs on its own, without a computer. Your first blinking LED takes about twenty minutes. Programming and IT How to learn SQL from scratch SQL is the query language of relational databases. Developers, analysts, testers and managers who want to pull numbers themselves all need it. Basic queries take a few weeks to learn; working confidently with complex reports takes two or three months of practice. Below is the order of topics and ways to train on a real database. Programming and IT How to learn Linux from scratch Linux runs most servers, containers and countless devices, so developers, testers, analysts and system administrators all need the command line. The easiest way to learn it is not by reading lists of commands but by working in the terminal every day and solving small practical tasks. Below is a sequence of topics for two to three months.

More solutions