AlgoInsight

Free lesson · Mobile System Design Interviews

Social Feed App - API Design

Medium8 min readmobilemobile-system-designinterview-prep

Step 2: What Does the Mobile App Actually Talk To?

Now that we understand the problem, we need to define how the mobile app communicates with the backend server. This is Step 2 of the framework: API Design.

Think of the API like a contract between two teams:

If both sides understand the contract, the app works. If they don't agree, things break.

Why REST and HTTP?

For a social feed app, we'll use REST (Representational State Transfer) over HTTP. Before you panic at the jargon—REST just means:

Why REST for a feed app?

✓ Simple and standard: Developers everywhere know REST. It's easy to test. ✓ Works well for request-response: Fetching a feed is a perfect request-response pattern—the app asks, the server responds. ✓ Stateless: Each request is independent. The server doesn't need to remember the app's state. ✓ Cacheable: We can cache responses (more on this later).

What about alternatives?

⚠️ Watch out: Don't over-engineer the API. Start with REST. Add complexity only when you need it.

API Endpoints: The Contracts

An endpoint is a specific URL and HTTP verb combination. Here are the main endpoints for our social feed:

1. Get Feed: GET /feed

What: Fetch a paginated list of posts for the user's feed.

Request:

GET /feed?cursor=abc123&limit=20
Authorization: Bearer {access_token}

Response (200 OK):

{
  "posts": [
    {
      "id": "post_001",
      "author": {
        "id": "user_123",
        "name": "Alice Johnson",
        "profilePictureUrl": "https://cdn.example.com/profiles/user_123.jpg"
      },
      "content": "Just finished an amazing hike!",
      "imageUrls": [
        "https://cdn.example.com/images/post_001_img1.jpg"
      ],
      "createdAt": "2026-03-25T14:30:00Z",
      "likeCount": 1247,
      "commentCount": 89,
      "isLikedByCurrentUser": false,
      "shareCount": 12
    },
    {
      "id": "post_002",
      "author": { ... },
      ...
    }
  ],
  "nextCursor": "xyz789",
  "hasMore": true
}

Why this structure?

2. Get Post Details: GET /posts/{id}

What: Fetch a single post with all its comments.

Request:

GET /posts/post_001
Authorization: Bearer {access_token}

Response (200 OK):

{
  "post": {
    "id": "post_001",
    "author": { ... },
    "content": "Just finished an amazing hike!",
    "imageUrls": [ ... ],
    "createdAt": "2026-03-25T14:30:00Z",
    "likeCount": 1247,
    "commentCount": 89,
    "isLikedByCurrentUser": false
  },
  "comments": [
    {
      "id": "comment_1",
      "author": {
        "id": "user_456",
        "name": "Bob Smith",
        "profilePictureUrl": "https://cdn.example.com/profiles/user_456.jpg"
      },
      "content": "Looks amazing! Where was this?",
      "createdAt": "2026-03-25T15:00:00Z",
      "likeCount": 23
    }
  ]
}

3. Create Post: POST /posts

What: Upload a new post to the server.

Request:

POST /posts
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "content": "Check out this sunset!",
  "imageUrls": ["https://s3.example.com/uploads/xyz789.jpg"]
}

Note: For image uploads, we'll use presigned URLs (explained below).

Response (201 Created):

{
  "id": "post_999",
  "author": { ... },
  "content": "Check out this sunset!",
  "imageUrls": [ ... ],
  "createdAt": "2026-03-25T16:45:00Z",
  "likeCount": 0,
  "commentCount": 0,
  "isLikedByCurrentUser": false
}

4. Like a Post: POST /posts/{id}/likes

What: Like a post. If already liked, unlike it.

Request:

POST /posts/post_001/likes
Authorization: Bearer {access_token}

Response (200 OK):

{
  "isLiked": true,
  "likeCount": 1248
}

Error Response (if post not found): (404 Not Found)

{
  "error": "Post not found"
}

5. Add Comment: POST /posts/{id}/comments

What: Post a comment on a post.

Request:

POST /posts/post_001/comments
Authorization: Bearer {access_token}
Content-Type: application/json

{
  "content": "This is incredible!"
}

Response (201 Created):

{
  "id": "comment_42",
  "author": { ... },
  "content": "This is incredible!",
  "createdAt": "2026-03-25T16:50:00Z",
  "likeCount": 0
}

Data Models: The Building Blocks

Let's define the data structures that flow through the API. Here's how you'd define them in code:

Kotlin Data Classes

// User object—represents a person
data class User(
    val id: String,
    val name: String,
    val profilePictureUrl: String
)

// Post object—the main content
data class Post(
    val id: String,
    val author: User,
    val content: String,
    val imageUrls: List<String>,
    val createdAt: String,  // ISO 8601 format: "2026-03-25T14:30:00Z"
    val likeCount: Int,
    val commentCount: Int,
    val isLikedByCurrentUser: Boolean,
    val shareCount: Int = 0
)

// Comment object—a reply to a post
data class Comment(
    val id: String,
    val author: User,
    val content: String,
    val createdAt: String,
    val likeCount: Int
)

