Sitemap
Engineering Under Pressure

We write about building systems that survive the real world. Backend architecture, AI engineering, DevOps, cloud, system design, performance, debugging, and production failures no theory for theory’s sake, just engineering lessons earned the hard way.

I Designed the Same System in 4 Interviews. Three Interviewers Rejected My Architecture

Every interviewer asked me to design a high-traffic backend. I kept adding Redis, Kafka, and replicas. The fourth interviewer asked one question that made most of my architecture irrelevant.

18 min read9 hours ago

--

The first interviewer stopped me twelve minutes into the system design round and asked why I had introduced Redis. I explained that caching would reduce database load, improve response times, and help the service scale to millions of requests. He nodded, looked at the architecture diagram, and asked how I planned to prevent customers from seeing prices that had already changed.

I had an answer ready. We would invalidate the cache whenever a product price changed, use a short TTL as a safety net, and publish update events so other services could refresh their local state. It sounded like the sort of answer a Senior Engineer was supposed to give. The interviewer waited until I finished, then asked what happened when the database transaction committed but the cache invalidation failed.

That question took longer to answer than it should have. I described retries, message queues, and eventually a transactional outbox, although I hadn’t planned for any of those components when I first drew the system. By the end of the interview, my simple product API had acquired Redis, Kafka, a background worker, and a recovery process. The interviewer asked whether I understood which consistency guarantee the business actually required.

I didn’t get the job.

Over the next six weeks, I encountered variations of the same problem in three more system design interviews. Each company wanted a scalable backend that could serve product information, handle changing prices, and support high traffic. I kept producing architectures that looked impressive on a whiteboard and became increasingly difficult to defend once the interviewer introduced concurrent updates, stale data, or partial failures.

The four interviews below are a fictional composite based on realistic Senior and Staff Engineer system design discussions. The traffic numbers and hiring outcomes are illustrative, while the database and distributed systems behavior follows real engineering constraints. The purpose is to examine why an architecture can sound sophisticated while failing to establish the guarantees the business actually needs.

Interview 1: I Added Redis Before Understanding What Could Be Cached

The first company operated an e-commerce platform with approximately five million daily active users. The interviewer asked me to design a product catalog API capable of handling high read traffic and frequent price updates. The platform needed to display product details, current prices, and availability across web and mobile clients.

I began with a conventional architecture. API requests would reach a load balancer, pass through stateless Spring Boot services, and retrieve product data from PostgreSQL. Redis would cache frequently requested product information, reducing database traffic and improving latency. Kafka would distribute product update events to downstream consumers.

The interviewer asked what I would store in Redis. I suggested caching complete product objects because that would minimize the number of database queries needed to render a product page. He asked whether product descriptions, prices, and inventory availability had identical consistency requirements.

I admitted that they didn’t. A slightly outdated description might be acceptable for a short period, while an outdated price could create customer complaints or contractual problems depending on the checkout rules. Inventory availability could change rapidly under concurrent orders, and a cached value shouldn’t be treated as authorization to reserve stock.

The interviewer asked why those fields belonged in the same cache entry.

I had grouped them together because they were returned by the same API endpoint. That was a convenient application-level decision, but it ignored the different correctness requirements of the underlying data. A product description could be cached aggressively, while checkout pricing and inventory decisions might require authoritative validation against durable state.

We discussed separating relatively static catalog information from price and availability data. The interviewer then introduced a price update that committed successfully in PostgreSQL while Redis remained unavailable. The cache could continue serving the old value after recovering, depending on how entries were populated and invalidated.

I proposed a short TTL. He asked whether the business could tolerate customers seeing an outdated price for that duration. I said we could reduce the TTL further, and he asked whether doing so would eliminate the consistency problem or merely shorten the period during which it could occur.

That was the first time I understood how weak my original reasoning had been. I had selected a caching strategy before defining the maximum acceptable staleness for each kind of data. The architecture could reduce average latency, but I couldn’t explain whether it preserved the business behavior the interviewer cared about.

