Skip to content

Latest commit

 

History

History
262 lines (207 loc) · 14.4 KB

File metadata and controls

262 lines (207 loc) · 14.4 KB

Mobile Pagination Strategies & Deep Dive

In a system design interview, "How do you handle the feed?" is a guaranteed question for any content-heavy application. This cheatsheet outlines the strategies and trade-offs you should discuss to demonstrate your understanding of mobile-specific constraints.

Your Goal: Show that you can choose the right architecture for the specific user experience (Infinite Scroll vs. Page Navigation) while considering network, battery, and memory usage.

1. Step 1: Clarify the Use Case

The most common mistake candidates make is jumping straight to a solution (e.g., "I'll use cursors!") without defining the problem.

Why is this important? The user experience (UX) dictates the underlying engineering architecture. You cannot choose the right pagination strategy without knowing how the user interacts with the data.

  • Infinite Scroll implies Dynamic Data. Users want to see new content without duplicates.
  • Static Pages implies Random Access. Users want to jump to specific records (e.g., "Page 10 of Order History").

How it guides the solution:

  • If you choose Offset for a Feed, users will see duplicate items when new content is posted (Page Drift).
  • If you choose Cursor for a static list, users can't jump to specific pages, breaking expected navigation patterns.

Scenario A: Infinite Scroll (Dynamic Content Streams)

Recommendation: Cursor-Based Pagination

  • The "Why": Users endlessly scroll down. Content is dynamic (items are created constantly).
  • The Problem it Solves: "Page Drift".
    • Scenario: User loads Page 1 (Items 1-10). While reading, 5 new items are added to the stream. User requests Page 2.
    • Offset Failure: Page 2 would return Items 6-15. Items 6-10 are duplicates of what the user already saw!
    • Cursor Success: The cursor points to "Item 10". The server asks "Give me 10 items after Item 10". No duplicates, no missed items.
  • Mobile Benefit: Smoother UX, no janky list updates to deduplicate items.
  • The Signal: Demonstrates an understanding of data integrity (no duplicates) and a prioritization of user experience over simpler implementations. Shows an understanding of the eventual consistency of distributed systems.

Scenario B: Static Lists / Admin Panels (Order History, Logs)

Recommendation: Offset/Page-Number Pagination

  • The "Why": User wants to jump to a specific record or date. "Go to Page 5". Data is relatively static.
  • The Mobile Benefit: Familiar navigation patterns (Previous/Next buttons). Easier implementation.
  • The Signal: Demonstrates a pragmatic approach that avoids over-engineering for simple problems.

Scenario C: Bi-directional Lists (Chat Apps)

Recommendation: Cursor-Based (Two-way)

  • The "Why": Users jump to a specific message (e.g., from Search or Push Notification) and need to scroll up to see history and down to see newer messages.
  • The Problem: Standard "Endless Scroll" only appends to the bottom. Chat requires prepending and appending.
  • The Signal: Identifying this unique constraint distinguishes senior mobile engineers. It requires managing two cursors (before_id and after_id) and maintaining scroll position stability during prepend operations.

2. API Design: What the Signal Looks Like

Show the interviewer you know how to design a clean, robust API contract.

The Request

Avoid simply stating that a GET request is sent. Do specify parameters and explain default safety mechanisms.

GET /v1/feed?cursor=dXNlcjpVMEc5V0ZXTjI=&limit=20
  • cursor: The opaque token from the previous response.
  • limit: Crucial Signal: Mention that you would enforce a server-side max limit (e.g., 100) to prevent a malicious client from crashing the server with excessive data requests.

The Response

Avoid returning a raw array. Do wrap data in an envelope with metadata.

{
  "data": [ ... ], // List of item objects
  "pagination": {
    "next_cursor": "dXNlcjpVMEc5V0ZXTjI=", // Opaque string
    "has_more": true // Helps the client UI know whether to show a spinner
  }
}

Advanced Signal: HATEOAS (Hypermedia)

You might suggest including full navigation links (HATEOAS style) instead of just the cursor.

"links": {
   "next": "https://api.example.com/v1/feed?cursor=dXNlcjpVMEc5V0ZXTjI=",
   "self": "https://api.example.com/v1/feed?cursor=..."
}
  • Pros: The client doesn't need to know how to construct the next URL (loose coupling). If you change the parameter name from cursor to token, the client app doesn't break.
  • Cons: Increases payload size (string bloat).
  • Verdict: Mention it as a "pure REST" option, but acknowledge that for mobile, sending just the lightweight cursor is often preferred to save bandwidth.

