Understand the Problem
Before writing any code, I ask: who is this for, what problem does it solve, and how will we know it's working? A clear problem statement prevents building the wrong thing beautifully.
Engineering isn't just about writing code : it's about solving the right problems. I approach every project with a product mindset: understand the user, measure what matters, iterate based on evidence, and ship what people actually need.
Before writing any code, I ask: who is this for, what problem does it solve, and how will we know it's working? A clear problem statement prevents building the wrong thing beautifully.
The best features are the ones you don't build. I identify the smallest possible version that validates the hypothesis, ship it, and iterate. Every project in this portfolio started as a single-feature MVP.
"Done" isn't when the code ships : it's when we confirm it works for users. I instrument features with analytics, monitor error rates, and track adoption to verify assumptions.
Product thinking isn't a separate skill from engineering : it's how I decide what to engineer. Every technical decision has a product implication:
I treat analytics as a product feature, not an afterthought. Every user-facing project includes instrumentation that answers specific questions about how people use the software.
| Metric | Why It Matters | Example |
|---|---|---|
| Page load time | Performance directly impacts bounce rate | Target <1.5s LCP across all pages |
| Feature adoption | Measures whether users discover and use features | % of GlobeScraper users who save searches |
| Error rate by endpoint | Surface broken flows before users report | API 5xx rate per route, target <0.1% |
| Uptime | Availability is the most basic product promise | 99.5% measured by Server Pulse every 60s |
| PII / personal data | Privacy first : never collect what you don't need | No cookies, no IP logging, no user tracking |
import structlog logger = structlog.get_logger() def track_feature_usage( feature: str, action: str, duration_ms: float | None = None, ) -> None: """Log a feature usage event. Events are structured JSON : no PII, no cookies. Downstream analytics ingest from log files. """ logger.info( "feature.used", feature=feature, action=action, duration_ms=duration_ms, ) # Usage in a route handler: @app.post("/api/search") def search(query: SearchRequest): start = time.monotonic() results = perform_search(query) elapsed = (time.monotonic() - start) * 1000 track_feature_usage("search", "execute", elapsed) return results
GlobeScraper started as a single Python script that scraped one property site. Here's how product thinking shaped its evolution into a full-featured application.
Single-file script, one data source, CSV output. Hypothesis: "Can I reliably scrape rental listings?" Result: Yes : ~95% data accuracy, but CSV was unusable at scale.
Migrated to SQLite, added deduplication, basic web UI. Hypothesis: "Will structured storage enable useful queries?" Result: Yes : filtering by region, price range, and date became trivial.
Added second data source, Pydantic validation, error handling. Hypothesis: "Can the architecture support multiple scrapers?" Result: Plugin pattern worked. Data quality improved 40% with validation.
Dockerised, CI/CD, monitoring, rate limiting, proper error pages. Hypothesis: "Is this reliable enough for daily use?" Result: Running continuously for 6+ months with 99.5% uptime.
Before starting any feature, I plot it on a simple 2×2 matrix. High-impact, low-effort items ship first. High-effort, low-impact items get questioned : do we really need this?
| Low Effort | High Effort | |
|---|---|---|
| High Impact | Do First | Plan Carefully |
| Low Impact | Quick Wins | Avoid |
A feature is done when it meets all of these criteria : not just the first one: