☰ Learn Python Tutorial Menu

Practice Challenges: Put It All Together

Written by CSA mentors · Updated 26 Sept 2026 · 7 min read


Here's what the first week looks like for a junior analyst at a Karachi fintech we've placed students with. Monday is cleaning phone numbers and computing fees. Wednesday is an ageing report. By Friday they've loaded a month of wallet transactions into SQLite and produced a flag list for the compliance team. These ten challenges are that week, condensed. Each is graded from warm-up to build-it. Attempt every one in the playground before opening the solution, then compare and improve your version. None of them needs pandas, and all ten run in the playground. If you're preparing for a technical interview, several of these are close cousins of questions our interview preparation mentors hear candidates being asked.

Three tiers of challenges, warm-up, data tasks and build it, with the lessons each tier relies on

Tier 1: Warm-up

Challenge 1: GST calculator

Write gst_breakdown(gross) that takes a GST-inclusive price (17%) and returns a tuple of (net, tax), each rounded to two decimals. Print the result for Rs 4,500.

Solution

def gst_breakdown(gross, rate=0.17):
    net = gross / (1 + rate)
    return round(net, 2), round(gross - net, 2)

net, tax = gst_breakdown(4500)
print(f"Net Rs {net:,.2f}, GST Rs {tax:,.2f}")     # Net Rs 3,846.15, GST Rs 653.85

Challenge 2: Grade classifier

Given a list of (name, marks) tuples, print each student's grade (A 80+, B 70+, C 60+, D 40+, F below) and then the count of each grade. Notice how the solution uses a list of cut-offs instead of an elif ladder. It's the same idea as the tariff dictionary from Lesson 10.

Solution

from collections import Counter

def grade(marks):
    for cutoff, letter in [(80, "A"), (70, "B"), (60, "C"), (40, "D")]:
        if marks >= cutoff:
            return letter
    return "F"

students = [("Hira", 91), ("Ali", 58), ("Sana", 74), ("Bilal", 35), ("Zara", 80)]
grades = Counter()
for name, marks in students:
    g = grade(marks)
    grades[g] += 1
    print(f"{name:<6} {marks:>3}  {g}")
print(dict(sorted(grades.items())))

Challenge 3: Word counter

Count the five most common words in a paragraph, ignoring case and punctuation. This one comes up in interviews more than any other on this page.

Solution

import re
from collections import Counter

text = """Sales in Lahore rose again. Lahore now leads Karachi, and Karachi
leads Multan. Sales teams in Lahore expect sales to rise further."""
words = re.findall(r"[a-z]+", text.lower())
for word, n in Counter(words).most_common(5):
    print(f"{word:<8} {n}")

Tier 2: Data tasks

Challenge 4: Sales by city

From a list of (city, amount) tuples, print each city's total, average and share of overall revenue, sorted by total descending.

Solution

from collections import defaultdict

rows = [("Lahore", 2500), ("Multan", 1800), ("Lahore", 3200), ("Karachi", 9100),
        ("Lahore", 1500), ("Karachi", 2700), ("Multan", 8400)]
by_city = defaultdict(list)
for city, amount in rows:
    by_city[city].append(amount)
grand = sum(a for _, a in rows)
for city, vals in sorted(by_city.items(), key=lambda kv: sum(kv[1]), reverse=True):
    total = sum(vals)
    print(f"{city:<8} total {total:>6,}  avg {total / len(vals):>7,.0f}  share {total / grand:.1%}")

Challenge 5: Clean phone numbers

Normalise a list of Pakistani mobile numbers to the form 03XXXXXXXXX, and report any that are invalid after cleaning (must be 11 digits starting with 03). Watch the last test number. It's a Lahore landline, and a good cleaner should reject it rather than mangle it.

Solution

def clean_phone(raw):
    digits = "".join(ch for ch in raw if ch.isdigit())
    if digits.startswith("92"):
        digits = "0" + digits[2:]
    return digits if len(digits) == 11 and digits.startswith("03") else None

raw_numbers = ["0300-1234567", "+92 321 7654321", "0345 12345", "92-333-9876543", "042-35761234"]
for raw in raw_numbers:
    cleaned = clean_phone(raw)
    print(f"{raw:<18} -> {cleaned or 'INVALID'}")

Challenge 6: Overdue invoices

Given invoices as (ref, issued_iso, amount) with 30-day terms, list those overdue on 2026-09-24 with days overdue and a late fee of Rs 50 per day after the first three days.

Solution

from datetime import date, timedelta

today = date(2026, 9, 24)
invoices = [("INV-201", "2026-06-30", 45000), ("INV-214", "2026-08-20", 12500),
            ("INV-230", "2026-09-15", 78000), ("INV-233", "2026-08-01", 6000)]

for ref, issued, amount in invoices:
    due = date.fromisoformat(issued) + timedelta(days=30)
    late = (today - due).days
    if late > 0:
        fee = max(0, late - 3) * 50
        print(f"{ref} due {due}  {late:>3} days late  fee Rs {fee:,}")

Challenge 7: CSV summary with bad rows

Write a CSV with a few rows including two invalid amounts, then read it back and print the total of valid rows and a list of the rejected rows.

Solution

import csv

with open("orders.csv", "w", newline="", encoding="utf-8") as f:
    w = csv.writer(f)
    w.writerow(["id", "city", "amount"])
    w.writerows([[1, "Lahore", "2500"], [2, "Multan", "N/A"], [3, "Karachi", "9100"], [4, "Lahore", ""]])