Interview Tip: Explicitly mention "Opaque Cursors".

  • Interviewer: "What's inside that string?"
  • You: "It's a Base64 encoded string containing the ID/Timestamp of the last item. We encode it so clients don't try to perform math on it (like id + 1), allowing us to change the backend logic (e.g., switching from Integer IDs to UUIDs) without breaking the mobile app."

3. Mobile Client Considerations (The "Mobile" part of the interview)

This is where you differentiate yourself from a backend engineer by focusing on client-side resource constraints and UX patterns.

3.1 Prefetching & Thresholds

  • The Signal: Demonstrates a proactive approach to loading content before the user reaches the end of the list.
  • Strategy: Trigger the next page load when the user is X items (e.g., 5-10) away from the end.
  • Trade-off: Too aggressive = wasted data (user might stop scrolling). Too lazy = user sees a spinner.

3.2 Network Reliability

  • Problem: Request for Page 2 fails.
  • Solution: Show a "Tap to Retry" footer. Do not automatically retry indefinitely (battery drain).
  • The Signal: Demonstrates consideration for battery life and the graceful handling of failure states.

3.3 Memory Management & Recycling

  • Problem: Infinite scrolling creates an infinitely growing list in memory.
  • Solution:
    • View Recycling: Explain the pattern of reusing UI components. As a user scrolls, views that leave the screen are "recycled" and re-bound with new data for entering items. This ensures the memory footprint corresponds to the screen size, not the total list size.
    • Diffing: Discuss using background threads to calculate the difference between the old and new list states (Insertions, Deletions, Moves). This allows the UI thread to animate only the specific changes, preventing jank.
    • Large Data Sets: For massive lists (10k+ items), discuss "Windowing" - dropping off-screen pages from the backing data structure to prevent Out-Of-Memory (OOM) errors.

3.4 UX Patterns: Skeletons vs. Spinners

  • Problem: How to indicate loading state for the initial page vs. subsequent pages.
  • Solution:
    • Initial Load: Use Shimmer/Skeleton placeholders that mimic the cell layout. This reduces perceived latency compared to a generic spinner.
    • Pagination: Use a subtle Spinner/Footer at the bottom of the list.
  • The Signal: You care about Perceived Performance and UI polish.

3.5 State Restoration (Process Death)

  • Question: "What happens if the user backgrounds the app, the OS kills the process to save memory, and the user returns?"
  • Answer:
    • The app must restore the exact scroll position and the data that was visible.
    • Strategy: Persist the current cursor and the list_state (scroll offset) to the system's saved state bundle. Re-fetch the data using the cursor or load from local persistence.
  • The Signal: This is a deep mobile-specific cut that backend engineers will miss.

3.6 Caching & Offline Support

  • Question: "How does pagination work offline?"
  • Answer:
    • Persist the first X pages (e.g., 50 items) in a local database.
    • When offline, serve from the database.
    • When online, fetch fresh data, update the database, and notify the UI (Single Source of Truth pattern).
  • The Signal: Demonstrates an understanding of "Offline-First" architecture and the creation of resilient applications.

3.5 Automatic Pagination (Auto-Pagination)

An advanced signal is discussing the abstraction of pagination logic through client libraries, often referred to as "Automatic Pagination."

  • The Concept: Instead of the developer manually extracting the "next page token" and firing a new request, the client library treats the paginated resource as an asynchronous stream or iterator.
  • Reducing Boilerplate:
    • Token Management: The library automatically captures the next_page_token from a response and populates the page_token in the subsequent request.
    • Abstraction: The developer simply iterates over the results (e.g., using a for loop or a stream observer); the library handles the network calls in the background.
  • Resource Management:
    • Lazy Resolution: High-quality implementations are "lazy" - they only fetch the next page when the consumer reaches the end of the current buffer, preventing the "greedy" loading of massive datasets.
  • The Signal: Demonstrates familiarity with high-level architectural patterns (AIP-4233) that reduce human error and improve code maintainability by hiding low-level network plumbing.

