Python Coding Challenges

Solve classic Python coding challenges by stating the contract, writing tests first, building a working solution and explaining its invariant and cost.

Lesson 100 of 107Intermediate15 min read

A coding challenge is a small, precisely defined problem that tests whether you can turn a specification into correct, efficient code. They appear in interviews, competitive programming and on practice sites, and they're an excellent way to strengthen the fundamentals from this course.

This page works through four classic challenges with full solutions: repeated characters, balanced brackets, merging intervals and shortest unweighted routes. More importantly, it shows a repeatable method for any challenge:

  1. Pin down the contract: inputs, outputs and what happens with invalid input.
  2. Write test cases first, including boundaries and cases designed to break shortcuts.
  3. Build a simple working solution, then improve it if needed.
  4. Explain why it works (its invariant) and what it costs (its complexity).

The challenges use a summer festival's badge printing, schedules and site map as their setting.

Challenge contracts

Before writing code, answer every question the problem statement leaves open. For "return the characters that appear more than once in a badge text":

  • Are upper- and lower-case letters the same character?
  • Do spaces and punctuation count?
  • What order should results be in, and should each character appear once?
  • What happens for an empty string?

Write the decisions down as a docstring:

Python
def repeated_symbols(text):
    """Return each character that appears more than once in text,
    case-sensitive, spaces included, sorted, each listed once.
    An empty string returns []."""

Ambiguous rules make correctness impossible to judge: two people can write "correct" solutions that disagree. In an interview, asking these questions out loud is part of what's being assessed. On practice sites, the examples usually settle them, so read every example before coding.

Test cases

Good tests cover three kinds of input:

  • Ordinary: a typical case from the problem statement.
  • Boundaries: empty input, one element, everything identical, the largest allowed size.
  • Adversarial: cases designed to break a tempting shortcut.

For balanced brackets, the tempting shortcut is "count opening and closing brackets and check the counts match". The adversarial test ")(" has matching counts but isn't balanced, and "([)]" has every pair present but in the wrong nesting order:

Python
cases = {
    "": True, "()": True, "([]{})": True,       # ordinary and boundary
    ")(": False, "([)]": False, "((": False,    # adversarial
}

Write these before the solution, so you're testing the specification, not the code you happened to write. Then a failing case tells you precisely which part of the reasoning is wrong.

Incremental solutions

Start with the clearest solution you're confident is correct, verify it against the tests, and only then optimise if the constraints require it. For merging overlapping intervals:

Python
def merge(intervals):
    merged = []
    for start, end in sorted(intervals):
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

Sorting first is the key insight: once intervals are ordered by start time, each one can only overlap the most recent merged interval. That turns an O(n²) "compare everything with everything" approach into O(n log n).

Optimising before the contract and tests are clear causes avoidable mistakes, such as treating [9, 11] and [11, 12] as overlapping or not. Decide that in the contract first (here, touching intervals merge because the comparison is <=), then write code that follows it.

Explanation

A finished solution includes a short explanation of why it's correct and what it costs. For repeated characters with a dictionary:

  • Invariant: after processing the first i characters, counts[c] equals the number of times c appears among them.
  • Correctness: at the end, the invariant covers the whole string, so filtering counts above 1 gives exactly the repeated characters.
  • Cost: O(n) time for one pass, plus O(k log k) to sort the k distinct characters; O(k) extra space.

An invariant is a statement that's true before the loop, stays true after each iteration, and gives the answer when the loop ends. For balanced brackets the invariant is "the stack holds exactly the opening brackets not yet closed, in order". Passing the examples shows the code works on those inputs; the invariant explains why it works on all of them.

Independent practice and a worked check

Task: solve repeated symbols, balanced brackets, merged intervals and shortest unweighted routes, documenting contracts and costs. Use this harness to test your own versions:

Python
def check(func, cases):
    failures = [(given, expected, func(*given)) for given, expected in cases
                if func(*given) != expected]
    for given, expected, got in failures:
        print(f"FAIL {func.__name__}{given}: expected {expected}, got {got}")
    print(f"{func.__name__}: {len(cases) - len(failures)}/{len(cases)} passed")

def repeated_symbols(text):
    counts = {}
    for ch in text:
        counts[ch] = counts.get(ch, 0) + 1
    return sorted(ch for ch, n in counts.items() if n > 1)

check(repeated_symbols, [(("",), []), (("abc",), []), (("aAa",), ["a"]), (("a a",), ["a"])])

This prints repeated_symbols: 4/4 passed. Note that "a a" contains one space, so only "a" repeats. Write each function's contract and complexity in its docstring. For the worked check, a challenge should include boundary and adversarial cases besides an ordinary example.

Reference and next practice

The collections and bisect modules provide building blocks used in many challenge solutions. Continue with practice problems and revisit interview questions and complexity. Practise with the Python exercises and Python quiz.

Python Coding Challenges Summary

  • Challenge contracts: State input types, expected outputs and invalid input behavior before solving.
  • Test cases: Include ordinary cases, boundaries and a case designed to break a tempting shortcut.
  • Incremental solutions: A clear working approach can be improved after its behavior is verified.
  • Explanation: A challenge solution should explain its invariant and complexity.

Python Coding Challenges Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Python Coding Challenges in a small program

Challenge 1, repeated characters, solved with a counting dictionary:

Python
def repeated_symbols(text):
    counts = {}
    for symbol in text:
        counts[symbol] = counts.get(symbol, 0) + 1
    return sorted(symbol for symbol, count in counts.items() if count > 1)

print(repeated_symbols("lantern trail"))
Output
['a', 'l', 'n', 'r', 't']
Line-by-line explanation
  1. Lines 3 to 4One pass counts every character, including the space.
  2. Line 5Only characters seen more than once are kept, sorted for a predictable result.
