← ALL PROJECTS

PROJECT 005 · CASE STUDY

Goodpods

LIVEEngineering lead2026-05-15 · REACT-NATIVE · REACT · TYPESCRIPT · NEXTJS · PYTHON · FASTAPI · POSTGRES · ELASTICSEARCH · GCP
VISIT LIVE ↗

Podcast discovery and social listening for 120k active users. I lead engineering across the mobile apps, the web app, and the platform behind them.

Problem

Podcast listening is solitary by default. The big apps are built for playback, not for finding out what the people you trust are actually listening to. Goodpods set out to fix that: a podcast player where discovery is social, so you follow people rather than an algorithm.

The engineering problem underneath is that “social podcast app” means running a full podcast platform (ingesting and indexing RSS feeds, resolving episodes, tracking playback) and a full social product (follows, activity feeds, groups, comments, leaderboards) at the same time, across iOS, Android, and the web, with a small team.

What I built

I’ve led engineering across the stack since 2020, when I took over the original backend. That covers the React Native app that ships to both stores, the Next.js web app, and the Python backend that feeds them all.

  • The API layer: FastAPI on Cloud Run, stateless and autoscaled, with Postgres as the single source of truth and Redis in front of it for caching and rate limiting.
  • The ingestion side: background workers on Compute Engine that scrape podcast RSS feeds, resolve episodes, compress artwork, and fan out push and email notifications through a job queue.
  • Search: an Elasticsearch cluster indexed from Postgres that powers podcast, episode, and profile search.
  • The social layer: follows, activity feeds, groups, comments, and leaderboards, modelled relationally rather than bolted on.
  • Earshot, the newest product line: podcasters pay for AI-generated marketing material for their episodes. The pipeline transcribes an episode, generates copy and media through LLM APIs, and gates the results behind Stripe subscriptions and credits.

Decisions worth explaining

This is the part portfolio sites usually skip, so it gets the most room.

Postgres for the social graph, not a graph database. Follows and activity feeds are join-shaped queries over data we already had in Postgres. A second datastore would have bought us vocabulary, not capability, and doubled the operational surface for a small team. Indexes and careful query design have held up fine at 40M+ page views a month.

Rebuild the service, keep the schema. Six years of production data makes a big-bang rewrite a bad bet. When the original backend needed replacing, the new async service (SQLAlchemy 2.0, TaskIQ workers) was built around the existing production schema instead of a fresh one. New features like payments and Earshot land on the modern stack while the old API keeps serving traffic, and nothing has to be migrated under load.

React Native, honestly. One team ships two native apps and shares most of its brain with the web codebase. The cost is real: native module upgrades and the occasional platform-specific bug hunt. But for a product whose value is features shipped per week, it has paid for itself.

Own the boring infrastructure when vendors fail you. Podcast artwork compression went through two third-party vendors before we wrote our own task-based pipeline: one was too limited, the other priced for companies ten times our size. Same story with job monitoring, where the open-source dashboard we relied on was abandoned upstream, so we forked it and maintain it ourselves. Neither was glamorous work, but both removed a recurring source of pain.

Outcome

Goodpods is live on the App Store, Google Play, and the web, with 120k active users and 40M+ page views a month. The platform runs three environments, ships through CI on every merge, and gets load-tested with Locust before big releases.

Stack

React Native and TypeScript on mobile, Next.js on the web, FastAPI and Postgres behind them, Elasticsearch for search, Redis for caching and queues, all on GCP.

FROM THE FIELD NOTES · 20 LOGS

War stories