4. Common Pitfalls ("Red Flags")

  • "I'll just return all the data." -> Immediate fail. Shows lack of scalability awareness.
  • "I'll use page parameters for the real-time feed." -> Shows lack of awareness regarding real-time data shifts (Page Drift).
  • "I'll sort by Client Timestamp." -> Never trust the client clock. Always use Server Time.
  • Ignoring Empty States: Always plan for the { "data": [] } case.

5. Security & Privacy

  • Scraping: Pagination makes it easy to scrape your entire database. Mention Rate Limiting (e.g., "Max 60 requests/minute").
  • Broken Access Control: Ensure the user has permission to see every item in the page, not just the first one.
  • Predictable IDs: If you use simple integers (/users?after_id=50), competitors can guess your growth rate. Opaque cursors hide this.
  • The Signal: Demonstrates awareness of production risks and the importance of protecting business assets.

6. Real-World Case Studies

6.1 Twitter (High Velocity Feed)

  • Problem: High-velocity writes cause massive "Page Drift" in offset systems.
  • Solution: Cursor-based (next_token).
// Request
GET /2/tweets/search/recent?query=system+design&max_results=10&next_token=b26v89c19zqg8o3fo7ggj03...

// Response
{
  "data": [ ... ],
  "meta": {
    "result_count": 10,
    "next_token": "b26v89c19zqg8o3fo7ggj03..."
  }
}
  • Design Pattern: Metadata envelope (meta) separates stream state from content, allowing for stable "anchors" in a constantly changing timeline.
  • Reference: Twitter API Pagination

6.2 Slack (Deep History Performance)

  • Problem: Scanning huge offsets (OFFSET 50000) is slow (O(N)).
  • Solution: Cursor-based.
// Request
GET /conversations.history?channel=C012345&cursor=dTx0...

// Response
{
  "ok": true,
  "messages": [ ... ],
  "has_more": true,
  "response_metadata": {
    "next_cursor": "bmV4dF9tZXNzYWdl..."
  }
}
  • Design Pattern: Namespaced response_metadata ensures the pagination token is discoverable even when the messages array is empty.
  • Reference: Slack API: conversations.history

6.3 Stripe (Financial Consistency)

  • Problem: Financial reconciliation requires zero missed items.
  • Solution: Strict cursors (starting_after, ending_before).
// Request
GET /v1/charges?limit=10&starting_after=ch_12345

// Response
{
  "object": "list",
  "data": [
    { "id": "ch_12346", ... },
    { "id": "ch_12347", ... }
  ],
  "has_more": true,
  "url": "/v1/charges"
}
  • Design Pattern: Uniform object: list structure allows client libraries to polymorphically automate pagination across all API resources.
  • Reference: Stripe API Pagination

6.4 Reddit (Highly Dynamic Rankings)

  • Problem: Content rank (votes) changes constantly during a session.
  • Solution: Fullname IDs as cursors.
// Request
GET /r/all/hot.json?limit=25&after=t3_15bfi0

// Response
{
  "kind": "Listing",
  "data": {
    "children": [ ... ],
    "after": "t3_16cfj1",
    "before": null
  }
}
  • Design Pattern: Standardized "Listing" wrapper ensures consistent cursor placement (after/before) for every dynamic feed in the app.
  • Reference: Reddit API Documentation

7. Summary Checklist for the Interview

  1. Clarify the Use Case: Is it infinite scroll (Feed), static navigation (History), or bi-directional (Chat)?
  2. Choose the Strategy: Cursor (Feed) vs. Offset (Static).
  3. Define the API: Request params (cursor, limit) and Response structure (envelope, metadata).
  4. Add Mobile Context: Prefetching, Error Handling, Offline Caching, and Process Death.
  5. Defend against Edge Cases: Rate limiting, Empty states, Malformed inputs.

8. Further Reading

  • Slack Engineering: Evolving API Pagination at Slack - A classic case study on why a major tech company migrated from offset to cursor-based pagination.
  • Twitter API Docs: Pagination in the Twitter API - See how one of the world's largest feeds handles pagination in production.
  • Stripe API Reference: Pagination - Widely considered the "Gold Standard" for developer-friendly cursor pagination.
  • Google AIP-158: Pagination - Detailed design patterns for standardizing list methods.
  • Google AIP-4233: Automatic Pagination - Standards for how client libraries should abstract pagination logic.
  • Nielsen Norman Group: Infinite Scrolling vs. Pagination - The definitive UX research on when to use which pattern.