Example 2

Challenge 2: balanced brackets with a stack

Python
def balanced(text):
    pairs = {")": "(", "]": "[", "}": "{"}
    stack = []
    for ch in text:
        if ch in "([{":
            stack.append(ch)
        elif ch in pairs:
            if not stack or stack.pop() != pairs[ch]:
                return False
    return not stack

cases = {"": True, "()": True, "([]{})": True, ")(": False, "([)]": False, "((": False}
for text, expected in cases.items():
    assert balanced(text) == expected, text
print("all", len(cases), "bracket cases pass")
Output
all 6 bracket cases pass
Line-by-line explanation
  1. Lines 5 to 6Every opening bracket is pushed; the stack holds brackets still waiting to close.
  2. Lines 8 to 9A closing bracket must match the most recent unclosed opener, which is why "([)]" fails.
  3. Line 10Anything left on the stack was never closed.
Example 3

Challenge 3: merge overlapping time slots

Python
def merge(intervals):
    merged = []
    for start, end in sorted(intervals):
        if merged and start <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], end)
        else:
            merged.append([start, end])
    return merged

print(merge([[14, 16], [9, 11], [10, 12], [16, 17], [19, 20]]))
print(merge([]))
print(merge([[9, 18], [10, 11]]))
Output
[[9, 12], [14, 17], [19, 20]]
[]
[[9, 18]]
Line-by-line explanation
  1. Line 3Sorting by start time means each interval can only overlap the last merged one.
  2. Lines 4 to 5<= merges touching slots such as 14-16 and 16-17, as the contract decided.
  3. Line 12A slot fully inside another is absorbed, because max keeps the later end.
Example 4

Challenge 4: fewest steps between festival stages

Python
from collections import deque

paths = {"Gate": ["Food", "Main"], "Food": ["Gate", "Acoustic"], "Main": ["Gate", "Dance"],
         "Acoustic": ["Food", "Dance"], "Dance": ["Main", "Acoustic"], "Camp": []}

def fewest_steps(graph, start, goal):
    steps = {start: 0}
    queue = deque([start])
    while queue:
        place = queue.popleft()
        if place == goal:
            return steps[place]
        for nxt in graph[place]:
            if nxt not in steps:
                steps[nxt] = steps[place] + 1
                queue.append(nxt)
    return None

print(fewest_steps(paths, "Gate", "Dance"))
print(fewest_steps(paths, "Gate", "Camp"))
print(fewest_steps(paths, "Food", "Food"))
Output
2
None
0
Line-by-line explanation
  1. Lines 7 to 17Breadth-first search visits places in order of distance, so the first time it reaches the goal is via the fewest steps.
  2. Lines 20 to 21An unreachable goal returns None, and start equal to goal returns 0: both boundary cases from the contract.

Common Python Coding Challenges Mistakes

Counting brackets instead of matching them

Don't
def balanced(text):
    return text.count("(") == text.count(")")     # ")(" passes
Do
def balanced(text):
    depth = 0
    for ch in text:
        depth += 1 if ch == "(" else -1 if ch == ")" else 0
        if depth < 0:
            return False                           # closed before opened
    return depth == 0

Testing only the example from the problem

Don't
assert merge([[1, 3], [2, 4]]) == [[1, 4]]       # one ordinary case
Do
for given, expected in [([], []), ([[1, 2]], [[1, 2]]), ([[1, 2], [2, 3]], [[1, 3]]),
                        ([[5, 6], [1, 2]], [[1, 2], [5, 6]]), ([[1, 10], [2, 3]], [[1, 10]])]:
    assert merge(given) == expected, given

Python Coding Challenges in Real Projects

Where these challenges appear

Each challenge here is a pattern you'll meet in real software. Counting with dictionaries underlies analytics and deduplication. Bracket matching is how editors, linters and parsers check code and markup. Interval merging handles calendar availability, booking systems and time-range queries. Breadth-first search powers routing, network analysis and "people you may know" features.

Interview platforms and sites such as Exercism, Advent of Code and LeetCode offer hundreds more challenges. Using the four-step method on each, rather than memorising solutions, is what builds lasting skill.

Python Coding Challenges FAQ

How do I get better at Python coding challenges?

Solve problems regularly, starting with easy ones, and follow the same method each time: clarify the contract, write tests, build a simple solution, then improve and explain it. Review other people's solutions afterwards to learn new patterns.

Should I memorise solutions to common problems?

Memorise patterns, not solutions: counting with a dictionary, two pointers, sorting first, using a stack, breadth-first search. Recognising which pattern fits a new problem is the real skill.

What data structures appear most in coding challenges?

Lists, dictionaries and sets most often, then stacks and queues (deque), heaps (heapq) and graphs as adjacency lists. Knowing their operation costs lets you pick the right one quickly.

How do I know if my solution is efficient enough?

Compare its complexity with the input limits. Around 10⁷ to 10⁸ simple operations per second is a rough guide, so with n up to 100,000, an O(n²) solution is usually too slow while O(n log n) is fine.

Are coding challenges the same as real programming work?

Not exactly. Real work involves unclear requirements, existing code, collaboration and maintenance. But the skills challenges train, precise thinking, testing edge cases and choosing data structures, transfer directly.

Python Coding Challenges Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

What should a challenge include besides an ordinary example?

Multiple choicemedium

Which review concern is valid for Python coding challenges?

Python Coding Challenges Quiz

2 questions to check what you learned.

  1. What should a challenge include besides an ordinary example?

    • Only a time limit
    • Boundary and adversarial cases
    • Python ignores the distinction
  2. Which statement correctly describes incremental solutions?

    • A clear working approach can be improved after its behavior is verified.
    • Only the example output matters, not the rule
    • Every operation has the same behavior