LogIn
I don't have account.

10 System Design Questions Companies Actually Ask (Not Just "Design Twitter")

Hemant Chauhan
75 Views

#distributed-system

#faang

#system-design-interview-questions

I once spent three weeks before an interview preparing for “Design Twitter,” “Design a URL Shortener,” and “Design Instagram.” They were classic textbook problems, with plenty of clean solutions available online, so it felt like the right way to prepare. Then the actual interview started and the first question was: “We have a retail POS system that needs to keep working even when the store's internet goes down for four hours. How would you design it?”

I froze, Not because the problem was harder than Twitter. It wasn't. I froze because I had never practiced a question where there was no existing template to follow. I had prepared to recognize familiar problems and reproduce well-known architectures, but I hadn't practiced taking an unfamiliar problem, identifying the constraints and reasoning my way toward a solution.

That gap is what often separates people who pass system design interviews from people who struggle with them. Knowing how to design Twitter is useful, but knowing how to think when the problem doesn't look like anything you've seen before is much more valuable.

So this is a different kind of list. These aren't the standard questions that appear in every system design course. They're the kinds of questions an interviewer might ask when they want to see whether you can actually reason about a system under real-world constraints.

Think about problems like pagination that remains correct while new data is constantly being written, replication across multiple data centers, choosing the right load-balancing strategy, designing an offline-tolerant retail system or building a notification pipeline that doesn't wake someone up eleven times for the same event.

For each problem, the interesting part isn't just the happy-path architecture. It's the constraint that makes the problem difficult and forces you to think beyond the obvious solution.

If you only have five minutes, here's the main idea: these questions aren't difficult because the underlying concepts are obscure. Pagination, replication, load balancing and rate limiting are all common system design topics. What makes these questions difficult is the specific constraint the interviewer adds. For example, pagination while new data is being written, replication during a network partition, a server failing while using round-robin load balancing or a retail system that needs to keep working for four hours without internet.

The interviewer isn't looking for you to simply draw the standard architecture you've memorized. They want to see whether you can identify the constraint, reason about its impact and adapt your design around it.

So when preparing, don't just memorize the diagram or the textbook solution. Practice the constraint. Ask yourself: What can go wrong here? What changes because of this constraint? And what trade-off would I make to handle it?

1. Design Pagination for an API With a Backend and Database

The obvious answer is to use LIMIT and OFFSET. For example, page 1 gets rows 1-20, page 2 gets rows 21-40 and so on. That works fine until the interviewer adds the real constraint: what happens if 50 new rows are inserted while the user is moving from page 1 to page 2?

With offset-based pagination, the rows can shift between requests. Imagine the user loads page 1, then 50 new records are inserted before they request page 2. Because the database calculates page 2 using the new data, some records the user already saw might appear again, while other records might be skipped completely. That's the main weakness interviewers are usually looking for.

A better approach for this kind of changing dataset is keyset pagination, also called cursor-based pagination. Instead of saying “give me the next 20 rows after offset 40,” the client says, “give me the next 20 rows after this particular record.” For example, if we're sorting orders by created_at and id, the query can look like:


SELECT *
FROM orders
WHERE (created_at, id) < (?, ?)
ORDER BY created_at DESC, id DESC
LIMIT ?;

The client sends the created_at and id of the last record it received as the cursor. The important detail is using both fields. Two orders can have exactly the same created_at, so using only the timestamp isn't enough. The id provides a deterministic tie-breaker.

The trade-off is that cursor-based pagination is much more stable when new records are being added, but you can't easily jump directly to something like page 50 because there is no simple offset. You typically move forward or backward using cursors. It also needs an appropriate composite index to perform well.

Approach Consistent under writes Random page jumps Main trade-off
Offset / Limit No Yes Can skip or duplicate records; expensive at high offsets
Keyset / Cursor Yes No Requires a good cursor and index
Snapshot-based Yes Yes More expensive; useful for exports

Another common follow-up is: “What if the UI needs to show a total count?” For example, a page selector might want to display “Page 3 of 120.” Doing an exact COUNT(*) on every request can become expensive for very large datasets. If an exact count isn't essential, a better approach can be to use a cached or approximate count that is refreshed periodically.

