Define the APIs expected from the system. This is your chance to analyze and define the read and write paths so that you can come up with the high-level design...
GET /v1/stream/songs/{songId}?quality={bitrate}
Returns a response containing a signed CDN URL for the audio file, song metadata (title, artist, album art URL, duration), and the available bitrate options. The signed URL contains an HMAC signature, expiry timestamp (15 minutes), and the user's nearest CDN region. The client uses this URL to fetch audio chunks directly from the CDN, the backend never serves audio bytes.
Why return a signed URL instead of proxying audio through the API? Because proxying 1.28 Tbps of audio through backend servers would require thousands of servers just for bandwidth. Signed URLs offload delivery to the CDN while preventing unauthorized access (the URL expires and is tied to the user's session).
POST /v1/events/play
json
{
"songId": "song_abc",
"durationPlayedMs": 45000,
"context": "playlist:pl_xyz",
"timestamp": "2026-03-01T12:00:00Z"
}
Fires after 30 seconds of playback (the industry-standard threshold for counting a "stream" for royalty purposes). Fire-and-forget from the client's perspective, the API returns 202 Accepted immediately and publishes to Kafka asynchronously. If the client can't reach the API, it queues the event locally and retries on next connectivity.
Playlist API:
POST /v1/playlists: Create a new playlist (name, description, public/private).
PUT /v1/playlists/{playlistId}/songs: Add or remove songs. Body contains an array of operations: [{action: "add", songId: "...", position: 5}, {action: "remove", songId: "..."}]. Supports batch operations to reduce round trips.
PATCH /v1/playlists/{playlistId}/songs/reorder: Move a song from one position to another. Uses optimistic concurrency: request includes expectedVersion, server rejects with 409 if the playlist was modified since.
GET /v1/recommendations?seed_songs={ids}&seed_artists={ids}&limit=30
Returns personalized song recommendations. Seeds are optional, if provided, recommendations are biased toward similar content. Without seeds, the system uses the user's full listening profile. Response includes a reason field explaining why each song was recommended ("Because you listened to Artist X", "Popular in your area").
Describe the overall system architecture. Identify the main components needed to solve the problem end-to-end. Use the diagramming tool to create a block diagram.
Client apps (mobile, web, desktop): Handle audio playback, local caching for offline mode, UI rendering, and adaptive bitrate switching. The client is "smart", it manages its own playback buffer, prefetches upcoming songs, and queues play events for retry if offline. This pushes complexity to the client and keeps backend services stateless.
API Gateway: Single entry point for all client requests. Handles JWT authentication, rate limiting (token bucket per user), request routing to backend services, and TLS termination. Returns cached responses for hot endpoints (trending playlists, popular searches). Implemented with Nginx or AWS ALB + custom auth middleware.
Streaming Service: Receives play requests, validates the user's subscription (can they stream 320kbps?), fetches song metadata from PostgreSQL, generates a signed CDN URL, and returns it. Stateless, scales horizontally. Does NOT serve audio bytes.
Search Service: Thin layer over Elasticsearch. Handles query parsing (tokenization, typo correction), executes multi-field search queries, applies personalization re-ranking (boost songs by artists the user has listened to), and returns paginated results. Caches popular queries in Redis (TTL 5 minutes).
Recommendation Engine: Serves pre-computed recommendations (Discover Weekly, Daily Mix) from a recommendation cache. For real-time requests, combines collaborative filtering embeddings with content-based features to generate on-demand suggestions. Models trained offline on Spark; serving layer is a lightweight API reading from a vector store. Cold-start users (new accounts with no listening history) receive country-specific popular playlists and genre-based suggestions from the onboarding flow (the user picks 3+ artists they like on signup); as listening data accumulates over the first few sessions, the system transitions to personalized recommendations.
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
| Table | Key Columns | Notes |
| songs | song_id (PK), title, album_id (FK), duration_ms, audio_key, genre, play_count | audio_key = S3 object key prefix |
| artists | artist_id (PK), name, bio, image_url, monthly_listeners | |
| song_artists | song_id + artist_id (composite PK), role | Many-to-many; role: primary, featured, producer |
| albums | album_id (PK), title, artist_id (FK), cover_url, album_type | album_type: album, single, EP |
| users | user_id (PK), email (UNIQUE), display_name, plan_type, country | plan_type: free, premium, family |
| playlists | playlist_id (PK), owner_id (FK), title, is_public, version, song_count | version for optimistic concurrency |
| playlist_songs | playlist_id + song_id (composite PK), position, added_at | position enables user-defined ordering |
| play_events | event_id (PK), user_id, song_id, duration_ms, context | Append-only, partitioned by month |
| follows | follower_id + followed_id (composite PK) | Social graph |
PostgreSQL for songs, artists, albums, users, playlists, these are relational data with complex joins (song → album → artist, playlist → songs). Read replicas handle the 200K metadata reads/sec. The total structured data is ~500GB, well within a single PostgreSQL cluster with partitioning.
Elasticsearch for search, the songs table is replicated to an ES index with analyzers for autocomplete (edge n-grams), typo tolerance (fuzzy matching), and multi-field search. ES is not the source of truth; PostgreSQL is. A CDC (Change Data Capture) pipeline keeps ES in sync.
S3 (or equivalent object storage) for audio files, each song stored as {song_id}/{bitrate}.opus (e.g., song_abc/128.opus). Object storage is the only cost-effective option at 3PB. Files are immutable once written.
Kafka for play events, the append-only event stream handles 48K writes/sec. Events are consumed by multiple downstream systems (analytics, recommendations, royalties). Retention: 7 days in Kafka, then archived to Parquet on S3.
Redis for session state, active stream registry (which user is streaming on which device), rate limiting counters, and cached recommendation results. Total Redis footprint: ~50GB (10M active sessions × 500 bytes + caches).
Event Pipeline (Kafka): Ingests play events, playlist edits, search queries, and other user actions. The primary play-events topic is partitioned by song_id for efficient per-song aggregation (royalty calculation, play counts). A secondary topic re-partitioned by user_id supports user-level analytics and recommendation retraining. Multiple consumer groups: analytics aggregator, recommendation retrainer, royalty calculator, fraud detector (detecting play count manipulation).
Data stores: PostgreSQL (metadata), Elasticsearch (search), S3 (audio files), Redis (sessions, caches), Kafka (events). Each chosen for its strength: PG for consistency, ES for search, S3 for scale, Redis for speed, Kafka for throughput.
CDN: Globally distributed edge nodes serving audio chunks. Pre-warms popular content based on regional popularity data. Handles 95%+ of streaming bandwidth, insulating the origin from traffic spikes. Multi-CDN strategy for resilience.
End-to-end song playback: signed URL generation, CDN chunk delivery, and event logging
GET /v1/stream/songs/{songId}?quality=128 to API Gateway.SELECT audio_key, duration_ms FROM songs WHERE song_id = $1. Checks if requested quality is within user's plan limit.https://cdn.example.com/{audio_key}/128.opus?sig={hmac}&exp={timestamp}. Signs with HMAC-SHA256 using a shared secret between backend and CDN.{url: "...", duration_ms: 210000, title: "...", artist: "...", albumArt: "..."}.POST /v1/events/play with duration, context, and timestamp. Gateway publishes to Kafka topic play-events.GET /v1/search?q=bo&autocomplete=true.GET /v1/search?q=bohemian+rhapsody&type=songs.PUT /v1/playlists/{id}/songs with operations array and expectedVersion: 42.version = 42 in playlists table. If mismatch, return 409.song_count by 5. Set version = 43.playlist_updated event to WebSocket channel. Other collaborators' clients receive the delta and update their local view.