Interface Design
Learn how to communicate effectively between frontend and backend in system design interviews
Frontend-Backend Communication
In the RADIO framework, the "I" stands for Interface — this is all about how your frontend communicates with the backend. Whether you're building a dashboard, a chat app, or an ecommerce store, the interface layer handles how you fetch and update data.
Allocate about 15% of your interview time here: 5% on the transport protocol, 10% on how you structure your API layer.
Choosing the Right Communication Protocol
Not all apps need the same type of data flow. Pick the right protocol based on your product's needs:
| Use Case | Recommended Protocol | Why |
|---|---|---|
| Real-time chat | WebSockets | Two-way communication, very low latency |
| AI assistant or LLMs | Server-Sent Events (SSE) | One-way push stream from server to client, efficient for token-by-token streaming |
| Feed, dashboard, blog | REST or GraphQL | Standard HTTP data fetching for on-demand or cached data |
| File uploads | REST with multipart | Optimized for binary payloads and large files |
| Periodic data refresh | Short Polling | Client fetches repeatedly at fixed intervals; simple but inefficient for high-frequency updates |
| Near real-time updates without sockets | Long Polling | Client holds request until server responds with new data, then immediately re-issues; reduces wasted requests compared to short polling |
Choose based on interaction style:
- WebSockets: Real-time updates (e.g., live cursor in collaboration apps)
- SSE: Progressive responses, especially from AI/ML backends
- REST/GraphQL: Default for most frontend systems (dashboards, stores, newsfeeds)
Designing Clean API Interfaces
Once you've chosen your protocol, focus on designing readable and efficient endpoints or queries. Let's take an example of a project management tool (like Notion or Linear):
GET /boards/:id
{
"id": "board123",
"name": "Product Roadmap",
"tasks": [
{ "id": "task1", "title": "Add OAuth" },
{ "id": "task2", "title": "Fix mobile bugs" }
]
}POST /tasks
{
"title": "Onboard new PM",
"boardId": "board123",
"assigneeId": "user2"
}PATCH /tasks/:id
{
"status": "done"
}Design APIs that:
- Reflect your UI's shape
- Avoid overfetching or underfetching
- Support optimistic updates (especially with PATCH/PUT)
REST vs GraphQL
| Criteria | REST | GraphQL |
|---|---|---|
| Structure | Multiple endpoints | Single endpoint |
| Flexibility | Fixed responses | Client defines what it wants |
| Caching | Easier with HTTP semantics | Custom caching logic needed |
| Tooling | Broad ecosystem | Requires GraphQL server setup |
Use REST if:
- Your backend team already exposes REST endpoints
- You want simpler caching and CDN support
Use GraphQL if:
- You have deeply nested data and need flexibility
- You want to reduce round trips (e.g., mobile clients)
API Design Best Practices
1. Consistent Naming Conventions
// Consistent resource naming
GET /api/users
GET /api/users/:id
POST /api/users
PUT /api/users/:id
DELETE /api/users/:id
// Consistent response format
{
"data": { ... },
"meta": { ... },
"errors": [ ... ]
}2. Error Handling
// Proper error responses
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid email format",
"details": {
"field": "email",
"value": "invalid-email"
}
}
}3. Pagination
// Standard pagination
{
"data": [...],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"hasNext": true,
"hasPrev": false
}
}Real-time Communication Patterns
WebSocket Implementation
// Frontend WebSocket setup
const ws = new WebSocket('wss://api.example.com/ws');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'MESSAGE_RECEIVED':
updateChat(data.message);
break;
case 'USER_JOINED':
updateUserList(data.user);
break;
}
};Server-Sent Events (SSE)
// Frontend SSE setup
const eventSource = new EventSource('/api/stream');
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
updateProgress(data.progress);
};
eventSource.onerror = () => {
// Handle connection errors
eventSource.close();
};Security Considerations
1. Authentication
- Use JWT tokens or session-based auth
- Implement token refresh mechanisms
- Secure token storage (httpOnly cookies for sensitive data)
2. CORS Configuration
// Backend CORS setup
app.use(cors({
origin: ['https://yourdomain.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));3. Rate Limiting
- Implement rate limiting on API endpoints
- Use exponential backoff for retries
- Monitor and alert on abuse patterns
Performance Optimization
1. Request Deduplication
// Prevent duplicate requests
const requestCache = new Map();
const fetchWithCache = async (url: string) => {
if (requestCache.has(url)) {
return requestCache.get(url);
}
const promise = fetch(url);
requestCache.set(url, promise);
try {
const result = await promise;
return result;
} finally {
requestCache.delete(url);
}
};2. Batch Requests
// Batch multiple requests
const batchRequests = async (requests: Promise<any>[]) => {
const results = await Promise.allSettled(requests);
return results.map(result =>
result.status === 'fulfilled' ? result.value : null
);
};Summary Checklist
Before you move on:
- You've chosen the right protocol (HTTP, SSE, WebSocket)
- You've thought through endpoint design (GET, POST, PATCH, etc.)
- You've considered REST vs GraphQL tradeoffs
Next up: we move to Optimizations — how to reduce latency, improve load times, and create a blazing-fast frontend experience.
Next: Performance Optimizations - Learn how to optimize your frontend application for speed, efficiency, and user experience.