So the key interview lesson is: don't automatically choose OFFSET just because it's familiar. First ask whether the underlying data is changing while the user is paging through it. If it is, cursor-based pagination is usually the safer choice.

2. Design Cross-Data-Center Replication

The interesting part of cross-data-center replication isn't simply “how do we copy data from one region to another?” The real challenge is what happens when the network connection between two data centers goes down for 90 seconds, but both regions continue accepting writes during that time.

There are three common approaches.

  • The first is single-leader replication. One data center acts as the primary source of truth, while other data centers have read replicas. This is relatively simple to reason about, but users in other regions may experience higher write latency because their writes have to reach the leader. If the leader region goes down, you also need a clear failover strategy for any writes that haven't been replicated yet.

  • The second approach is multi-leader replication. Each data center can accept writes locally and those changes are replicated asynchronously to the other regions. This gives better write availability and lower latency, but it creates a much harder problem: conflicting writes. If Region A changes a user's record while Region B changes the same record during a network partition, both changes may be valid locally. Once the regions reconnect, the system needs a rule for deciding what the final value should be.

  • The third approach is leaderless or quorum-based replication. Instead of having one leader, a write is sent to multiple replicas and reads can require a certain number of replicas to agree. This gives you more control over the consistency and availability trade-off, but it is also harder to reason about and easy to misconfigure if you don't understand the quorum rules.


Region A (leader)  -- async replication -->  Region B (follower)
       |                                           |
  accepts writes                            read-only, or
                                            promoted on failover

The biggest interview trap is choosing multi-leader simply because it sounds more highly available. The moment you do that, the interviewer will likely ask: “What happens when both regions update the same record while they're disconnected?”

You need a concrete answer. Maybe you use last-write-wins, where the update with the latest timestamp wins. Maybe you use versioning or vector clocks to detect conflicting updates. Or perhaps the application has enough information to merge the changes itself.

The important thing isn't choosing one strategy that is always correct. It's showing that you understand the trade-off and, if you choose multi-leader replication, you have a specific conflict-resolution strategy rather than simply saying, “We'll handle conflicts later.”

3. Design a Round-Robin Load Balancer

This one seems almost too simple to be an interview question, until the interviewer adds: “One of your five servers is now returning 500s on every third request. Round robin still sends it a fifth of the traffic. Fix it.”

A basic Round-Robin load balancer keeps a pointer to the backend list and sends each request to the next server. If a server is unhealthy, the balancer can skip it and try the next available backend.

public class RoundRobinBalancer {
    private final List<Backend> backends;
    private final AtomicInteger cursor = new AtomicInteger(0);

    public Backend next() {
        int attempts = 0;
        while (attempts < backends.size()) {
            int idx = cursor.getAndIncrement() % backends.size();
            Backend backend = backends.get(idx);
            if (backend.isHealthy()) return backend;
            attempts++;
        }
        throw new NoHealthyBackendException();
    }
}
  • Use AtomicInteger because multiple request-handling threads may access the load balancer concurrently and we need the cursor increment to be atomic so concurrent requests don't race on the same counter value.

That's the starting point, not the finish line. The real conversation is usually about health checks , active checks that periodically call /health and passive checks that temporarily eject a server after repeated failures observed from real traffic.

Then comes Weighted Round Robin. If your servers have different capacities, a larger server can be given a higher weight so it receives proportionally more traffic.

Finally, there is sticky sessions. Round Robin can cause problems when session state is stored in server memory because consecutive requests from the same user may reach different servers. This often leads to the next interview question: “Why not make the application stateless instead?”

Algorithm Uneven load Unhealthy nodes* Session affinity
Round Robin ❌ ❌ ❌
Weighted Round Robin ⚠️ Partially ❌ ❌
Least Connections ✅ ❌ ❌
Consistent Hashing ⚠️ Partially ❌ ✅ Naturally
  • Unhealthy-node handling is typically added separately through health checks and node ejection.

The key takeaway: Round Robin is simple, handling failures, different server capacities and session state is where the real system design discussion begins.

4. Design a Retail Store POS System That Survives Internet Outages

This is a great system design question because it tests whether you can think in terms of real-world tradeoffs, rather than simply reciting the CAP theorem.