LOG 066 · 2025-12-01Shipped a paid AI product in under two weeks, then rebuilt it when the requirements grewBuilt the backend for Magic Marketing, a paid AI marketing tool for podcasters, as a fast MVP, then rewrote it as v2 when the client's feature requests outgrew the prototype architecture.LOG 054 · 2025-04-10Built a global IP rate limiter in a day to stop bot scrapingShipped a from-scratch IP rate limiter for the Goodpods API in one day to shut down aggressive bot scraping, catching a load balancer IP bug in development before it could throttle every real user.LOG 053 · 2025-02-01Cloud Run was exhausting our database connections, so I did the maths and fixed the root causeA production connection-exhaustion incident on Cloud Run: pool tuning bought time, then the worker-times-instances arithmetic pointed to the real fix with a read replica.LOG 045 · 2024-07-01Rewrote the hot-path feed query, twiceReplaced the exact-ranking feed query behind Goodpods' For You screen with a cheap sampled version, taking p50 latency from 1900ms to 110ms.LOG 044 · 2024-04-01Built a native audio engine from scratch in Swift and Android Media3As a web engineer I wrote a native podcast player for iOS and Android with queues, background playback and lock screen controls, and bridged it to our TypeScript app.LOG 042 · 2024-02-01Built offline listening: downloads plus an offline-first data layerBuilt episode downloads and a persistent React Query cache so the Goodpods app worked with no connection.LOG 041 · 2024-01-01Per-request monitoring at over 1M requests a day, without buying a monitoring productWith no observability budget, I built request monitoring into Redis for an API doing over 1M requests a day, then used it to find and fix the ten slowest endpoints.LOG 040 · 2024-01-01Replaced our React Native app by shipping the web app through CapacitorHow I turned one Next.js codebase into the iOS and Android apps for Goodpods and retired an entire React Native codebase.LOG 039 · 2023-12-01Instrumented listening in detail, then stopped the player losing your placeBuilt fragment-level listen analytics across the Goodpods app, then fixed the resume bugs that made the player jump back to the start, cutting support complaints to zero.LOG 038 · 2023-11-01Shipped prefetching, pulled it from prod the same day, then re-shipped it surgicallyMy first prefetch deploy maxed out the Goodpods backend's autoscaling, so I reverted it from production the same day and re-shipped a more surgical version.LOG 037 · 2023-10-01Rebuilt listen tracking around fragments without breaking old clientsMigrated Goodpods' core listen data to a fragment model that captured partial and resumed listens, kept old clients working via daily aggregation, and unlocked podcaster analytics and listen-time leaderboards.LOG 028 · 2023-09-01Making Next.js caching behave at a million requests a dayA multi-month fight to get stale-while-revalidate working on the Goodpods webapp without pages caching forever or deploys serving a stale app.LOG 020 · 2023-07-01Put the content in the first-load HTMLServer-rendering structured data and real page content took Goodpods from a few dozen organic clicks a week to over 1000.LOG 019 · 2023-07-01Shipped curated lists in ten days and users made 1,000 of themBuilt the curated lists feature across the Goodpods native app in about ten days; users created 1,000 lists within the first ten days after release.LOG 016 · 2023-03-01Took the webapp's Lighthouse score from the 20s to 95 for an SEO pushA five-month Core Web Vitals campaign on the Goodpods Next.js webapp that lifted Lighthouse from 20-30 to 95 and improved search rankings.LOG 015 · 2023-03-01A stale-while-revalidate cache took SSR pages from 2-5 seconds to 100msI designed a Redis stale-while-revalidate cache for Goodpods' SSR pages, gated to crawlers only, that cut response times from 2-5 seconds to around 100ms.LOG 005 · 2021-02-01Built Detox E2E tests on CI, then removed them when they stopped paying offAfter a team reduction left us without full-time QA, I set up Detox end-to-end tests on CI, ran them for eighteen months, then removed them when the evidence showed they were not worth their maintenance cost.LOG 004 · 2020-08-10Rebuilt the Goodpods API from Django to FastAPI, endpoint by endpointProposed and led a parallel rewrite of a failing Django backend to FastAPI, taking the platform from 50 to 1500 concurrent users.LOG 003 · 2020-08-01Moved all the slow, flaky work onto a background worker fleetReplaced a high-maintenance Celery setup with an RQ worker fleet on Redis, with priority queues and per-podcast fault tolerance, cutting notification-triggering actions from 5 seconds to 80ms.LOG 002 · 2020-07-20Replaced FileZilla-to-prod with tests, pipelines and near-daily deploysHow I took the Goodpods API from live-editing production over FileZilla and weekly outages to a tested, three-environment pipeline with near-daily deploys.