Case study · Technical Founder / CTO
Renovate
Rethinking how renovation projects are planned, organised and managed.
Trust, and how quickly help arrives
Renovate connects homeowners with verified tradespeople — plumbers, electricians, builders, and other trades. The two things people consistently find hardest about hiring one are knowing who to trust, and knowing how quickly they can get help.
So the product supports both a fast, urgent path and a considered one: describe a job and have it go out to nearby available tradespeople, first to accept gets it — or browse profiles, portfolios and reviews and book someone specific. Tradespeople go through a verification step before they can take jobs at all.
The full path, not just the code
Technical Founder / CTO
Engineering
- Architecture
- Backend services
- APIs
- iOS development
- Infrastructure
Product
- Translating product ideas into technical systems
- Evaluating feature feasibility
- Making engineering trade-offs
Leadership
- Managing developers
- Reviewing technical work
- Interviewing / hiring web & mobile interns
- Organising development
Legal
- Creating an accurate privacy policy
- Refining the terms of service
- Being specific on the way we will use our users data
How the pieces fit together
Described at a level that’s accurate but keeps the codebase confidential — no data models, business logic, or source code.
Why it stops here
Renovate is closed-source and still operating, so this page intentionally stops at the shape of the system rather than its implementation — no data models, security rules, API contracts or source code. Every Firebase-backed service sits behind a protocol with an in-memory mock, which is what lets the app run and preview fully UI-complete with no backend connected. Real-time features (chat, live job status, dashboards) are powered by Firestore listeners through Combine; anything that has to be server-authoritative — notifications, status transitions, the parts a client shouldn’t be trusted with — runs in Cloud Functions instead. Happy to talk through any of the reasoning in more depth in person.
The choices worth writing down
Five decisions that shaped the system, explained in enough depth to show the reasoning — without the implementation, data models, or code behind them.
Authentication & verification
Two very different user types — homeowners and tradespeople — need different levels of trust before either can act.
Build identity and credential storage from scratch, or use a managed identity provider and put the real trust decision elsewhere.
Firebase Authentication for identity, with a multi-step verification flow gating tradespeople before they can receive job requests.
Managed auth removes an entire class of security work that isn’t the interesting part of the problem — the verification step is where the product actually earns trust.
Verification status has to be checked consistently everywhere a tradesperson can act, not only at sign-up — easy to miss a spot if you’re not disciplined about it.
Proximity search
Match a job to nearby available tradespeople without scanning every tradesperson on every request.
Pull a broad set of candidates and filter by distance client-side, or narrow the set at the database level first.
Geohash-indexed location data in Firestore, queried by range, with a precise distance check applied afterward.
Firestore has no native geo-query support; geohashing is the standard way to get range-queryable proximity search out of a document database.
A geohash range query returns an approximate bounding region, not an exact radius — there’s always a second, more precise filtering pass on top.
Mobile architecture
Build and iterate on UI quickly without every screen depending on a live backend connection.
Call Firebase directly from views and view models, or put an abstraction layer in front of it.
MVVM with a protocol-based service layer — every Firebase-backed service sits behind a protocol with an in-memory mock implementation.
The app compiles, runs and previews fully UI-complete with no backend linked, which makes UI work and design iteration considerably faster.
An extra layer to maintain, and the mocks have to be kept honest or they quietly drift from what the real service does.
Real-time data & server logic
Some things (chat, live job status) need to update instantly; others (side-effects that must be trustworthy) can’t be left to the client.
A traditional REST/GraphQL API in front of the database, or clients reading the database directly with server logic only where it must be authoritative.
Firestore real-time listeners (via Combine) power the client directly; Cloud Functions in TypeScript handle the logic that has to be server-authoritative.
Far less backend surface to build and operate than a full API layer, while keeping the parts that matter off the client.
Logic ends up split between client-observed data and server-side functions, which takes discipline to keep consistent.
Infrastructure & environments
Iterate on the product without risking production data or real users.
Share one backend project across development and production, or separate them.
Separate Firebase environments for development and production, with environment-specific configuration.
Safe to build and break things in development without touching real users or data.
Extra configuration to keep in sync across environments as the app grows.
A real product is not coursework
Technical decisions have consequences that show up much later.
Architecture evolves — the first version is never the last.
Product constraints end up shaping engineering decisions as much as the other way round.
Reliability stops being optional the moment other people use the software.
Leading engineers is a different job from writing the code yourself.
The technically elegant solution isn’t always the most useful product solution.
Shipping forces you to prioritise, whether you want to or not.