What I Should Have Established First

I should have asked which data was informational and which data influenced binding business decisions. Product descriptions, images, and category metadata could potentially tolerate substantial staleness. Displayed prices might require a more carefully defined policy, while the final price charged during checkout should be validated according to the platform’s authoritative pricing rules.

I also should have distinguished between a product page displaying an approximate availability indicator and the inventory reservation operation itself. A cached availability number can improve browsing performance, but it cannot safely replace an atomic inventory update when customers compete for limited stock. Those are different operations with different consistency requirements.

The interviewer wasn’t asking me to avoid Redis. He wanted evidence that I understood what Redis was allowed to know and which decisions required stronger guarantees. My architecture had treated caching as a universal performance improvement without accounting for the correctness obligations attached to the cached data.

Interview 2: The Cache Stampede That Took Down PostgreSQL

The second interview involved a product API experiencing unpredictable traffic spikes. The company wanted the service to handle a major promotional event where a small number of products could receive enormous request volume. The interviewer asked how I would keep latency low while protecting PostgreSQL.

I proposed Redis again, but this time I was more careful about consistency. I separated product metadata from authoritative checkout operations and explained that cached values could be refreshed asynchronously where stale reads were acceptable. The interviewer seemed satisfied and asked how the cache behaved when a popular product entry expired.

I explained that the next request would miss the cache, load the product from PostgreSQL, and populate Redis. He asked what happened if ten thousand requests arrived during the same interval before the first database query completed. I said the requests would temporarily hit PostgreSQL, and he asked whether the database could survive that burst.

The problem was a cache stampede. A highly popular key could expire, causing many concurrent requests to fetch the same data from the underlying database. The cache had reduced normal database traffic, but the expiration event could concentrate load into a short period and create a failure that was difficult to reproduce under ordinary traffic.

I suggested adding random jitter to TTL values. The interviewer agreed that jitter could reduce synchronized expiration across different keys, but it wouldn’t prevent thousands of requests from competing for the same popular key after that key expired. He asked how I would coordinate cache regeneration.

I proposed request coalescing or single-flight loading, where one request refreshes the value while others wait or receive an acceptable stale result. The interviewer asked whether the coordination mechanism would operate across multiple Spring Boot instances. A local lock would reduce duplicate work inside one process, but it wouldn’t coordinate the entire application fleet.

We discussed distributed coordination, stale-while-revalidate behavior, and the possibility of serving previously cached data during a temporary backend failure. Each option had different latency and consistency consequences. If the data could tolerate short periods of staleness, serving an old value while refreshing in the background might protect PostgreSQL and improve availability.

The interviewer then asked what happened if Redis failed entirely. I explained that requests could fall back to PostgreSQL, but he wanted to know whether that fallback would overload the database. The application needed bounded concurrency, admission control, or another mechanism to prevent a cache outage from turning into a database outage.

That was the part I hadn’t considered carefully enough. A cache can protect a database during normal operation while creating a dangerous dependency when it becomes unavailable. The system needs an explicit plan for the transition between cached and uncached behavior.

The Failure Mode I Had Ignored

Caching changes the traffic distribution seen by the database. Under normal conditions, PostgreSQL may receive only a small fraction of read requests, which can make the system appear comfortably provisioned. During a cache outage or widespread expiration, the database may suddenly receive a much larger share of the workload.

A Senior Engineer should be able to estimate that exposure. If the application receives 50,000 read requests per second and Redis serves 98% of them, PostgreSQL sees approximately 1,000 requests per second from that workload. Losing the cache could increase direct database demand dramatically, even if the application instances remain healthy.

The appropriate response depends on the service’s requirements. Some systems can serve stale data, some can degrade nonessential features, and others must reject excess requests to protect authoritative operations. The important point is that cache failure behavior must be designed rather than discovered during the next promotional event.

Interview 3: The Read Replica That Made the System Less Correct