Quick Answer: The POS terminal should continue working during an internet outage using a local-first design. Transactions are stored in a durable local database and placed into an outbox for synchronization when connectivity returns. Every transaction should also have a client-generated idempotency key, so retrying the same transaction during reconciliation doesn't create duplicates.

class OfflineTransaction {
    private final String idempotencyKey; // client-generated UUID
    private final String terminalId;
    private final List<LineItem> items;
    private final Instant capturedAt;
    private SyncStatus status; // PENDING, SYNCED, CONFLICT
}

The real design discussion then moves to inventory, payments and conflict resolution.

  • For inventory, two terminals could potentially sell the last unit while both are offline. You either accept some overselling risk and reconcile later or use techniques such as per-terminal stock reservations.

  • For payments, the design depends on the payment method. Cash transactions can usually be recorded offline, while card payments generally require network connectivity for authorization unless the payment system specifically supports offline authorization.

  • For conflict resolution. When terminals reconnect, the system needs clear rules for handling conflicting updates. Simply assuming that the last terminal to sync is correct can lead to incorrect inventory or transaction data.

The key takeaway: a reliable offline POS system is less about choosing one perfect consistency model and more about deciding which business tradeoffs are acceptable when the network is unavailable.

5. Design a Real-Time Notification Pipeline

The generic version of this question is pretty straightforward. The interesting version is when the interviewer asks: “The same event - say, a comment on a post - needs to trigger a push notification, an in-app badge update and an email digest. How do you make sure the user doesn't receive three identical alerts?”

Quick Answer: Treat the event as a single source of truth and fan it out to different notification channels. Kafka works well as the event backbone, with separate consumers for push notifications, in-app updates and email digests.


Event Producer → Kafka Topic
                     |
        ┌────────────┼────────────┐
        ▼             ▼            ▼
  Push Consumer   In-App Consumer  Email Consumer
   (dedupe +        (update DB,     (buffer + batch,
   rate limit)      WebSocket)      send later)

The real design discussion is around fan-out, deduplication and delivery guarantees.

  • For an in-app feed, fan-out on write makes reads faster but can become expensive for users with millions of followers. Fan-out on read avoids that write cost but makes reads more expensive. The right choice depends on the scale and access pattern.

  • Then comes per-user rate limiting and notification bundling. If someone likes a user's comment 12 times in ten minutes, sending 12 notifications is a poor experience. A short-lived Redis window keyed by user and event type can group similar events before dispatch.

  • For at-least-once delivery rather than relying on end-to-end exactly-once processing. Kafka consumers can receive the same event more than once, so each consumer should use an idempotency/deduplication key to safely ignore duplicate events.

The key takeaway: the hard part isn't sending notifications. it's making the pipeline reliable, scalable, deduplicated and pleasant for the user.

6. Design a Rate Limiter

Almost every backend-heavy system eventually needs a rate limiter. The question gets more interesting when the interviewer asks: “Your rate limiter is distributed across ten API gateway instances. How do you keep the limit consistent without making every request depend on a central store?”

A rate limiter controls how many requests a user or client can make within a given period. There are several common approaches and each has different tradeoffs:

Algorithm Burst handling Memory cost Distributed-friendly
Fixed Window ❌ Poor at window boundaries Low ✅ Yes, with Redis
Sliding Window Log ✅ Excellent High ⚠️ Expensive at scale
Sliding Window Counter ✅ Good Low ✅ Yes
Token Bucket ✅ Good, controlled bursts Low ✅ Yes

For many real-world APIs, Token Bucket is a strong choice because it allows controlled bursts while maintaining an average request rate.

A common implementation uses Redis with a Lua script so checking and consuming a token happens atomically:

boolean allowed = redis.eval(
    TOKEN_BUCKET_SCRIPT,
    List.of("bucket:" + userId),
    List.of(
        String.valueOf(refillRate),
        String.valueOf(capacity),
        String.valueOf(now)
    )
);

The important part here is atomicity. Without it, two API gateway instances could read the same token count at the same time and both allow a request, causing the client to exceed the configured limit.

The key takeaway: the algorithm controls the traffic pattern, but in a distributed system, atomic state updates are what keep the rate limit reliable across multiple gateway instances.

