List the key functional requirements for the system (Ask the AI for hints if stuck)...
List the key non-functional requirements (performance, scalability, reliability, etc.)...
Estimate the scale of the system. Consider daily active users, read/write ratio, storage requirements, bandwidth, and any relevant QPS calculations...
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...
{
"name": "Weather Pro",
"description": "Real-time weather forecasts...",
"category": "weather",
"priceCents": 0,
"screenshots": [
"url1",
"url2"
],
"minOsVersion": "14.0",
"supportedDevices": [
"phone",
"tablet"
]
}
Returns 201 with appId and a presigned S3 URL for binary upload. The client uploads the binary directly to S3 (not through our API servers) to avoid bottlenecking the API layer with 100 MB+ file transfers. After upload completes, the client calls POST /v1/apps/:appId/submit to trigger the review pipeline.
GET /v1/apps/:appId/status. Returns current review status: uploaded, scanning, in_review, approved, rejected. If rejected, includes rejectionReasons array with specific policy violations.
GET /v1/apps/search?q=weather&category=utilities&minRating=4&sort=relevance&page=1
Returns paginated results with app name, icon, rating, download count, price, and a snippet of the description. The sort parameter accepts relevance, rating, downloads, and newest. Relevance ranking combines text match score, download velocity, and rating.
GET /v1/apps/:appId. Full app details including description, screenshots, version history, reviews summary (average rating, total count, distribution), size, permissions required, and compatibility info.
POST /v1/apps/:appId/download
json
{
"deviceId": "device_abc",
"osVersion": "16.2",
"currentVersion": null
}
Returns a signed CDN URL with a 15-minute expiry and the expected SHA-256 checksum. If currentVersion is provided, the server checks if a delta update is available and returns the delta URL instead. The signed URL prevents unauthorized sharing of download links.
POST /v1/apps/:appId/reviews
json
{
"rating": 4,
"title": "Great app, minor bugs",
"body": "Works well but crashes occasionally...",
"appVersion": "2.1.0"
}
Requires authenticated user who has downloaded the app. One review per user per app (updates are PUT). Returns 201 with reviewId.
POST /v1/apps/:appId/purchase
json
{
"paymentMethodId": "pm_xyz",
"idempotencyKey": "purchase_client_abc123"
}
Creates a payment intent, charges the user, records the entitlement, and returns a download token. The idempotencyKey prevents double charges on network retries.
PUT /v1/apps/:appId/versions/:versionId/rollout
json
{
"targetPercentage": 10,
"haltOnCrashRate": 0.02,
"monitoringWindowHours": 24
}
Developers configure staged rollout parameters. The platform automatically advances or halts the rollout based on crash rate telemetry during the monitoring window.
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.
The key architectural insight is that this system has two fundamentally different traffic planes that scale independently:
Metadata plane (high QPS, small payloads): search queries, app detail pages, review listings, catalog browsing. These are 1-10 KB responses served at 5,800 QPS. Optimized for low latency with aggressive caching.
Binary delivery plane (lower QPS, massive payloads): app downloads at 100-500 MB each. These generate 250 TB/day of egress. Optimized for throughput and cost via CDN edge caching.
Separating these planes means the search infrastructure can scale on CPU and memory (Elasticsearch query load) while the download infrastructure scales on bandwidth and CDN capacity, completely independent knobs.
API Gateway: Routes all client requests, handles JWT authentication, rate limiting (token bucket per user), and TLS termination. The gateway does not touch binary data. Download requests pass through to get a signed CDN URL, then the client downloads directly from the CDN.
App Catalog Service: Manages app metadata CRUD. Developers create and update app listings. Publishes metadata change events to Kafka for downstream consumers (search indexing, cache invalidation). Reads from PostgreSQL, writes through cache.
Submission Service: Handles the app review pipeline. Receives binary upload notifications from S3, triggers automated security scans (malware, permission audit, API compliance), queues for human review, and publishes approval/rejection events. CPU-intensive scanning runs on dedicated worker nodes.
Search Service: Wraps Elasticsearch with ranking logic. Combines text relevance, download velocity (trending signal), rating, and category matching into a composite score. Caches popular queries in Redis (5-minute TTL).
Download Service: The platform's most critical service. Validates user entitlement (free app or purchased), evaluates rollout rules (which version this user should receive), generates time-limited signed CDN URLs, and records download events. Stateless, horizontally scalable.
Review Service: Manages user reviews and ratings. Writes to MongoDB, publishes rating change events to Kafka. Maintains aggregated rating stats (average, distribution) in Redis for fast reads. Anti-abuse logic: requires app installation, one review per user.
Payment Service: Integrates with Stripe for payment processing. Handles charges, refunds, and developer payouts. Uses idempotency keys for retry safety. Records all transactions in PostgreSQL for audit and financial reporting.
Rollout Service: Manages staged rollouts for new app versions. Uses deterministic hashing (hash of userId + appId) to assign users to rollout buckets consistently. Monitors crash rate telemetry and automatically halts rollouts that exceed the threshold.
CDN (CloudFront/Akamai): The backbone of the binary delivery plane. Edge nodes in 200+ locations cache app binaries close to users. Cache-hit rate target: 99%+ for popular apps. Origin: S3 buckets with versioned binaries.
Kafka Event Bus: Decouples services. Key topics: app-submissions (new uploads), app-approved (review results), metadata-changes (search reindexing), downloads (analytics), payments (financial events). Enables async processing without blocking the API request path.
Define the data model. Identify the main entities, their attributes, and relationships. Consider the choice of database type (SQL vs NoSQL) and justify your decision based on access patterns...
| Table | Key Columns | Notes |
| apps | app_id (PK), developer_id (FK), name, description, category, status, price_cents, created_at | Core app metadata. Status: draft, in_review, published, suspended |
| app_versions | version_id (PK), app_id (FK), version_string, binary_s3_key, binary_size, sha256_checksum, code_signing_cert_id, rollout_percentage, status | Each upload creates a new version. S3 key points to the binary |
| developers | developer_id (PK), email, org_name, certificate_id, payout_account, status | Developer accounts with code signing certificates |
| purchases | purchase_id (PK), user_id (FK), app_id (FK), amount_cents, platform_fee_cents, idempotency_key (UNIQUE), status, created_at | ACID transactions for financial records |
| reviews | review_id, app_id, user_id, rating, title, body, app_version, created_at | Stored in MongoDB for flexible schema and high write throughput |
| users | user_id (PK), email, name, device_ids (JSONB), platform, created_at | User accounts. device_ids tracks registered devices for entitlement sync |
| entitlements | entitlement_id (PK), user_id (FK), app_id (FK), source_type, source_id, granted_at, revoked_at | Single source of truth for app access. source_type: purchase, bundle, subscription, promo_code |
| certificates | cert_id (PK), developer_id (FK), public_key, fingerprint, issued_at, expires_at, revoked | Code signing certificate registry |
PostgreSQL for apps, versions, developers, users, purchases, entitlements, and certificates. These are relational data with ACID requirements. A purchase must atomically insert into purchases and entitlements in a single transaction, partial writes mean a user is charged without receiving the app (or gets the app without being charged). The entitlements table is the single source of truth for "can this user access this app?" regardless of how they acquired it (direct purchase, bundle, subscription, or promo code). Read replicas handle the 5,800 QPS read traffic.
MongoDB for reviews. Reviews are append-heavy, schema-flexible (some reviews have titles, some don't, future reviews might include photos), and tolerate eventual consistency. A review appearing 5 seconds late is invisible to users. MongoDB's document model stores the complete review (text, rating, developer response, helpful votes) in a single document, avoiding joins.
S3 for app binaries, screenshots, and icons. Versioned buckets store every uploaded binary for rollback support. Lifecycle policies move versions older than 90 days to S3 Infrequent Access (40% cheaper). Binaries are immutable once uploaded, simplifying caching.
Elasticsearch for search. Indexes app name, description, category, tags, and developer name. Supports full-text search with relevance scoring, category facets, and rating range filters. Updated asynchronously via Kafka when app metadata changes.
Redis for caching and rate limiting. Caches popular app metadata (cache-aside pattern with 5-minute TTL), search results for common queries, and download entitlement checks. Also stores rate limiting counters and rollout percentage assignments (deterministic hash mapping).
Deep dive into 2-3 key components. Explain how they work, how they scale, discuss tradeoffs, capacity, and any relevant algorithms or data structures.
Code signing flow: developer signs with private key, platform verifies with certificate, client re-verifies on install
Code signing is the mechanism that lets a user's device trust that an app binary is authentic and unmodified. It uses asymmetric cryptography: the developer signs the binary with their private key, and anyone with the developer's public certificate can verify the signature.
The signing process:
The verification process (both platform and client):
Why is this different from HTTPS? HTTPS proves the CDN node you connected to is legitimate (server authentication). Code signing proves the binary itself was created by the claimed developer. These solve different problems:
Think of it this way: HTTPS is like a sealed envelope delivered by a trusted courier. Code signing is the letter inside being written in the sender's unmistakable handwriting. The envelope protects the letter during delivery, but the handwriting proves who wrote it.
Key Insight
HTTPS and code signing solve different trust problems. HTTPS proves you are talking to the real CDN. Code signing proves the binary was created by the real developer. A compromised CDN serving a tampered binary passes HTTPS validation (the CDN is legitimate) but fails code signing verification (the binary was modified after the developer signed it).
Staged rollout: 1% canary, crash monitoring, progressive expansion to 100%, with automatic halt on regression
When a developer publishes a new version, it does not immediately go to all users. The platform uses staged rollout to gradually increase the percentage of users who receive the new version, monitoring crash telemetry at each stage.
Rollout stages: 1% → 10% → 50% → 100%
Deterministic user bucketing: The Rollout Service computes hash(userId + appId) % 100. If the result is less than the current rollout_percentage, the user gets the new version. This hash is deterministic: the same user always gets the same result, so they do not flip between versions on successive downloads.
Crash monitoring: Devices report crash events to a telemetry endpoint. The Rollout Service compares the crash rate of the new version (within the canary group) against the previous version's baseline crash rate. If the new version's crash rate exceeds the baseline by more than 2x (configurable via haltOnCrashRate), the rollout is automatically halted.
What "halt" means: The rollout_percentage is frozen. Users already on the new version stay on it (forcing a downgrade is disruptive). New users and users who haven't updated yet continue receiving the old version. The developer is notified to investigate and either fix the issue (push a new version) or request a manual rollback.
Why not skip staged rollout for small patches? A "small" patch can still contain a critical regression. At 50M DAU, even a 1% crash rate affects 500,000 users. A 1% canary catches this at 5,000 users (1% of the 1% who actually trigger the bug), giving the developer time to react before the impact scales. Skipping staged rollout is skipping your seatbelt because you are only driving to the corner store.
Common Pitfall
Never skip staged rollout, even for patches. A 1% canary group at 50M DAU means bugs are caught at 500K users instead of 50M. The developer who says their fix is too small to break anything is the developer whose fix breaks everything.
Not every device can run every app version. The platform must manage a compatibility matrix across three dimensions:
Edge case: no compatible version exists. A user on a very old OS version may have no compatible app version at all. The Download Service returns a 404 with a structured error: {"error": "incompatible_device", "min_os_required": "15.0", "user_os": "13.2"}. The client displays "This app requires iOS 15 or later" rather than a generic error. The search results still show the app but with a badge indicating the incompatibility.
Edge case: multi-architecture binaries. Some apps ship universal binaries (ARM64 + x86 in one package), while others ship split binaries for smaller download sizes. The app_versions table stores an architectures JSONB column. The Download Service matches the client's reported architecture against this list. If only a universal binary exists, serve that. If a split binary is available for the client's architecture, prefer it (smaller download). Fallback to universal if the specific architecture is missing.
The Download Service resolves this by querying app_versions with filters: WHERE app_id = ? AND min_os_version <= ? AND architecture_match(architectures, ?) AND status = 'published' ORDER BY created_at DESC LIMIT 1. This returns the latest published version compatible with the user's device. Combined with rollout evaluation, the query becomes: find the latest version where the user is within the rollout percentage AND the version is compatible with the device.