The third interviewer asked me to design a product and pricing API for a marketplace. The platform had a primary PostgreSQL database and several read replicas. The company wanted to scale reads while keeping checkout behavior reliable.

I proposed routing most product reads to replicas and sending writes to the primary database. Redis would cache popular product data, and Kafka would distribute changes to search and analytics services. The interviewer asked how quickly a customer would see a price change after an administrator updated a product.

I explained that PostgreSQL replication would eventually propagate the update to replicas. He asked whether the application guaranteed read-after-write consistency for the administrator. I said we could route the administrator’s immediate read to the primary database, then use replicas for subsequent requests.

He asked how long that routing rule should remain in effect.

I hadn’t defined a reliable way to know when the relevant replica had applied the update. A fixed delay might work most of the time, but replication lag could vary under load or operational problems. Routing reads based on elapsed time would be an approximation rather than a guarantee.

The interviewer then introduced a checkout scenario. A customer opened a product page showing a price of $120, while an administrator had just changed the authoritative price to $150. The customer’s request reached a replica that hadn’t applied the update yet, and the application returned the older value.

I explained that checkout would validate the price against the primary database. The interviewer asked whether the customer should be charged $150 automatically or informed that the price had changed. That was a business decision the architecture couldn’t make on its own.

We discussed versioned prices, explicit checkout confirmation, and the possibility of preserving quoted prices for a defined period. The correct solution depended on the marketplace’s pricing policy, customer expectations, and applicable business requirements. A database replica could serve stale information, but the checkout workflow needed to define how that information related to the final transaction.

The interviewer then asked whether a read replica could be used to validate inventory availability. I explained that a replica might be useful for browsing, but an inventory reservation needed an authoritative concurrency-control mechanism. A stale read from a replica couldn’t safely establish that stock remained available when multiple customers were competing for the same units.

The discussion moved into PostgreSQL transaction isolation and atomic updates. I described using a conditional update to decrement inventory only when sufficient stock remained. The interviewer asked how the application would handle a failed reservation after the product page had displayed availability. I explained that the UI needed to tolerate that outcome because availability could change between browsing and checkout even without replication lag.

By the end of the interview, I had stopped treating read replicas as transparent copies of the primary database. They were useful for scaling reads, but their consistency characteristics had to be considered explicitly. The architecture needed to decide which operations could tolerate stale data and which required authoritative state.

Why Read Replicas Are a System Design Interview Trap

Read replicas can improve read capacity and isolate some workloads from the primary database, but asynchronous replication introduces lag. Applications that assume every replica reflects the latest committed state can produce confusing or incorrect behavior. The risk becomes especially important when reads influence financial, inventory, or account decisions.

The solution isn’t necessarily to route every read to the primary database. That could eliminate much of the scaling benefit and create a new bottleneck. A better design classifies reads according to their consistency requirements and selects an appropriate source for each operation.

For some workflows, read-after-write consistency can be achieved through primary reads or mechanisms that track replication progress. Other workflows can tolerate eventual consistency and benefit from replicas. The interviewer wanted me to explain those choices rather than assume replication was a performance feature with no behavioral consequences.

The Preparation Change I Made Before Interview 4

After three interviews, I reviewed my architecture diagrams and noticed how often I began with infrastructure. I would draw a load balancer, application services, PostgreSQL, Redis, Kafka, and sometimes a read replica before establishing which business operations required strong consistency. The diagrams looked familiar because they resembled architectures I’d seen in production.

The problem was that familiarity had become a shortcut. I could explain how each component worked, but I wasn’t always explaining why it belonged in the system. When an interviewer introduced a failure scenario, I added another mechanism to compensate for an assumption I hadn’t examined earlier.

I changed my preparation method. Before drawing infrastructure, I wrote down the business operations, the invariants they needed to preserve, and the consequences of stale or duplicated data. I then estimated traffic and identified which operations were likely to become bottlenecks.

Only after that did I select components.