7. Design a Distributed Cache

The generic answer is usually: “Put Redis in front of the database.” The interesting follow-up comes when the interviewer asks: “Your cache node just crashed and restarted empty. Now thousands of clients hit the database at once. What happens?”

This is the thundering herd problem, a large number of requests miss the cache at the same time and overload the database.

There are a few common ways to handle it:

  • Request coalescing : only one request fetches a missing key from the database while other requests wait for the same result.
  • TTL jitter : add a small random delay to cache expiration so many related keys don't expire at exactly the same time.
  • Cache warming : pre-populate frequently accessed keys before routing normal traffic to a restarted cache node.

A simple request-coalescing approach can look like this:

public V getOrLoad(K key) {
    V cached = cache.get(key);
    if (cached != null) return cached;
    Lock lock = keyLocks.computeIfAbsent(
        key, k -> new ReentrantLock()
    );
    lock.lock();
    try {
        cached = cache.get(key); // Re-check after locking
        if (cached != null) return cached;
        V loaded = loadFromDb(key);
        cache.put(key, loaded, ttlWithJitter());
        return loaded;
    } finally {
        lock.unlock();
    }
}

The important detail is the second cache check after acquiring the lock. Another request may have already loaded and cached the value while the current request was waiting.

The key takeaway: a distributed cache isn't just about fast reads. You also need to think about cache failures, simultaneous misses, expiration patterns and protecting the database from sudden traffic spikes.

8. Design a Unique ID Generator for a Distributed System

Auto-increment IDs work well with a single database, but they become difficult once you have multiple database shards or application instances. UUIDs solve the uniqueness problem, but completely random UUIDs can reduce index locality and cause more B-tree index fragmentation.

A common solution is a Snowflake-style ID generator. The ID combines a timestamp, a machine or worker ID and a sequence number into a 64-bit integer:

Timestamp + Machine ID + Sequence

The timestamp keeps IDs roughly sortable by creation time, the machine ID makes IDs unique across different servers and the sequence number allows a single machine to generate multiple IDs within the same millisecond.

The important follow-up is: “What happens if two machines have clock drift and generate an ID during the same millisecond?”

The answer is the machine ID. Even if both machines generate an ID at the exact same timestamp, their machine IDs are different, so the resulting IDs are still unique.

You also need to handle clock moving backward, because system clock adjustments can otherwise break the ordering and uniqueness guarantees. Production implementations typically detect clock rollback and either wait, use a logical strategy or fail temporarily depending on the requirements.

The key takeaway: Snowflake-style IDs provide globally unique, roughly time-ordered IDs without requiring a central database or coordination for every ID generation.

9. Design a Distributed Job Scheduler

The naive answer is “Use a cron job.” The real question comes when the interviewer asks: “You have 500 scheduled jobs and two scheduler instances for redundancy. How do you make sure the same job doesn't run twice when both instances are alive?”

The key is to use a distributed lock per job execution. This could be a database row-level lock with a leased_until timestamp or a Redis lock with a TTL. Both scheduler instances may detect that a job is due, but only the instance that successfully acquires the lock should execute it. The other instance backs off.


Scheduler A ──┐
              ├──> Distributed Lock ──> Execute Job
Scheduler B ──┘          │
                         └── Only one gets the lock

The lock also needs an expiry time. If the scheduler holding the lock crashes, another instance should eventually be able to acquire it instead of leaving the job permanently stuck.

For jobs that fail, use a retry mechanism with a dead-letter queue for jobs that continue to fail after the allowed retries. This prevents one failed job from blocking the scheduler and gives you a way to investigate persistent failures.

The key takeaway: a distributed scheduler isn't just about triggering jobs on time. You also need distributed locking, failure recovery, retries and a clear strategy for crashed scheduler instances.

10. Design a Search Autocomplete / Typeahead Service

Autocomplete looks like a simple UI feature, but it can be a good test of backend data structures and scalability. The core idea is usually a Trie, which makes prefix-based searches efficient.

For example, when a user types:

"jav"

the Trie can quickly find suggestions such as:


Java
JavaScript
Java Spring Boot
Java interview questions

