cswork / libraryOpen practice workspace

Computer science concepts

Stripe API Pagination, Expansion, And Rate Limits

Candidates must show practical mastery of designing and using paginated REST APIs, the expansion pattern for reducing round-trips, and robust handling of…

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: OFFSET in Postgres/MySQL scans/skips rows; for deep pages prefer WHERE (key, id) > (k, id) LIMIT M indexed 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=customer in Stripe 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 Redis for distributed enforcement and use sliding-window or fixed-window with jitter to reduce thundering herd.

  • Client behavior on 429s: honor Retry-After header; 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/Reset or similar; log p99 latency, 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 OFFSET pagination for large datasets.
Using OFFSET for 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