// API response for the feed
data class FeedResponse(
    val posts: List<Post>,
    val nextCursor: String?,  // null if no more posts
    val hasMore: Boolean
)

// API response for post details
data class PostDetailResponse(
    val post: Post,
    val comments: List<Comment>
)

Swift Structs

// User object
struct User: Codable {
    let id: String
    let name: String
    let profilePictureUrl: String
}

// Post object
struct Post: Codable {
    let id: String
    let author: User
    let content: String
    let imageUrls: [String]
    let createdAt: String
    let likeCount: Int
    let commentCount: Int
    let isLikedByCurrentUser: Bool
    let shareCount: Int
}

// Comment object
struct Comment: Codable {
    let id: String
    let author: User
    let content: String
    let createdAt: String
    let likeCount: Int
}

// API responses
struct FeedResponse: Codable {
    let posts: [Post]
    let nextCursor: String?
    let hasMore: Bool
}

struct PostDetailResponse: Codable {
    let post: Post
    let comments: [Comment]
}

Pagination: How to Load Infinite Posts

The feed has potentially millions of posts. We can't send them all at once—the user's phone would run out of memory in seconds. Instead, we load posts in pages.

Cursor-Based Pagination

We use a cursor, which is a pointer to a position in the data. Think of it like a bookmark:

  1. First request: App calls GET /feed with no cursor
  2. Server responds: "Here are posts 0-19, and here's the cursor to get the next 20"
  3. Next request: App calls GET /feed?cursor=xyz789
  4. Server responds: "Here are posts 20-39, and here's the cursor for posts 40-59"

Why cursor-based instead of offset-based (page 1, page 2, page 3)?

💡 Tip: Cursor-based pagination is the industry standard for infinite feeds. Use it unless you have a specific reason not to.

Image Upload: Presigned URLs

When a user creates a post with images, uploading them needs a special strategy. Here's how:

The Problem: Users' images are huge (2-5 MB). We don't want the mobile app to send them through our main API server—that's wasteful.

The Solution: Presigned URLs

  1. App requests: "I want to upload an image. Can you give me a presigned URL?"
  2. Server responds: "Sure! Here's a temporary URL. You can upload directly to S3 (cloud storage) using this."
  3. App uploads: Directly to cloud storage (S3, Google Cloud Storage, etc.), not through our API
  4. App tells server: "I uploaded the image to this URL. Here's my post with that image."
  5. Server responds: Creates the post and stores the image URL

Why?

Authentication: Bearer Tokens

Every request except login needs proof that the user is authenticated. We use Bearer tokens:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

The token proves: "I logged in before, and the server gave me this token. I'm allowed to be here."

⚠️ Watch out: Tokens expire. The mobile app needs to refresh them before they expire, or re-login.

Common API Errors

Here are the errors your mobile app should handle:

Status Code Meaning What to Do
200 Success Show the data
201 Created Show success message
400 Bad Request Show error to user (they did something wrong)
401 Unauthorized Token expired; ask user to login
404 Not Found Post/comment doesn't exist; show error
429 Rate Limited Too many requests; wait before retrying
500 Server Error Temporary issue; retry later

Quick Decision Card: API Design Choices

If an interviewer asks why you chose REST, or why cursor-based pagination, here's what to say:

Choice Alternative Why We Chose Ours
REST/HTTP WebSocket or GraphQL Simple, familiar, request-response fits this use case
Cursor-based pagination Offset-based (page 1, 2, 3) Stable when data changes, efficient for large datasets
Presigned URLs for images Send images through API Faster, reduces server load, easier retry logic
Bearer tokens API keys or Session cookies Stateless, works across distributed servers

Interview's Perspective: What Interviewers Look For

When they ask "Design the API," they're actually testing:

  1. Do you think about data structures? Can you define clear models (Post, Comment, User)?
  2. Do you handle pagination? Infinite feeds are useless without pagination.
  3. Do you think about scale? You mentioned that we can't send all posts at once—that shows you're thinking realistically.
  4. Do you think about image uploads? Lots of candidates forget this. Presigned URLs shows you understand practical constraints.
  5. Do you handle errors? Real APIs fail. What happens when a request times out?

🎯 Interviewer's Perspective: Your API design should be simple enough to implement in a weekend, but thoughtful enough to handle millions of requests. That's the sweet spot.

Common Pitfall: Over-Specifying the API

Many candidates spend too much time on API design details like exact header formats or authentication schemes. Remember: the API design matters, but it's not the whole story.

The real challenge is how the mobile app uses these APIs—that's where the architecture and caching strategies come in (next steps).

🔗 Building on Previous: Remember the caching strategies from Module 3 (memory cache, disk cache, network cache)? The FeedResponse and Post objects will flow through all these layers. We'll see that in the next file.

What's Next?

Now that we've defined how the mobile app talks to the server, we need to design how the mobile app internally organizes all this data. This is the architecture—where we'll use patterns like MVVM, Repository, and caching.

Back to Mobile System Design Interviews