Stripe API Pagination, Expansion, And Rate Limits
Asked of: Software Engineer
Last updated
What's being tested
Candidates must show practical mastery of designing and using paginated REST APIs, the expansion pattern for reducing round-trips, and robust handling of rate limits in client and server designs. Interviewers probe system correctness (consistency and ordering), performance (latency and throughput), operational safety (retry/backoff, idempotency), and tradeoffs between simplicity and scale. At Stripe, this maps to reliable developer UX for high-volume clients and safe multi-tenant service operation.
Core knowledge
-
Pagination: know difference between cursor-based and offset-based pagination; cursor is stable and O(1) per page while offset degrades with large offsets (O(offset) in many DBs). Use cursor when N >> page_size.
-
Cursor semantics: a cursor should be an opaque, compact token (e.g., base64 of last-seen key + shard id + timestamp); it must encode sorting key and tie-breaker to guarantee deterministic paging.
-
Offset pitfalls:
OFFSETinPostgres/MySQLscans/skips rows; for deep pages preferWHERE (key, id) > (k, id) LIMIT Mindexed scans or keyset pagination. -
Consistent pagination: decide between snapshot isolation (stable view, heavier — e.g., timestamped snapshot or transaction id) and eventual view (reflects concurrent writes). Be explicit in API docs about staleness guarantees.
-
Expansion pattern: expansion (e.g.,
expand=customerinStripe API) inlines related resources to avoid N+1 client fetches; server must fetch efficiently (batch queries,IN (...)), enforce size limits, and document extra cost. -
Rate limiting models: know token bucket (smooth bursts) vs leaky bucket (steady rate). Implement counters in
Redisfor distributed enforcement and use sliding-window or fixed-window with jitter to reduce thundering herd. -
Client behavior on 429s: honor
Retry-Afterheader; use exponential backoff with jitter (e.g., base * 2^n + random) and respect idempotency for retries. Surface backoff info to users or observability. -
Idempotency: design write APIs to accept an idempotency key (e.g., UUID) and persist the result for at least the time window clients may retry; consider storage TTL and eviction semantics.
-
Operational headers & telemetry: expose
X-RateLimit-Limit/Remaining/Resetor similar; logp99latency, error rates, and 429 counts per client and per route to tune quotas. -
Performance tradeoffs: precomputing denormalized lists (cache) speeds page responses for heavy reads but complicates consistency; measure reads per second and eviction cost before denormalizing.
-
Security & quotas: apply per-API-key, per-account, and per-IP quotas. Differentiate authentication tiers (e.g., test vs live) and implement graceful degradation (soft limits, informative 429 payloads).
-
Backwards compatibility: expanding fields changes response size; version responses and cap total payload size to avoid breaking mobile clients.
Worked example — "Design a paginated API for listing transactions"
Start by clarifying semantics: ask whether ordering must be strictly stable (e.g., by created_at, id) and whether the client expects a consistent snapshot across pages. Declare assumptions: sort by created_at DESC, id DESC, eventual consistency acceptable, and page size default 50.
Organize the solution into pillars: API contract (query parameters limit, cursor; return fields data, has_more, next_cursor); storage access (use keyset pagination: WHERE (created_at,id) < (:cursor_created_at, :cursor_id) ORDER BY created_at DESC, id DESC LIMIT :limit with an index on (created_at, id)); and operational concerns (rate limiting, caching). Explain scaling: for low-latency, read-through Redis cache per-account for most recent pages; for deep scans use index-only scans or a streaming background process to materialize per-account lists.
Flag tradeoffs: cursor tokens are safe and efficient but require clients to hold state; snapshot guarantees require storing a snapshot id or using MVCC and increase complexity. For heavy multi-tenant load, per-account cursor state persisted in Redis reduces recomputation but needs eviction rules.
Close: note follow-ups — if more time, discuss prefetching next page, partial responses with expand controls, and monitoring metrics like page p99 and 429 rates to tune per-account quotas.
A second angle — "Implement a client library that handles rate limits and expansions"
Here the constraint is client-side: limited memory (mobile) and intermittent connectivity. The same concepts apply but framing shifts: the client should expose a high-level retry policy using exponential backoff with jitter, queue user-initiated requests and persist idempotency keys to local storage, and support lazy expansion (only expand when caller requests). For bandwidth constraints use If-None-Match/ETag or request small pages first and allow server-side expand hints. The client must surface Retry-After to the caller and offer cancellation hooks; on unstable networks, prefer idempotent operations and exponential backoff with capped retries.
Common pitfalls
Pitfall: Choosing
OFFSETpagination for large datasets.
UsingOFFSETfor deep pages leads to high DB CPU and unpredictable latency; interviewers will prefer keyset (cursor) pagination and ask how you handle ties.
Pitfall: Ignoring expansion cost and payload size.
Blindly allowing unlimited expansions can produce large responses and backend N+1 queries; always cap expansions server-side and batch related-fetches (SELECT ... WHERE id IN (...)).
Pitfall: Retrying non-idempotent writes without idempotency keys.
A tempting quick fix is naive retries on 5xx/429; a stronger answer explicitly requires idempotency keys and explains TTL/cleanup and semantic guarantees.
Connections
This area commonly pivots to API versioning (how changes to expanded fields affect clients), distributed caching (cache invalidation strategies for paginated lists), and observability/alerting (instrumenting 429 spikes and per-client throttling metrics). Interviewers may also ask about database indexing and query plans when you justify keyset vs offset.
Further reading
-
Stripe API Reference — Pagination and Expanding Objects — canonical patterns for cursor and expansion usage in a production API.
-
RFC 7231 (HTTP/1.1 Semantics) — authoritative guidance on status codes like
429andRetry-After.