I also practiced explaining what would happen when each dependency failed. If Redis disappeared, could the application protect PostgreSQL? If a replica lagged behind, which requests could still use it safely? If an update event was delayed, would the system merely display stale information or violate a business invariant?

Those questions forced me to distinguish performance optimization from correctness. The architecture became easier to reason about because each component had a specific responsibility and a defined failure boundary.

Interview 4: I Designed the System Without Redis First

The fourth interviewer asked me to design a high-traffic product platform. The requirements included product browsing, price updates, inventory availability, and checkout. The expected traffic was substantial, but the interviewer hadn’t provided enough information to determine which parts of the system required caching.

Get Production Systems’s stories in your inbox

Join Medium for free to get updates from this writer.

I began by asking about the workload. How many products existed, what percentage of traffic involved popular products, and how frequently did prices change? I also asked whether product browsing could tolerate stale data and whether checkout needed to preserve quoted prices or always use the latest authoritative price.

The interviewer explained that most browsing traffic involved relatively static product metadata. Prices changed occasionally, inventory changed frequently for popular products, and checkout had strict correctness requirements. The system needed to support high read traffic without allowing customers to purchase unavailable inventory.

I proposed beginning with PostgreSQL as the authoritative store and a Spring Boot service responsible for the core business operations. Product metadata could be cached because its staleness requirements were relatively relaxed. Checkout pricing and inventory reservations would use explicit authoritative validation according to the business rules.

The interviewer asked why I hadn’t drawn Redis yet. I explained that I wanted to establish the data consistency boundaries before selecting a caching strategy. Once we understood which reads could tolerate staleness and how much traffic they generated, Redis could be introduced where it provided measurable value.

He asked how I would scale product browsing to tens of thousands of requests per second. I described caching relatively static product metadata and using appropriate database indexes for uncached queries. I also explained that popular-key expiration needed protection through request coalescing, stale-while-revalidate behavior where acceptable, and bounded fallback traffic to PostgreSQL.

The interviewer introduced a Redis outage. I explained that the application should degrade according to the importance of each operation. Nonessential browsing features could serve stale data or temporarily reduce functionality, while authoritative checkout operations would remain protected by explicit concurrency limits and database capacity controls.

He then asked about price changes. I described separating the displayed catalog price from the authoritative checkout decision, with a defined policy for handling changes between browsing and purchase. If the business required a guaranteed quoted price, the system would need a durable quote or price-version mechanism rather than relying on cache invalidation timing.

The interviewer asked how I would prevent overselling inventory. I explained that cached availability could be used for browsing hints, but reservations needed an atomic database operation or another concurrency-control mechanism that preserved the stock invariant. The system could reject a reservation when stock had changed, and the client would need to handle that outcome.

We discussed read replicas next. I proposed using them for queries that tolerated replication lag, while routing consistency-sensitive operations to an authoritative source. The interviewer asked how I would validate that the replica strategy was working. I described measuring replication lag, query latency, database load, and the frequency of stale-read consequences in workflows where those consequences mattered.

The conversation lasted longer than the previous three interviews. We still discussed Redis, PostgreSQL, Kafka, and scaling, but the components appeared only after the requirements justified them. The interviewer challenged several decisions, and I could explain the trade-offs without repeatedly redesigning the system from scratch.

What the Four Interviews Revealed

The first interview exposed a consistency problem. I had cached data with different business requirements inside the same object and assumed invalidation would keep everything correct. The second revealed a capacity problem because I hadn’t designed how the system would behave when popular cache entries expired or Redis became unavailable.

The third exposed the limitations of read replicas. I had treated replicated data as though it were immediately consistent, then struggled to explain which operations could tolerate lag. The fourth went better because I separated authoritative business decisions from performance optimizations before choosing infrastructure.

The recurring mistake was designing the architecture around familiar technologies rather than the guarantees the business needed. Redis, Kafka, and read replicas are useful tools, but they don’t automatically produce a reliable system. Their value depends on the workload, consistency requirements, and failure behavior.