But a production autocomplete system needs more than prefix matching. The suggestions should be ranked by popularity, relevance or recency, rather than simply returned alphabetically. A common approach is to store the top-K suggestions at each Trie node, so they can be returned quickly without recalculating rankings for every request.

Another important consideration is how the Trie is updated. Rebuilding or modifying the entire Trie for every new search term doesn't scale well. Instead, search frequency data can be collected separately and the Trie can be updated or rebuilt periodically in batches, such as every hour.

The key takeaway: a Trie solves fast prefix matching, but ranking, updates and scalability are what make autocomplete a real system design problem.

FAQ

1. Why do these questions feel harder than "Design Twitter"?

Because there's no popular blog post with the "correct" diagram to memorize. You have to reason from constraints :- concurrent writes, network partitions, clock drift, instead of recalling a template.

2. Should I still study the classic problems (Twitter, URL shortener, Instagram)?

Yes, but treat them as vocabulary-building, not the interview itself. The core building blocks :- sharding, caching, queues, replication are the same. What changes is which constraint the interviewer decides to press on.

3. How do I prepare for a constraint I've never seen before?

Practice narrating tradeoffs out loud on unfamiliar prompts, not memorizing diagrams for familiar ones. When you get a curveball constraint mid-interview, say the tradeoff you're weighing before you commit to an answer, interviewers are grading the reasoning, not just the final box-and-arrow diagram.

4. Do I need to write code in a system design round?

Usually pseudocode or short illustrative snippets are enough. Interviewers want to see that you can translate a design decision into something concrete, not write a complete production implementation.

5. What should I clarify before designing a system?

Start with the requirements and constraints. Ask about expected traffic, read/write ratio, data size, latency requirements, availability, consistency and failure scenarios. A design that works for 1,000 requests per second may look very different at 1 million.

6. Should I always choose the most scalable solution?

No. Over-engineering is also a design problem. Start with the simplest architecture that satisfies the requirements, then explain how you would scale it when traffic or complexity increases.

7. How important are failure scenarios in system design interviews?

Very important. A strong design should answer questions like: “What happens if Redis goes down?”, “What if Kafka is unavailable?” or “What if one database shard fails?” You don't need to eliminate every failure, but you should explain how the system detects and recovers from it.

8. Should I mention CAP theorem in every distributed system question?

No. CAP is useful for understanding tradeoffs, but simply saying “CAP says we choose consistency or availability” doesn't demonstrate much. Explain the actual business requirement and why your system chooses one behavior over another during a failure.

9. How do I know when to use a queue?

Use a queue when work doesn't need to happen synchronously or when you need to absorb traffic spikes, decouple services or retry failed work. Don't add Kafka or another queue just because it appears in every architecture diagram.

10. When should I use caching?

Use caching when data is read frequently and can tolerate some level of staleness. Also explain what happens when the cache misses, expires or becomes unavailable. A cache should improve performance without becoming a single point of failure.

11. How should I handle "exactly once" requirements?

Be careful with the phrase exactly once. In distributed systems, end-to-end exactly-once processing is difficult. A more practical approach is often at-least-once delivery combined with idempotent operations and deduplication keys.

12. What matters more: the architecture or the explanation?

The explanation. A reasonable architecture with clear tradeoffs is usually stronger than a complicated architecture that you cannot justify. Keep explaining why you chose each component and what tradeoff it introduces.

13. What is the biggest mistake candidates make?

Jumping straight into technologies and drawing boxes without understanding the problem. Before saying “Let's use Redis and Kafka,” explain the requirement those technologies are solving.

14. What should I do when the interviewer changes the requirements?

Don't restart the entire design. Identify which part of your architecture is affected, explain the tradeoff and modify that part. This shows that your design is flexible rather than a memorized template.

15. Is there one perfect architecture for a system design question?

No. Most system design questions have multiple valid solutions. The goal is to build a design that satisfies the requirements, explain its tradeoffs and show how you would evolve it as the system grows.

16. What should I focus on during the last few minutes of the interview?

Use the remaining time to review bottlenecks, failure scenarios, scalability and tradeoffs. Ask yourself: “What happens when traffic becomes 10× larger?” and “What happens when one of these components fails?” Those questions often reveal the strongest parts of your design.

Responses (0)

Write a response

CommentHide Comments

No Comments yet.