A collection of small Python exercises and mini-projects, built to strengthen fundamentals through deliberate practice rather than tutorial-following. Each project lives in its own folder and targets a specific set of concepts.
| Project | Concepts practiced |
|---|---|
| rock-paper-scissors | functions, dictionaries, exception handling, loops, strict typing (Literal, tuple, dict generics) |
| heads-or-tails | functions, dictionaries, exception handling, binary comparison logic, strict typing |
| atm-simulator | OOP (classes, encapsulation, object references), custom exceptions, match statements, forward references, multi-class design (User, Account, Transaction, Bank, Auth) |
| api-calls | async/await, httpx sync vs async requests, asyncio.gather, mocking HTTP calls in tests |
| personal-assistant | speech recognition, text-to-speech, local LLM integration (Ollama via httpx), conversation state, third-party library type-stub limitations |
More exercises will be added as I work through core Python and into FastAPI fundamentals.
A command-line rock-paper-scissors game against the computer, with score tracking across rounds.
- Functions and separation of concerns (input, computer choice, win/lose logic, game loop)
- Dictionaries for clean lookups instead of hardcoded index-matching or long if/elif chains
- Exception handling (
KeyError) for invalid user input, with a retry loop instead of crashing or silently failing whileloops for both input validation and the main game loop- Strict typing with
Literaland typed tuples/dicts, checked withmypy
uv run python rock-paper-scissors/rock_paper_scissors.pyYou'll be prompted to enter r, p, or s each round. Score is
tracked and shown after every round; type n when asked to play
again to exit.
A command-line coin-flip game against the computer, with score tracking across rounds.
- Same core shape as rock-paper-scissors (input, random outcome, comparison, scoring loop), applied to a simpler binary case
- Direct equality comparison instead of a win/lose lookup table, since a coin flip has no "beats" relationship
- Strict typing with
Literalfor both game outcomes and coin sides
uv run python heads-or-tails/heads_or_tails.pyYou'll be prompted to enter h or t each round. Score is tracked
and shown after every round; type n when asked to play again to exit.
A command-line ATM simulator with login, balance inquiry, cash withdrawal, and transaction history, built around a proper multi-class design instead of dictionaries.
- Real OOP design:
User,Account,Transaction,Bank, andAuthas distinct classes with clear responsibilities, instead of dicts and loose functions - Object references between classes (
Accountholds a realUserreference;Transactionholds a realAccountreference) instead of manually-resolved IDs - Custom exceptions (
InsufficientFundsError) alongside built-ins (ValueError), and catching them at the right layer — where the program can respond meaningfully, not where the error originates matchstatements for menu handling- Forward references (
"Account"as a string type hint) for classes that reference each other before both are fully defined - A
Bankclass as the central ledger, aggregating transactions across all accounts rather than each account tracking its own history
uv run python atm-simulator/atm_simulator.pyLog in with one of the seeded users (amir/1122 or ali/4455),
then use the menu to check balance, withdraw cash, or view
transaction history.
A comparison of synchronous vs. concurrent API calls, fetching live cryptocurrency prices from the CoinGecko public API.
async/awaitsyntax and semantics — the difference between non-blocking (single sequentialawait) and truly concurrent (asyncio.gather) execution- Real I/O-bound waiting vs. CPU-bound work, and why async only helps the former
httpxfor both sync (httpx.get) and async (httpx.AsyncClient) HTTP requests- Testing async code with
pytest-asyncio, and mocking HTTP calls with test doubles instead of hitting a real API in tests
uv run python api-calls/api_calls.pyPrints elapsed time for fetching 5 coin prices sequentially vs. concurrently, showing the real-world speedup from overlapping network waits.
A wake-word-activated voice assistant: listens for a trigger word, executes simple commands (opening websites), and falls back to a local Ollama LLM for open conversation, with speech-to-text and text-to-speech.
- Integrating multiple third-party libraries (
speech_recognition,pyttsx3) around a core loop - Reusing the async/
httpxknowledge from api-calls to call a local LLM API (Ollama) for conversation - Command routing via dictionary lookup (same pattern as earlier
games) instead of long
if/elifchains - Maintaining conversation state across turns (message history sent to the LLM)
- Handling a real limitation of typed tooling: some third-party
libraries ship no type stubs, requiring targeted
mypyconfig overrides rather than code changes
uv run python personal-assistant/personal_assistant.pyRequires a local Ollama instance running with a pulled model. Say the wake word to activate, then speak a command or question; say the quit phrase to stop listening for commands.