A strong Senior or Staff system design answer should explain why each component exists and what happens when it stops working. The interviewer may be less interested in whether the candidate knows Redis than in whether the candidate understands the consequences of caching the wrong data. That distinction becomes increasingly important as systems grow and the cost of incorrect assumptions increases.

The System Design Framework I Would Use Today

I would begin by defining the core operations and their correctness requirements. Product browsing, price display, inventory reservation, and checkout may appear in the same application, but they don’t necessarily require the same consistency guarantees. Separating those operations makes it easier to select appropriate data access patterns.

Next, I would estimate the workload. Read volume, write frequency, hot-key distribution, and expected latency targets determine whether caching is useful and where bottlenecks are likely to appear. A system with a few extremely popular products behaves differently from one with uniformly distributed traffic, even when total request volume is similar.

I would then identify the authoritative source for each business decision. Cached data can improve performance, but the system needs to define when stale information is acceptable and when an operation must validate against durable state. The same reasoning applies to read replicas and asynchronously updated projections.

After selecting the architecture, I would test its failure boundaries. Cache outages, replication lag, concurrent reservations, and delayed update events should be considered explicitly. The goal is to understand which guarantees remain valid when components behave imperfectly.

Finally, I would explain how the system would evolve. A simple architecture may be sufficient initially, while higher traffic or stricter availability requirements could justify additional infrastructure. The important skill is knowing when that complexity becomes necessary and what operational responsibilities it introduces.

The Senior & Staff System Design Interview Mastery covers advanced architecture decisions and difficult interview follow-ups. The Staff Engineer Architecture Decision OS focuses on evaluating trade-offs and defending technical decisions under changing constraints.

Frequently Asked Questions About Redis, PostgreSQL, and System Design Interviews

When should I use Redis in a system design interview?

Use Redis when caching, coordination, or another supported capability addresses a specific requirement. Explain the expected workload, consistency needs, and failure behavior before introducing it. Interviewers may ask why Redis is necessary and what happens when it becomes unavailable.

How do I prevent stale data in Redis?

The appropriate strategy depends on the required consistency guarantee. Options may include invalidation, versioning, short TTLs, or avoiding cached reads for operations requiring authoritative state. Cache invalidation alone doesn’t automatically provide strong consistency across independent systems.

What is a cache stampede?

A cache stampede occurs when many requests attempt to regenerate the same missing or expired cached value simultaneously. This can create a sudden surge of traffic to the underlying database or service. Request coalescing, controlled refresh, and stale-while-revalidate strategies can reduce the risk when their consistency trade-offs are acceptable.

How do I handle Redis failure in a high-traffic system?

Define how the application should degrade when Redis is unavailable. Depending on the workload, the service may serve stale data, limit fallback requests, reduce nonessential functionality, or reject excess traffic. Unbounded fallback to PostgreSQL can overload the database during a cache outage.

Does Redis guarantee strong consistency with PostgreSQL?

No. Redis and PostgreSQL are separate systems, and ordinary cache updates or invalidations aren’t automatically atomic with PostgreSQL transactions. Applications must define how inconsistencies are handled and which operations require authoritative reads. Stronger guarantees may require different designs rather than additional cache invalidation attempts.

Should I cache product prices in Redis?

It depends on the business requirements and the consequences of stale prices. Cached prices may be suitable for browsing when the application has a defined policy for updates and checkout validation. Final financial decisions should follow the platform’s authoritative pricing rules rather than assuming the cache always contains the latest value.

Can Redis be used for inventory management?

Redis can support certain inventory-related workloads, but correctness depends on the complete architecture and durability requirements. A cached availability indicator should not automatically be treated as authorization to reserve stock. Systems must establish an authoritative concurrency-control mechanism that prevents overselling.

What is the difference between cache-aside and write-through caching?

Cache-aside loads data into the cache when requested and typically updates or invalidates cached entries separately from database writes. Write-through approaches update the cache as part of the application’s write path, although coordination with an independent database still requires careful failure handling. Neither pattern automatically eliminates distributed consistency problems.