total, rejected = 0.0, []
with open("orders.csv", newline="", encoding="utf-8") as f:
    for row in csv.DictReader(f):
        try:
            total += float(row["amount"])
        except ValueError:
            rejected.append(row)
print(f"Total Rs {total:,.0f}; rejected {len(rejected)} rows: {[r['id'] for r in rejected]}")

Tier 3: Build it

Challenge 8: Inventory class

Build an Inventory class with add(sku, qty, price), sell(sku, qty) (raising an error if stock is insufficient), value() and a __len__. Demonstrate it, including a rejected sale.

Solution

class OutOfStock(Exception):
    pass

class Inventory:
    def __init__(self):
        self._items = {}                          # sku -> {"qty": int, "price": float}

    def add(self, sku, qty, price):
        item = self._items.setdefault(sku, {"qty": 0, "price": price})
        item["qty"] += qty
        item["price"] = price

    def sell(self, sku, qty):
        item = self._items.get(sku)
        if item is None or item["qty"] < qty:
            raise OutOfStock(f"{sku}: have {item['qty'] if item else 0}, need {qty}")
        item["qty"] -= qty
        return qty * item["price"]

    def value(self):
        return sum(i["qty"] * i["price"] for i in self._items.values())

    def __len__(self):
        return len(self._items)

inv = Inventory()
inv.add("NB-01", 40, 250)
inv.add("PN-07", 100, 40)
print("Sold for", inv.sell("NB-01", 5))
try:
    inv.sell("PN-07", 500)
except OutOfStock as e:
    print("Rejected:", e)
print(len(inv), "SKUs worth Rs", f"{inv.value():,.0f}")

Challenge 9: sqlite3 mini report

Load the sales rows from Challenge 4 into an in-memory SQLite table and answer with SQL: total per city, the single largest sale, and the number of sales above the overall average.

Solution

import sqlite3

rows = [("Lahore", 2500), ("Multan", 1800), ("Lahore", 3200), ("Karachi", 9100),
        ("Lahore", 1500), ("Karachi", 2700), ("Multan", 8400)]
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE sales (city TEXT, amount REAL)")
con.executemany("INSERT INTO sales VALUES (?, ?)", rows)

for city, total in con.execute("SELECT city, SUM(amount) FROM sales GROUP BY city ORDER BY 2 DESC"):
    print(f"{city:<8} {total:,.0f}")
print("Largest:", con.execute("SELECT city, MAX(amount) FROM sales").fetchone())
print("Above average:", con.execute(
    "SELECT COUNT(*) FROM sales WHERE amount > (SELECT AVG(amount) FROM sales)").fetchone()[0])

Challenge 10: JSON transaction audit

Parse a JSON array of mobile-wallet transactions and flag any account that either sent more than Rs 50,000 in total or made more than two transactions between 00:00 and 05:00. Output the flags as JSON. This is a simplified version of a real compliance rule, and the shape (accumulate per account, then test thresholds) is one you'll write again and again.

Solution

import json
from collections import defaultdict

feed = json.loads('''[
 {"from": "0300111", "amount": 20000, "time": "2026-09-24T01:10:00"},
 {"from": "0300111", "amount": 35000, "time": "2026-09-24T13:00:00"},
 {"from": "0321222", "amount": 900,   "time": "2026-09-24T02:00:00"},
 {"from": "0321222", "amount": 1200,  "time": "2026-09-24T03:30:00"},
 {"from": "0321222", "amount": 700,   "time": "2026-09-24T04:45:00"},
 {"from": "0333444", "amount": 4000,  "time": "2026-09-24T11:00:00"}]''')

sent = defaultdict(float)
night = defaultdict(int)
for t in feed:
    sent[t["from"]] += t["amount"]
    if int(t["time"][11:13]) < 5:
        night[t["from"]] += 1

flags = []
for acct in sent:
    reasons = []
    if sent[acct] > 50000:
        reasons.append("high volume")
    if night[acct] > 2:
        reasons.append("night activity")
    if reasons:
        flags.append({"account": acct, "sent": sent[acct], "night_txns": night[acct], "reasons": reasons})

print(json.dumps(flags, indent=2))

Friday afternoon at the fintech: The night-activity flag list from Challenge 10 is, give or take a few columns, what that new analyst handed to the compliance team at the end of week one. They still receive it daily. Nothing in it goes beyond what you've just practised.

Where to go next

Most analytics tasks break down into parse, clean, group, aggregate, report. Functions and classes keep each step testable, and exceptions keep bad rows from stopping the run. Counter, defaultdict, datetime, csv, json and sqlite3 cover a remarkable amount without a single install. When you compare your solution with the reference, ask whether yours is clearer, safer or faster, and improve one thing. Then take the CSA SQL track, and come back to the pandas lessons with a real dataset of your own.

Stretch tasks

  1. Extend Challenge 7 to also write the rejected rows to rejected.csv with a reason column.
  2. Combine Challenges 5 and 10: clean the from numbers first, so the same account written two ways is aggregated together.
  3. Rewrite Challenge 4 as a Report class with add(city, amount) and a __str__ that prints the table, then feed it the rows from Challenge 9 via SQL.

Lesson 26 of 26

Sign in to track your progress and earn learning points for every lesson you finish.

Example

print("Hello from CSA!")

for i in range(5):
    print(i * i)
Try it Yourself »