How do PostgreSQL read replicas affect consistency?

Asynchronous read replicas may lag behind the primary database. Applications reading from replicas can therefore observe older committed states. Operations requiring stronger consistency may need primary reads or mechanisms that account for replication progress.

How do I guarantee read-after-write consistency with PostgreSQL replicas?

One approach is to route relevant reads to the primary database after a write. More advanced approaches may track replication progress and wait until a replica has applied the required changes. Fixed delays alone don’t guarantee consistency because replication lag can vary.

Should I use Kafka for cache invalidation?

Kafka can distribute update events to consumers that maintain caches or derived views. However, delayed processing, duplicate delivery, and consumer failures still require explicit handling. The decision should depend on the workload and consistency requirements rather than assuming event-driven invalidation is always necessary.

How do I prevent overselling inventory in PostgreSQL?

Use a concurrency-control mechanism that preserves the inventory invariant under simultaneous requests. Atomic conditional updates, appropriate locking, or carefully designed optimistic concurrency can be suitable depending on the workload. Sequential application-level checks alone may be insufficient.

What are common caching mistakes in system design interviews?

Common mistakes include caching data without defining acceptable staleness, ignoring cache stampedes, assuming invalidation is atomic with database writes, and failing to plan for cache outages. Candidates may also introduce Redis before estimating whether the workload requires it. Strong answers explain the trade-offs and failure behavior of the chosen approach.

How do I design a scalable product catalog system?

Begin by separating product metadata, pricing, availability, and checkout according to their consistency requirements. Estimate read and write traffic, identify hot data, and select appropriate storage and caching strategies. The architecture should preserve authoritative business decisions while scaling operations that can tolerate weaker consistency.

What do Staff Engineer interviewers look for in caching discussions?

They may evaluate whether candidates understand consistency boundaries, workload characteristics, failure recovery, and the operational cost of additional infrastructure. Staff-level discussions can also include migration strategy, cross-team ownership, and how architectural decisions evolve as requirements change. The exact expectations depend on the organization and role.

What I Would Tell an Engineer Before Their Next System Design Interview

I would tell them to delay drawing infrastructure until they understand the business operations. A cache can make an API faster while introducing stale data, and a read replica can improve capacity while changing what users observe after a write. Those trade-offs aren’t reasons to avoid the technologies, but they are reasons to establish the requirements before selecting them.

I would also encourage them to practice failure scenarios that challenge their original assumptions. What happens when Redis disappears during peak traffic, when a price update commits but invalidation fails, or when a replica hasn’t applied the latest transaction? Those questions reveal whether the architecture preserves the guarantees the business actually needs.

For engineers preparing for advanced system design interviews, the Senior & Staff System Design Interview Masteryprovides structured preparation across architecture decisions, scalability, and difficult follow-up scenarios. The Staff Engineer Architecture Decision OS focuses on the reasoning behind trade-offs that experienced engineers must defend.

For a broader preparation path, the Senior & Staff Engineer Career Accelerator combines 62 advanced engineering systems with 30 days of private asynchronous mentorship. It includes personalized written feedback on technical interviews, architecture decisions, and career development. Engineers who already know their specific preparation gaps may prefer a focused resource, while those seeking wider guidance can evaluate whether the mentorship format fits their circumstances.

Devrim Özçay — ProdRescue by Devrim

I publish advanced backend architecture investigations, Senior/Staff system design preparation, and production failure analysis on Substack. The articles examine the technical decisions that determine whether software remains correct and reliable when traffic, concurrency, and partial failures challenge the original architecture.

--

--

Production Systems
Production Systems

From mid-level to senior/staff engineer. Interviews, real career moves

Engineering Under Pressure
Engineering Under Pressure

We write about building systems that survive the real world. Backend architecture, AI engineering, DevOps, cloud, system design, performance, debugging, and production failures no theory for theory’s sake, just engineering lessons earned the hard way.