<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Fahim ul Haq on Medium]]></title>
        <description><![CDATA[Stories by Fahim ul Haq on Medium]]></description>
        <link>https://medium.com/@fahimulhaq?source=rss-71fb82f73ada------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*oEKzOQryv6UkwpB8fB9W1Q.jpeg</url>
            <title>Stories by Fahim ul Haq on Medium</title>
            <link>https://medium.com/@fahimulhaq?source=rss-71fb82f73ada------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 17 Jun 2026 15:23:19 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@fahimulhaq/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Why weak trade-offs undermine strong interview answers]]></title>
            <link>https://medium.com/@fahimulhaq/why-weak-trade-offs-undermine-strong-interview-answers-b80d7e485069?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/b80d7e485069</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 15 Jun 2026 06:44:43 GMT</pubDate>
            <atom:updated>2026-06-15T06:44:43.364Z</atom:updated>
            <content:encoded><![CDATA[<p>Every candidate I interview can name a cache. Far fewer can explain how that cache fails under this workload.</p><p>I have conducted hundreds of<a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> System Design interviews</a>, and the pattern is consistent. A candidate draws a cache, a broker, and a read replica. They connect the boxes with arrows and say “<a href="https://kafka.apache.org/">Kafka for async processing</a>” with confidence.</p><p>Then I ask why Kafka fits this workload better than a simpler queue. The answer usually turns into a definition of Kafka, not a reasoning chain tied to the system in front of us.</p><p>That gap is the real signal I am evaluating. Kafka is not justified by the word “async” alone. It fits when the workload needs replay, independent consumer offsets, retention windows,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-partitioning?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> partition-level ordering</a>, or recovery after consumer failure.</p><p>If the system only needs transient background jobs, a managed queue may be the better fit. It carries less operational overhead.</p><p>Architectural judgment does not begin with component selection. It begins when a candidate connects a component to a workload constraint and names the cost that comes with it. A fragile answer names components. A strong answer explains the constraint, workload characteristic, and cost behind each choice.</p><blockquote><strong>Note:</strong> Trade-off depth is the primary signal in a senior<a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> System Design</a> interview. Vocabulary breadth is expected. It does not distinguish a candidate.</blockquote><p>The difference shows up clearly when you put two candidate responses next to each other.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IzwLHkfeFm4tXyjZKFFUrg.png" /><figcaption>Shallow topology versus annotated trade-off reasoning in a System Design answer</figcaption></figure><p>The same issue appears with defaults. A read replica, worker pool, or cache can be a good choice, but only if the workload makes its assumptions safe.</p><h3>Default patterns need workload-specific justification</h3><p><a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">Read replicas</a>, async worker pools, cache layers, and denormalized stores are reasonable defaults. They solve common bottlenecks, which is also why candidates reach for them too quickly.</p><p>The problem is not the pattern. The problem is treating it as self-explanatory. If I ask why a candidate chose async replication instead of quorum writes, I am not looking for a definition of <a href="https://www.educative.io/courses/grokking-the-system-design-interview/spectrum-of-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">eventual consistency</a>. I am looking for evidence that they modeled the workload and accepted the replica lag risk for this system.</p><p>A candidate who says “read replicas reduce load on the primary” has named a general benefit. A candidate who says “this read path can tolerate brief staleness, but read-after-write flows stay on the primary” has made a workload-specific argument.</p><p>One answer names a benefit. The other explains why the benefit is safe for this workload.</p><blockquote><strong>Watch out:</strong> A default pattern carries a hidden workload assumption. Stating the pattern without stating the assumption is an incomplete trade-off.</blockquote><p>The table below maps common defaults to the assumptions they carry and the workload conditions that can break them.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KxJh_evG-rD1Nbii0Da1LQ.png" /></figure><p>Once those hidden assumptions are exposed, the next test is whether the candidate can explain what happens when they fail.</p><h3>Failure modes expose recalled designs</h3><p>Follow-up questions are where recalled designs collapse. Bursty writes, stricter consistency, or a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/consistent-hashing-for-scalable-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">hot partition</a> quickly reveals whether the candidate modeled failure modes or assembled components from memory.</p><p>Two breakdowns make this visible.</p><ul><li><strong>Stale inventory after purchase: </strong>In a high-volume e-commerce system, a few seconds of replica lag during flash-sale writes can show inventory that has already sold. If a user reads immediately after purchase, I want to know whether the design routes that session to the primary, uses a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/event-ordering-and-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">read-your-writes token</a>, or accepts stale confirmation state.</li><li><strong>Cascading retries without backoff:</strong><a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"><strong> </strong>Retries without backoff</a>, jitter, timeouts, or retry budgets can turn one slow dependency into caller-side connection pool exhaustion. When multiple upstream services retry at once, concurrent attempts multiply, threads wait for connection slots, and p99 latency rises across callers sharing the same pool.</li></ul><p>Vague trade-off language like “eventual consistency is fine” collapses when I ask “fine for whom, and what happens when it is not?” Strong candidates name that failure mode before I have to ask.</p><p>The following diagram shows how retries without backoff create a cascade and how bounded retries with backoff and jitter contain it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*brqLQ3q26tlmplyg02bdHQ.png" /><figcaption>Cascading failure propagation with and without retry mitigation strategies</figcaption></figure><p>Once a failure mode is clear, the next question is where the pressure moves when we try to fix it.</p><h3>Local fixes move system pressure</h3><p>A component that relieves one bottleneck usually moves pressure somewhere else. That movement is what separates trade-off reasoning from component familiarity.</p><p>The same pattern shows up in common interview components.</p><ul><li>Read replicas reduce primary read load, but under heavy writes the <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-communication-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">replication pipeline</a> can fall behind. If replicas also serve expensive reads, they may have less capacity to apply changes quickly.</li><li>Message brokers smooth ingestion spikes, but they introduce consumer lag, offset recovery, poison-message handling, partition assignment, rebalancing, and backlog drain time.</li><li><a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-cache?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">Caches</a> reduce database load, but they move pressure to freshness, invalidation, and cold-path capacity. When a cache goes cold, every request can hit a database path the system was never sized to handle at full traffic.</li></ul><blockquote><strong>Practical tip:</strong> When you add a component, state where the bottleneck moved. “This moves pressure from X to Y, and Y is more tolerable because…” is a complete trade-off statement.</blockquote><p>The trade-off is not whether these components are useful. It is whether you can explain where the pressure went and why the new location is more acceptable for this workload.</p><p>The diagram below shows common optimizations alongside the system pressure they transfer.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*iUaa6T8LtVJHZeKeGCaEzQ.png" /><figcaption>Pressure-shift diagram showing local optimizations and their transferred system costs</figcaption></figure><p>Once you can explain where pressure moves, the next step is to make the full trade-off explicit. What constraint drove the decision, what option did you choose, and what cost did you accept?</p><h3>Constraint, choice, and cost as a trade-off framework</h3><p>When I sit across from a candidate, I am mentally tracing a three-part chain. What constraint is driving the decision? What choice did they make under that constraint? What new cost or failure mode did they accept?</p><p>When any link is missing, the answer feels like a conclusion I am being asked to accept rather than a reasoning path I can probe.</p><p>Two comparisons show the framework in practice.</p><p><strong>Async replication vs. quorum writes</strong></p><ul><li><strong>Constraint:</strong> The write path has a sub-50 ms p99 target, and the product can tolerate moderate staleness on non-critical reads.</li><li><strong>Choice: </strong>Use async replication to avoid the round-trip cost of quorum acknowledgment.</li><li><strong>Cost:</strong> Replica reads can be stale until replication catches up, and read-your-own-write violations are possible if the same session reads from a replica immediately after writing.</li></ul><p>A stronger answer also names the mitigation. Read-after-write flows can route to the primary, or the system can use version tokens before allowing a replica read.</p><p><strong>Synchronous calls vs. queued work</strong></p><ul><li><strong>Constraint:</strong> The <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-communication-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">response path</a> has a 200 ms service-level agreement, but the downstream p99 exceeds 400 ms under load.</li><li><strong>Choice:</strong> Put the work behind a broker so the caller is not blocked by downstream <a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">latency</a>.</li><li><strong>Cost:</strong> The system now has <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-messaging-queue?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">at-least-once delivery</a>, so consumers must be idempotent. It also adds consumer delay, offset recovery, and backlog drain time after failure.</li></ul><blockquote><strong>Note:</strong> Strong engineering teams use this same structure in <a href="https://adr.github.io/">architecture decision records</a>. The decision matters, but so do the constraints and costs that made it reasonable.</blockquote><p>This structure gives reviewers something useful to challenge. The trade-off matrix below applies it to common design decisions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RnKjSKwpYBKhbUa9RqSImg.png" /></figure><p>Once the trade-off is explicit, a requirement change becomes easier to handle because you can see which decisions depended on which assumptions.</p><h3>Assumption shifts test design integrity</h3><p>A candidate who states assumptions explicitly can revise the design selectively when requirements change. If they said “this is read-heavy, this path tolerates 500 ms of staleness, and this <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/database-partitioning-and-sharding-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">partition key</a> distributes writes evenly,” then both of us can trace which decisions depended on those assumptions.</p><p>That creates a productive conversation. If read-after-write behavior becomes strict, we can revisit replica reads. If write volume spikes, we can revisit replication lag. If one tenant becomes much larger than the rest, we can revisit the partition key.</p><p>A candidate who leaves assumptions hidden has a harder problem. When I change one requirement, they either defend the entire architecture or abandon it. Neither response builds confidence. The fix is simple. Make the reasoning visible.</p><p>This mirrors what I have observed in production systems. The systems that adapt best to requirement changes are usually the ones where the original designers documented their constraints and known costs. Architecture decision records exist for the same reason. The decision matters, but so do the conditions that made it reasonable.</p><p>Systems without that context often get patched cautiously or rewritten because no one can tell which original constraints still apply.</p><blockquote><strong>Practical tip:</strong> Before finalizing a design in an interview, state your top three assumptions explicitly. This gives the interviewer a clear surface to probe and shows where the design is load-bearing.</blockquote><p>The strongest candidates I have interviewed did not give perfect designs. They gave designs I could reason about. They named their constraints and costs. When I changed a requirement, they updated the right component and explained why the rest held.</p><p>Before finalizing your answer, name the constraint behind each major component choice. Then name the cost you are accepting, such as stale reads, duplicate delivery, consumer lag, or <a href="https://www.educative.io/courses/grokking-the-system-design-interview/maintainability?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">operational overhead</a>.</p><p>When the interviewer changes a requirement, update only the decisions tied to that assumption. The component you name is the starting point. The constraint, choice, and cost behind it are the answer.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b80d7e485069" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Managing data consistency is the hardest interview tradeoff]]></title>
            <link>https://medium.com/@fahimulhaq/managing-data-consistency-is-the-hardest-interview-tradeoff-1ebaff632bb5?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/1ebaff632bb5</guid>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Wed, 10 Jun 2026 05:00:45 GMT</pubDate>
            <atom:updated>2026-06-10T05:00:45.872Z</atom:updated>
            <content:encoded><![CDATA[<p>In nearly every<a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> System Design interview</a> I conduct, the same mistake shows up early. The candidate says “strong consistency” or “eventual consistency” as if they are choosing from a dropdown. No invariant. No failure scenario. No explanation of where writes commit or where reads land.</p><p>After enough interviews and production incidents, I’ve reached the same conclusion. Data consistency is not a property you declare. It emerges from the write commit path, replication mode, read routing, and cache behavior. What breaks is determined by where those paths diverge.</p><p>A write can commit to a leader with a durable <a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">write-ahead log (WAL)</a> and still fail a read-after-write expectation if the next read hits an asynchronous replica that has not replayed that log entry. The write is durable, but the user can refresh the page and see old state.</p><p>I’ve seen this pattern in distributed storage systems where one read path silently routed to a replica that was lagging behind the write leader. Users saw confirmed changes disappear on refresh. Nothing was down. The system was alerting on replica health and request errors, but not on stale-read rates or read-after-write violations.</p><p>The lesson was clear. Consistency problems live in the infrastructure topology, not in a design document. The following diagram shows where this divergence happens in a typical replicated architecture.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xKOOH4tq75X8YXkGfYkLvg.png" /><figcaption>Read-after-write consistency depends on where the post-write read is routed</figcaption></figure><blockquote><strong>Watch out:</strong> Saying “we’ll use eventual consistency” without specifying which reads tolerate staleness is an easy way to signal shallow reasoning in a<a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> System Design</a> interview.</blockquote><p>Understanding this topology is the foundation. But real systems rarely need a single consistency model everywhere, and that is where most designs start to fall apart.</p><h3>Different paths need different guarantees</h3><p>A mature design answer decomposes the workload before naming a <a href="https://www.educative.io/courses/grokking-the-system-design-interview/spectrum-of-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">consistency model</a>. Payment deduction, profile updates, notification delivery, inventory reservations, and analytics reads all fail differently when data is stale.</p><p>A weaker answer labels the whole service as “eventually consistent” and creates contradictions.</p><ul><li>The payment path is under-protected. Duplicate debits, stale balances, or negative balances can slip through.</li><li>The analytics path is over-coordinated. The system pays consensus <a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">latency</a> for reads that already tolerate minutes of staleness.</li></ul><blockquote><strong>Practical tip:</strong> Do not assign one consistency model to the whole service. Assign guarantees to individual paths based on the invariant each path protects.</blockquote><p>A better answer maps each path to the failure it must prevent.</p><ul><li><strong>Payment deduction:</strong> Idempotent ledger write with a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/acid-vs-base-database-transaction-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">serializable transaction</a>, compare-and-swap (CAS), or a consensus-backed write path. Prevents duplicate debit or negative balance.</li><li><strong>User profile update:</strong> Read-your-writes for the updating user. Short staleness is usually fine for others. Concurrent edits need optimistic concurrency control or conditional writes.</li><li><strong>Notification delivery:</strong><a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-messaging-queue?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> Durable queue</a>, retries, acknowledgments, and deduplication. Seconds of lag are usually acceptable.</li><li><strong>Analytics reads:</strong> Eventually consistent aggregate views. Minutes of lag are usually acceptable.</li><li><strong>Inventory reservation:</strong> Linearizable guarantee per stock keeping unit (SKU) partition. Prevents oversell or double reservation.</li></ul><p>That decomposition determines how replica lag and cache staleness show up in production, which brings us to the failure mode most engineers underestimate.</p><h3>Replica lag shows up as correctness bugs</h3><p><a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Replica lag</a> and cache staleness often show up as user-visible correctness bugs before they trigger availability alerts. A user updates their shipping address, refreshes the page, and sees the old address. Two support agents load the same ticket version, make different edits, and the later write overwrites the earlier one.</p><p>These are not outages. They are correctness failures. Every service may look healthy in isolation, but the product still violates the user-visible guarantee.</p><blockquote><strong>Note:</strong> Many consistency bugs appear at normal traffic levels, where dashboards often look healthy. Request rates, CPU, and error counts may stay flat while stale reads or lost updates quietly affect users.</blockquote><p>One common failure mode is a mismatch between the write path and the read path. A write commits to the leader, but the confirmation read hits a lagging replica or stale cache. That breaks read-your-writes behavior.</p><p>The support-agent example has a different shape. It is a concurrent update problem. Two clients read the same old version, then both write based on it. Session affinity will not fix that. You need <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/event-ordering-and-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">optimistic concurrency control</a>, version checks, compare-and-swap, or another conditional write mechanism.</p><p>For read-after-write behavior, the fix is freshness-aware routing. Attach a monotonic version token to the write acknowledgment. On the next read, compare the client’s token against the replica’s replay position, revision,<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/ETag"> ETag</a>, or consistency token. If the replica is behind, wait briefly for it to catch up or route the read to the primary.</p><blockquote><strong>Practical tip:</strong> This pattern only works when your database or managed service exposes a freshness marker such as a <a href="https://www.postgresql.org/docs/current/datatype-pg-lsn.html">log sequence number (LSN)</a>, replay position, revision, ETag, or consistency token. If it does not, use primary reads for post-write flows or a bounded wait API if the database supports one.</blockquote><p>The following sequence diagram contrasts the broken path with the protected one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*0efp-uFZbGsWXGVu8yLr1w.png" /><figcaption>Read-after-write consistency via freshness-aware routing and version fencing</figcaption></figure><p>Protecting individual paths matters, but applying this protection everywhere has a measurable cost in latency.</p><h3>Strong consistency has a coordination cost</h3><p>When a candidate says “we’ll use strong consistency everywhere,” I usually ask them to account for p99 write latency under synchronous replication. Across nodes, strong consistency requires coordination. That might mean synchronous replication, a consensus protocol like<a href="https://raft.github.io/"> Raft</a>, or a distributed lock with fencing tokens around a shared resource.</p><p>That coordination shows up directly in the latency budget. It usually rises as <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/cap-vs-pacelc-theorem-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">network distance</a> increases, more participants sit inside the consistency boundary, or the write path depends on a slow acknowledger.</p><p>The impact usually appears in two ways.</p><ul><li><strong>Slower p99 writes:</strong> In <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/coordination-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">quorum-based systems</a>, each write waits for enough acknowledgments to commit. The slowest required acknowledger often dominates tail latency.</li><li><strong>Higher availability sensitivity:</strong> A slow leader, a degraded follower, a network partition, or any slow quorum member on the critical path can push every write into a slower path.</li></ul><p>I’ve watched this happen in production. A single degraded node in a three-node group turned a 15 ms p99 into 35 ms. The issue was not a code change or a traffic spike. The slow node was frequently on the critical quorum path because of leader placement and elevated latency on one follower.</p><p>This is why consistency strategy is about limiting strict guarantees to the narrowest path that protects an important invariant. You avoid forcing every path through the strictest guarantee unless the invariant requires it. Make the balance-deduction path linearizable. Let notification fan-out be eventually consistent.</p><blockquote><strong>Watch out:</strong> Cross-region consensus can easily push p99 write latency past 200 ms, depending on region placement, quorum requirements, and network variance. That may be acceptable for a money movement path. It is usually wasteful for a feed, notification, or analytics read.</blockquote><p>The diagram below shows how expanding the consistency boundary can affect p99 write latency.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QqmxYmRFk1cfSCrOyifwRQ.png" /><figcaption>How expanding the consistency boundary affects p99 write latency (ms)</figcaption></figure><blockquote><strong>Illustrative only: </strong>Actual latency depends on quorum placement, leader location, network distance, fsync behavior, batching, and slow acknowledgers. In many quorum-based systems, moving from 3 to 5 to 7 participants increases the quorum size, so the cost changes in steps rather than as a smooth line.</blockquote><p>Knowing this cost is what makes the choice defensible. You can apply a strict guarantee only after you know which invariant justifies paying for it.</p><h3>Start with the invariant, then choose the mechanism</h3><p>The answer that holds up under follow-up questioning starts with the invariant, not the mechanism. Name <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/functional-vs-non-functional-requirements-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">what must remain true</a> before choosing a database, cache, queue, or replication strategy.</p><p>For example, a user’s balance must never go negative. Two riders must never be assigned the same driver at the same time. An inventory reservation must not sell more units than exist for a SKU.</p><blockquote><strong>Practical tip:</strong> If you cannot name the failure you are preventing, you probably do not need the strictest consistency guarantee yet.</blockquote><p>Once the invariant is clear, describe the observable failure if consistency is relaxed. The system may duplicate a debit, double-book a driver, oversell inventory, or show stale state on a confirmation page. Now the mechanism has a job. It pays a specific coordination cost to prevent a specific failure.</p><p>A useful consistency answer follows four steps:</p><ul><li><strong>Invariant:</strong> What must remain true?</li><li><strong>Observable failure:</strong> What breaks if the guarantee is too weak?</li><li><strong>Proportionate mechanism:</strong> What is the narrowest mechanism that protects that invariant?</li><li><strong>Explicit cost:</strong> What latency,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/availability?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> availability</a>, or operational cost are we accepting?</li></ul><p>The mechanism should match the risk:</p><ul><li><strong>Balance path:</strong> Idempotent ledger write with a serializable transaction, CAS, or a consensus-backed write path.</li><li><strong>Assignment path:</strong> Conditional write on the driver or ride partition, backed by a leader or consensus group for that partition.</li><li><strong>Activity feed:</strong> Async fan-out or pull-based aggregation with dedupe, soft ordering, and cache <a href="https://redis.io/docs/latest/commands/ttl/">time-to-live (TTL)</a>.</li></ul><p>Data consistency is not a system-wide setting. It is a per-invariant decision. In your next design discussion, write down the invariant before choosing the storage or replication path. Then name the failure you are preventing and the cost you are willing to pay. That habit leads to systems whose guarantees match the failures users actually notice.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1ebaff632bb5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why interview frameworks matter when pressure kicks in]]></title>
            <link>https://medium.com/@fahimulhaq/why-interview-frameworks-matter-when-pressure-kicks-in-97e1fc5331ac?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/97e1fc5331ac</guid>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Tue, 09 Jun 2026 04:06:00 GMT</pubDate>
            <atom:updated>2026-06-09T04:06:00.588Z</atom:updated>
            <content:encoded><![CDATA[<p>Over the past several years, I’ve conducted<a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> System Design interviews</a> for backend and infrastructure roles, and I keep seeing the same pattern. A candidate with real production experience starts naming<a href="https://redis.io/"> Redis</a>,<a href="https://kafka.apache.org/"> Kafka</a>, and<a href="https://www.postgresql.org/"> Postgres</a> within the first ninety seconds.</p><p>At that point, we have not defined the <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/functional-vs-non-functional-requirements-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">requirements</a>. We have not discussed access patterns. We have not clarified consistency expectations, latency targets, or failure tolerance.</p><p>The candidate is not lacking knowledge. Under interview pressure, engineers tend to default to familiar components instead of the decisions that should come first.</p><p>That shortcut creates contradictions later:</p><ul><li>A<a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-cache?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> cache</a> chosen before the read-write ratio is understood has no clear invalidation strategy.</li><li>A queue introduced before ordering and retry semantics are defined may undermine correctness rather than improve resilience.</li><li>A<a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-databases?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> database</a> choice made before the workload shape is clear may conflict with the scaling path the interviewer asks about five minutes later.</li></ul><p>The problem is sequencing, not knowledge. A repeatable decision structure helps because it makes the implicit reasoning engineers use in real systems visible under pressure. Without that structure, each follow-up becomes a local patch that can contradict an earlier assumption.</p><p>The following comparison uses the same components in both paths. The only variable is the order in which decisions get made.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eCFFyxrIdGlB6eBbO9P0gg.png" /><figcaption>How decision order changes a System Design interview</figcaption></figure><p>The same components can produce a coherent or unstable answer depending on when they enter the conversation. That is why component choice is a weak starting point for a<a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> System Design</a> interview.</p><h3>Wrong decision order makes good components look weak</h3><p>I’ve watched this failure mode dozens of times. A candidate clearly understands brokers, gateways, workers, and replicas, but the answer becomes hard to defend because those components appear before the decisions that should constrain them.</p><p><a href="https://www.educative.io/courses/grokking-system-design-fundamentals/database-partitioning-and-sharding-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Partitioning</a> is proposed before access patterns are named. Consistency guarantees are promised before the candidate has established which reads and writes actually need those guarantees.</p><p>I’ve made this mistake myself. In an early architecture review, I proposed horizontal sharding before we had defined write ownership, and we spent months undoing that decision. Sharding was not the wrong choice. It was the wrong choice at that point in the reasoning.</p><p>This ordering matters architecturally:</p><ul><li>Partitioning chosen too early can create hotspots, force cross-shard reads, or make resharding expensive. User ID partitioning may fit per-user timelines, but it can struggle with high-fanout accounts. Time-based partitioning may fit append-heavy streams, but it can make user-scoped reads more expensive.</li><li>Consistency promised too broadly creates latency commitments before critical paths are known.<a href="https://www.educative.io/courses/grokking-the-system-design-interview/spectrum-of-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> Linearizable reads and writes</a> across geographically distributed replicas often require cross-region coordination, which can add 50 to 200 ms per write depending on inter-region round-trip time (RTT),<a href="https://www.educative.io/courses/grokking-system-design-fundamentals/coordination-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> quorum configuration</a>, and leader placement.</li><li>Correctness requirements vary by path. Cross-region coordination may be acceptable for ledger updates or payment confirmation, where correctness and idempotency dominate latency. It is usually too expensive for feed rendering, where every extra network round-trip competes with page-load latency.</li></ul><blockquote><strong>Watch out:</strong> The interviewer does not penalize the component. They penalize the inverted reasoning chain, which signals that the candidate might make the same mistake in a real architecture review.</blockquote><p>The table below maps four common decisions to their outcomes when made too early versus when made after the right constraints are known.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R1BfeoSmSCGUvGwnaDocVg.png" /></figure><p>The next failure mode appears when the interviewer starts changing the workload or failure model. If the original answer was built from premature component choices, each follow-up forces a local patch instead of a coherent design update.</p><h3>How follow-ups expose unanchored designs</h3><p>Answers usually break when each follow-up changes one local decision without updating the rest of the system. The original design may have been reasonable, but the reasoning stops holding together.</p><p>From the interviewer’s chair, the pattern is easy to spot:</p><ul><li>Retries are added without <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">retry budgets</a>, exponential backoff, jitter, idempotency keys, or acknowledgment of downstream saturation risk.</li><li><a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Read replicas</a> are introduced without addressing replication lag, read-after-write expectations, failover behavior, or which requests must still hit the primary.</li><li><a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Low latency</a> and strong consistency are promised across regions without explaining where coordination happens or which operations actually need that guarantee.</li></ul><p>Each addition sounds defensible in isolation. Together, they create a design that contradicts itself. The candidate treats follow-ups as isolated patches instead of tests of whether the original reasoning can absorb new constraints.</p><blockquote><strong>Note:</strong> A design without explicit upstream decisions has no anchor. Every follow-up becomes a threat to earlier assumptions instead of an extension of the same logic.</blockquote><p>I’ve seen the same pattern in production reviews. In one migration, a team added a Redis cache to a write-heavy service without revisiting the consistency model.</p><p>The cache was invalidated outside the database transaction boundary. Writes committed, but cache entries sometimes survived until their TTL expired. Readers saw stale state after successful updates.</p><p>Redis was not the problem. The cache entered the design before the team had answered four basic questions:</p><ul><li>Which reads required consistency, and which could tolerate staleness?</li><li>Which writes needed read-after-write behavior?</li><li>Would invalidation happen inside the write path or asynchronously?</li><li>What was the maximum acceptable TTL window?</li></ul><p>The diagram below traces how this plays out across three follow-up questions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8GN-Dpq9iBqN226NxQG7FQ.png" /><figcaption>How local fixes compound without upstream decisions</figcaption></figure><p>The way out is a decision sequence that keeps reasoning anchored as complexity grows.</p><h3>Decision ordering reduces cognitive load</h3><p>A repeatable decision sequence reduces cognitive load because it constrains downstream choices before components enter the discussion. In interviews and architecture reviews, the core question is the same. Were the constraints established before the <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/the-art-and-science-of-technology-selection-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">tools were selected</a>?</p><p>Here is the sequence I recommend:</p><ul><li><strong>Workload shape:</strong> Classify each major path independently. A system can be write-heavy on ingestion, read-heavy on dashboards, and bursty during campaigns.</li><li><strong>Critical path:</strong> Identify the one or two paths where latency or correctness requirements are hardest to relax. Everything else can usually tolerate relaxed guarantees.</li><li><strong>State boundaries:</strong> Decide what is owned where. Stateless components are easier to scale horizontally, while stateful components need clear ownership, replication, failover, and recovery decisions.</li><li><strong>Failure tolerance:</strong> Decide what can be retried, what can be dropped, and what must be durable. This shapes <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-messaging-queue?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">queue semantics</a>. At-most-once is acceptable for metrics or logs where loss is tolerable. At-least-once delivery requires idempotent consumers. Exactly-once usually requires transactional writes, deduplication, or carefully scoped semantics.</li><li><strong>Scaling direction:</strong> Choose vertical scaling, horizontal scaling, sharding, replication, or buffering after state and failure decisions are clear.</li></ul><blockquote><strong>Practical tip:</strong> This is not a linear checklist. It is a dependency graph.<a href="https://www.educative.io/courses/grokking-system-design-fundamentals/scaling-trade-offs-when-to-cache-queue-replicate-or-shard?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096"> Scaling direction</a> depends on state boundaries and failure tolerance together. Internalize the dependencies, not just the order.</blockquote><p>Take a notification delivery service. Ingestion is write-heavy and bursty during campaign sends. The hard requirement is confirming delivery acceptance, not updating read receipts in real time.</p><p>That tells us the queue owns buffering and delivery attempts, while a durable store owns confirmations and audits. The hot path can usually acknowledge acceptance after durably enqueueing the notification instead of blocking on a synchronous database write.</p><p>Once these decisions are explicit, follow-ups become easier to handle. If reads increase 10x, first ask which reads increased. Status polling might justify read replicas or a cache with a defined staleness window. Delivery confirmation reads should stay on the source of truth or route through a path with stronger consistency.</p><p>The visual below shows why component selection should come after constraints. Components are leaves of the decision graph, not the root of the design.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fNj6t1nelpWpWFXpMpOVbQ.png" /><figcaption>How constraints narrow component choices</figcaption></figure><p>That dependency-first habit is not unique to interviews. It is close to how experienced engineers debug production systems.</p><h3>Production debugging makes frameworks durable</h3><p>When a system fails at scale, the starting point is the <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/types-of-failures-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">failure boundary</a>, not a new component. You observe symptoms, isolate the affected path, reason across dependencies, and then decide what to change.</p><p>That sequence maps closely to interview reasoning:</p><ul><li>Symptoms map to requirement framing. What should the system do, and which constraints matter most?</li><li>Boundaries map to bottleneck identification. Which path is under pressure? Reads, writes, fan-out, storage, coordination, or delivery may each point to a different bottleneck.</li><li>Dependencies map to <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/system-design-trade-offs?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">trade-off and failure-mode analysis</a>. What happens if a dependency slows down, drops messages, returns stale data, or becomes unavailable?</li><li>Fixes map to component selection. Choose tools after the constraints and failure modes are visible.</li></ul><blockquote><strong>Watch out:</strong> Strong interviewers are not testing whether you know what Kafka does. They are testing whether you reason about systems the way someone who has debugged them under pressure actually thinks.</blockquote><p>A framework prevents a common failure mode where an engineer has enough knowledge to build the system, but cannot make the reasoning legible under <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-mock-interviews?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">active questioning</a>.</p><p>Before your next design discussion, write down the five upstream decisions that shape the rest of the system. Start with workload shape, critical path, state boundary, failure tolerance, and scaling direction. When a follow-up changes the workload, update the relevant upstream decision first, then adjust the components. That habit keeps your design coherent under pressure.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=97e1fc5331ac" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The Amazon OA cheat sheet: Every pattern I’ve seen in 2026]]></title>
            <link>https://medium.com/@fahimulhaq/the-amazon-oa-cheat-sheet-every-pattern-ive-seen-in-2026-065ef2abd575?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/065ef2abd575</guid>
            <category><![CDATA[aws]]></category>
            <category><![CDATA[amazon]]></category>
            <category><![CDATA[coding-interviews]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 08 Jun 2026 06:16:28 GMT</pubDate>
            <atom:updated>2026-06-08T06:16:28.653Z</atom:updated>
            <content:encoded><![CDATA[<p>If you have been preparing for the Amazon online assessment by memorizing LeetCode templates, there is a good chance you are optimizing for the wrong thing.</p><p>The Amazon OA still rewards strong algorithm fundamentals, but it is no longer helpful to think of it as a pure pattern-recall test. The assessment is designed to evaluate how you solve problems under pressure, how you adapt when a familiar pattern changes, and how well your technical decisions reflect sound judgment. That is why a useful Amazon OA cheat sheet cannot just be a list of problems to memorize. It has to help you recognize problem shapes, respond to twists, and prepare for the broader assessment around the coding questions.</p><p>This guide is built around that goal. First, it explains what the Amazon online assessment looks like in 2026. Then it covers the Amazon OA patterns that still show up most often. After that, it looks at why the questions feel harder now, especially when standard solutions stop working cleanly. Finally, it gives you a four-week preparation plan that helps you build the kind of skill that carries into later interviews too.</p><p>If you want the broader context on why the assessment feels different now, I’ve also written about that in<a href="https://medium.com/@fahimulhaq/the-amazon-oa-just-got-way-harder-6e0efe5f7478"> <em>The Amazon OA just got way harder. Here’s why</em></a>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*k5j5zQME4aSh-YAvSUVMTw.png" /></figure><h3>What the Amazon online assessment includes in 2026</h3><p>Before getting into Amazon OA patterns, it helps to answer the more basic question many candidates are actually searching for first: what is in the Amazon online assessment in 2026?</p><p>The exact structure varies by role, but the key point is that the OA is not just a coding round. Amazon’s official<a href="https://www.amazon.jobs/content/en/how-we-hire/university/sde-oa"> SDE online assessment prep</a> page makes it clear that this is an early hiring step designed to assess more than syntax and speed. For some roles, candidates may also encounter work style or systems-oriented components. Amazon’s<a href="https://www.amazon.jobs/content/en-gb/how-we-hire/sde-ii-oa-prep"> SDE II online assessment prep</a> page reinforces that broader picture by describing technical questions alongside systems design and work style evaluation.</p><p>That matters because most people searching for <em>amazon online assessment 2026</em> are not only asking what coding questions to practice. They are usually trying to understand four things at once: what the OA format looks like, what coding patterns matter most, how Leadership Principles connect to the test, and how to prepare without wasting time.</p><p>That broader intent is also why the strongest pages on this topic do more than list coding patterns. They explain the assessment as a whole, then show where the patterns fit inside it. That is the structure this guide follows too.</p><h3>Why Amazon OA patterns matter more than memorized solutions</h3><p>Most candidates practice in a way that feels productive but does not transfer well to the real test. They solve one sliding window question, one BFS problem, one DP problem, and assume that exposure is enough.</p><p>Usually, it is not.</p><p>The real skill is not recognizing a question you have seen before. It is recognizing the shape of a question you have not seen before. When you read a problem, the first useful question is not “Have I done this exact one?” It is “What kind of problem is this?”</p><p>Is it about a contiguous range? A repeated decision? Fast lookup? Connectivity? A recursive hierarchy?</p><p>That classification step matters because it often determines whether you move cleanly toward a solution or spend twenty minutes trying the wrong idea. This is the practical value of learning Amazon OA patterns well. They are not there to give you scripts. They are there to help you see structure faster.</p><h3>The five Amazon OA patterns to know in 2026</h3><p>Across recent prep material and candidate experience, five pattern families continue to show up frequently in Amazon-style OA questions: sliding window, dynamic programming, hash map lookups, graph traversal, and tree recursion.</p><p>These patterns matter not because Amazon only asks them, but because many OA questions still fall into one of these buckets once you strip away the story around them.</p><h3>1. Sliding window</h3><p>Sliding window problems usually involve contiguous subarrays or substrings. The question often asks you to maximize, minimize, or validate something within a continuous range.</p><p>A classic example is the longest substring without repeating characters. The important signal is not the exact example, but the fact that the answer depends on maintaining a valid moving window over a sequence.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hG-hp4By1b7zwDP2PoOhzA.png" /></figure><p>The most common mistake is moving the boundaries mechanically without being clear about the rule the window is supposed to preserve. If you do not know the invariant, the code becomes fragile very quickly.</p><h3>2. Dynamic programming</h3><p>Dynamic programming appears when a larger answer depends on smaller overlapping subproblems.</p><p>You usually see this in counting problems, sequence optimization, path choices, or multi-step decisions where a greedy approach is unreliable. The hard part is not usually the loop. It is defining the state correctly and setting the base cases clearly enough that the rest of the solution has a stable foundation.</p><p>A simple way to test your understanding is this: can you explain what each DP entry means in one sentence? If not, the implementation will probably get messy.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xjSWQ6XwhpBmUO1lhdy4Eg.png" /></figure><h3>3. Hash map lookups</h3><p>Hash maps show up when fast lookup changes the problem from repeated searching into efficient tracking.</p><p>Typical signals include frequency counting, pair matching, duplicate detection, grouping, or any problem that depends on whether you have seen something before. Problems like Two Sum are the obvious entry point, but the same pattern appears in far more OA-style questions than many candidates realize.</p><p>The most common mistake is not deciding early enough what information should be stored as you iterate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GZVl4ZotJITSs1d0mut-zg.png" /><figcaption>4. Graph traversal</figcaption></figure><p>Graph traversal shows up whenever a problem is really about relationships, reachability, movement, dependency, or connectivity.</p><p>Sometimes the input says “graph” directly. Often it does not. A grid can behave like a graph. A flight network can behave like a graph. A list of dependent tasks can behave like a graph. Once you see that structure, the right question becomes whether BFS or DFS is the better fit.</p><p>The common mistake is incomplete traversal logic, especially around visited state or disconnected components.</p><h3>5. Tree recursion</h3><p>Tree recursion appears when the input is hierarchical and the answer depends on combining results from child nodes.</p><p>This often includes problems involving depth, path sums, traversal order, subtree checks, or validation. The key challenge is not writing recursive code for the sake of it. It is being precise about what each recursive call returns and what should happen at the base case.</p><p>Many tree mistakes are small but fatal. A weak base case can quietly break the whole solution.</p><h3>A quick Amazon OA cheat sheet for pattern classification</h3><p>This table is most useful as a recognition aid. It is not meant to be memorized line by line. Its job is to shorten the gap between reading a problem and choosing a sensible direction.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6KtkSNEvG_xPEGRl58yOpg.png" /></figure><p>Once you can classify problems this way, the next useful question is not which template to copy. It is whether the standard pattern still holds when the problem changes slightly.</p><h3>Why the Amazon OA feels harder now</h3><p>This is where a lot of candidates get caught. They recognize a problem as “probably sliding window” or “probably DFS,” but the usual version of the pattern does not fit as neatly as expected.</p><p>That is part of why the Amazon OA feels harder now.</p><p>The challenge is not that the patterns disappeared. It is that many questions now force you to reason through the pattern instead of applying it mechanically. A sliding window may have a condition that changes mid-stream. A graph problem may hide its structure inside an unfamiliar setup. A recursion problem may include an edge case that breaks the most obvious traversal.</p><p>Part of that shift is likely a response to AI-assisted interview prep. When standard solutions are easier to generate with LLMs, assessments become more useful when they reward adaptation rather than recall. That does not make core patterns less important, but it does make surface-level memorization far less reliable.</p><p>This is the difference between memorizing patterns and understanding them.</p><p>A candidate who memorized five templates can handle the clean version of five problem types. A candidate who understands why a pattern works can still move forward when one assumption changes. That is the skill that seems to matter more now, and it is the one worth practicing on purpose.</p><h3>How to practice for Amazon OA questions that are harder to pattern-match</h3><p>The most useful way to prepare for this kind of question is not simply to solve more random problems. It is to make solved problems unstable on purpose.</p><p>After you solve a standard problem, change one meaningful constraint and solve it again from scratch. That forces you to move from recall into reasoning.</p><p>A few examples work especially well:</p><ul><li>change a fixed-size window into a variable one</li><li>turn a maximize problem into a counting problem</li><li>introduce negative values, duplicates, or awkward edge cases</li><li>remove extra memory and ask whether the solution still holds</li><li>hide a graph structure inside a grid or rule-based problem</li></ul><p>This kind of practice feels slower, but it is often much more valuable. It helps you see what your solution was actually relying on instead of just proving that you could reproduce it once.</p><p>A good question to ask after every solved problem is this: What assumption is my solution quietly depending on? Once you know that, you know what to test next.</p><h3>Why Leadership Principles matter during the OA</h3><p>Most candidates treat Amazon’s Leadership Principles as something to think about later, when the behavioral interview shows up. That is a mistake.</p><p>Amazon’s <a href="https://www.amazon.jobs/en/principles">Leadership Principles</a> are not just behavioral talking points. They reflect how Amazon thinks about decision-making, prioritization, ownership, and judgment. That shows up earlier than many candidates expect, especially in work style components and in the choices you make under technical pressure.</p><p>For example, clarifying the real requirement before optimizing often reflects better judgment than jumping into the most clever possible solution. Handling edge cases early shows a kind of depth that matters. Choosing a practical approach under time pressure can say more about how you work than writing something elegant too late.</p><p>The point is not to perform alignment. The point is to let the principles sharpen how you make decisions when the problem is messy.</p><h3>A four-week Amazon OA prep plan that actually helps</h3><p>The best prep plan is one that helps you pass the OA without training bad habits for the rounds that come later.</p><h3>Week 1: Learn the patterns properly</h3><p>Start with the five major pattern families and solve a few classic problems from each one. Three or four per pattern is enough to begin with.</p><p>But do one extra thing before coding: write a sentence explaining why the pattern fits that problem. That builds the classification muscle that many candidates skip.</p><p>If your language fluency still slows you down, it helps to fix that early. Structured resources like <a href="https://www.educative.io/courses/grokking-coding-interview?utm_campaign=persona_coding_interview_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Grokking the Coding Interview Patterns</a> can help build that foundation before you move into timed OA-style work.</p><h3>Week 2: Practice constraint mutation</h3><p>Take the same problems from week 1 and change one important condition in each. Then solve them again without looking at your earlier code.</p><p>This is where recognition starts becoming real understanding. It is also the week many candidates rush, even though it is often the highest-leverage part of prep.</p><h3>Week 3: Run timed simulations</h3><p>Now shift into test conditions. Do full mock sessions under a realistic time limit.</p><p>This week is about decision-making as much as correctness. When should you move on? When is a brute-force starting point good enough? When are you polishing too much? Those choices are part of the assessment too.</p><p>If you want more realistic interview reps at that stage, tools like <a href="https://www.mockinterviews.dev/">MockInterviews.dev</a> can help you practice in a more interview-like setting with timed coding and feedback.</p><h3>Week 4: Connect solutions to judgment</h3><p>In the final week, keep doing timed practice, but add a short reflection after each session.</p><p>Ask yourself why you chose your first approach, what trade-off you were making, which edge case you caught, and which one you missed. That habit helps you prepare not just for the OA, but for explaining your thinking later in interviews.</p><h3>How to prepare for the Amazon OA without over-optimizing for the wrong thing</h3><p>There is one trade-off that does not get discussed enough in interview prep: you can prepare for the OA in a way that helps you pass the screen, but weakens you for the interviews that come next.</p><p>That usually happens when prep becomes too centered on speed, memorized templates, and narrow pattern recall. You may get better at identifying familiar questions quickly, but worse at explaining why your solution works, what trade-offs it makes, and how you would adapt it under different constraints.</p><p>A better approach is quieter and more durable. Learn the common <a href="https://www.educative.io/blog/crack-amazon-coding-interview-questions?utm_campaign=persona_coding_interview_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_june&amp;eid=5082902844932096">Amazon OA patterns</a>. Practice classifying them. Then deliberately test what happens when the problem changes. That is the habit that builds real problem-solving skill.</p><p>The practical takeaway is simple: do not use an Amazon OA cheat sheet as a list to memorize. Use it as a tool to see the shape of a problem faster and think through it more clearly. That is what helps you get through the assessment, and it is also what makes the rest of the interview process easier to handle.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=065ef2abd575" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Evaluating database tradeoffs under interview pressure]]></title>
            <link>https://medium.com/@fahimulhaq/evaluating-database-tradeoffs-under-interview-pressure-3053be956e2e?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/3053be956e2e</guid>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[design-thinking]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Tue, 02 Jun 2026 05:05:02 GMT</pubDate>
            <atom:updated>2026-06-02T05:05:02.647Z</atom:updated>
            <content:encoded><![CDATA[<p>Most candidates I interview name a database within the first two minutes of a<a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> System Design interview</a>. Before they ask about read-to-write ratio, access cardinality, or consistency requirements, they have already committed to<a href="https://www.postgresql.org/"> Postgres</a>,<a href="https://cassandra.apache.org/_/index.html"> Cassandra</a>, or<a href="https://aws.amazon.com/dynamodb/"> DynamoDB</a>. That shortcut usually breaks on the first follow-up question.</p><p>Over the years of running staff-level interviews, I kept seeing the same pattern. A candidate hears “design a ride-sharing backend” and says, “I’ll use PostgreSQL for the core data.” The choice might still be reasonable. The problem is that the workload reasoning is missing, so the answer becomes hard to defend under pressure.</p><p>The root problem is treating storage as a preference instead of a constraint. The workload should force the choice. A poor document-model fit pushes joins into the application layer. A<a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-databases?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> relational database</a> used for bursty event ingestion can run into write bottlenecks or connection pressure unless the pipeline is built around batching or buffering.</p><blockquote><strong>Note:</strong> A weak database choice usually shows up first as an operational symptom, not an abstract design flaw. Common early signs include application-side joins, pool exhaustion,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-partitioning?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> hot partitions</a>, and write bottlenecks.</blockquote><p>These are not hypothetical risks. I made this mistake myself by defaulting to Postgres for an event ingestion pipeline, and I spent two quarters dealing with connection pool exhaustion.</p><p>Naming an engine before naming the workload is a reasoning inversion. As traffic grows, it tends to surface as schema workarounds, overloaded indexes, hot partitions, or pool saturation. The candidates who impress me are the ones who wait until they can explain what the storage layer actually needs to do.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*En06ShSlWRGd9O4_1EreqA.png" /><figcaption>Trend-driven vs. workload-driven database selection</figcaption></figure><p>The difference between these two paths is not the database name. It is whether the workload was defined before the engine was chosen.</p><h3>Mapping access patterns to database selection</h3><p>When I coach candidates through<a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> System Design</a> questions, I push them to delay naming a technology until they can describe the dominant read and write paths, the relationship cardinality, and the <a href="https://www.educative.io/courses/grokking-the-system-design-interview/spectrum-of-consistency-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">consistency guarantees</a> the system actually needs. That is the real design work. The engine choice should come out of that analysis, not lead it.</p><p>Three workload shapes usually make the tradeoff clear.</p><ul><li><strong>Time-ordered scans:</strong> These often favor wide-column stores that preserve sort order within a partition through partition and clustering keys, making range reads efficient without heavy secondary-index dependence. Time-series databases offer similar advantages for metrics-style workloads. Relational tables can still work when the timestamp is the leading index key, but scan and index costs grow as data volume increases.</li><li><strong>High-throughput key-value writes:</strong> These often fit log-structured merge-tree (LSM) based engines because writes can be buffered in memory and flushed sequentially to disk. That improves sustained write throughput, but the tradeoff is higher read amplification and compaction overhead under load.</li><li><strong>Multi-row transactional invariants:</strong> These push you toward engines that can enforce atomic updates across related records with strong concurrency control. In practice, that usually means a relational database or a distributed SQL system, depending on the scale and consistency requirements.</li></ul><blockquote><strong>Practical tip:</strong> Before naming any engine, write down the three dominant query shapes. If you cannot name them clearly, you are probably not ready to choose storage.</blockquote><p>Once that workload shape is clear, partitioning and indexing decisions become much easier to justify. If you skip that step and choose the engine first, you usually end up paying for it later through awkward <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/indexing-and-query-optimization-in-database-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">secondary indexes</a>, hot partitions, or schema workarounds.</p><h3>Failure modes reveal whether the choice was grounded</h3><p>The real test of a <a href="https://www.educative.io/courses/grokking-the-system-design-interview/trade-offs-in-databases?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">database choice</a> is not how clean it sounds on the whiteboard. It is what breaks first under load. I have seen this in postmortems and in interviews where a design sounded fine until someone pushed it past the happy path.</p><p>A pattern usually shows up quickly.</p><ul><li>Rising p99 latency often appears first. In relational systems, this may come from lock contention or index scans that grow with data volume. In partitioned systems, it often comes from poor key distribution that creates hot shards.</li><li><a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">Retry storms</a> tend to follow when the write path starts timing out or conflicting under contention. This gets worse when coordination lives in the application layer, and the retry logic was never built for sustained concurrency.</li><li>Stale reads show up when replicas fall behind, or failover shifts reads to lagging replicas. Whether users see stale data depends on the <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">replication</a> design and the consistency guarantees the system exposes.</li></ul><p>Connection pressure deserves special attention because it often looks like a database scaling problem when it is really a concurrency problem. A relational database can be the right choice and still fail during traffic spikes if the pool is sized for average load instead of server capacity.</p><p>In PostgreSQL, each backend connection consumes server memory and scheduling overhead, so the practical ceiling is set by database capacity, not by how many application threads are waiting to connect. One common mistake is setting max_connections based on application thread count rather than what the database can actually sustain.</p><blockquote><strong>Note:</strong> Candidates who can explain which bottleneck appears first, and why, usually show much better judgment than candidates who only recite consistency models.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*85drOyuTG3dghzFck45MKA.png" /><figcaption>Operational symptoms that reveal workload-database mismatch</figcaption></figure><p>Once those symptoms are visible, the next question is which engine choices make them more or less likely for a given workload.</p><h3>Concrete scenarios make tradeoffs legible</h3><p>Abstract comparisons between<a href="https://www.educative.io/courses/grokking-system-design-fundamentals/sql-nosql-and-newsql-databases-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> SQL, NoSQL, and distributed SQL</a> usually stay shallow. A concrete scenario forces each option to respond to a real bottleneck instead of a product label.</p><p>Take a ride-sharing platform. The ride-assignment path and the event-ingestion path are different workloads, so they may justify different databases.</p><p>For the ride-assignment path, a relational database is often the cleanest fit because it can enforce assignment invariants with <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/acid-vs-base-database-transaction-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">transactions</a>, constraints, and concurrency control. The cost is that sustained write growth can put pressure on the primary, indexes, or connection handling.</p><p>For the event-ingestion path, a write-optimized distributed store may be a better fit because the priority is sustained write throughput, not multi-row transactional guarantees. The cost is weaker joins, more application-side coordination, and more sensitivity to partition-key design.</p><blockquote><strong>Watch out:</strong> Distributed SQL is not a free upgrade from relational. In <a href="https://www.educative.io/courses/distributed-systems-practitioners?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">distributed systems</a>, stronger consistency at scale usually comes with quorum coordination, higher operational complexity, and potentially higher cross-region latency.</blockquote><p>That is the point I want candidates to make in interviews. A strong answer does not just name PostgreSQL, Cassandra, or <a href="https://cloud.google.com/spanner">Spanner</a>. It explains which path each engine fits, what cost it accepts, and what is likely to break if the workload changes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*vn8JmN-w8PCsAbI3YSer2A.png" /><figcaption>How workload paths justify different database choices</figcaption></figure><p>Once those tradeoffs are tied to a concrete workload, the interview answer becomes much easier to explain under follow-up pressure.</p><h3>Workload-first communication under pressure</h3><p>The structure I recommend is simple. State the dominant access pattern. Name the consistency requirement. Identify the main <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/scaling-trade-offs-when-to-cache-queue-replicate-or-shard?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">scaling axis</a>. Then choose the engine that fits those constraints and name the tradeoff it accepts.</p><p>That format holds up well in interviews because it is built on constraints, not preference. If write volume doubles, you already know which bottleneck to examine first. If the interviewer asks about tenant skew,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> replica lag</a>, or future migration, the workload analysis gives you a starting point instead of forcing you to defend a product choice you named too early.</p><p>I have seen this difference clearly in both interviews and production work. Engineers who reason from workload constraints usually give stronger answers than engineers who start with familiar technology names. The same habit also prevents expensive rewrites later, because it forces the tradeoff into the open before the system grows around the wrong assumption.</p><p>The goal is not to memorize a better database answer. It is to make the reasoning repeatable. Start with the workload. Name the constraint.<a href="https://www.educative.io/courses/grokking-system-design-fundamentals/the-art-and-science-of-technology-selection-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> Choose the engine</a>. Then explain the cost.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3053be956e2e" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[What building distributed systems taught me about hiring]]></title>
            <link>https://medium.com/@fahimulhaq/what-building-distributed-systems-taught-me-about-hiring-1c2eb526cec7?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/1c2eb526cec7</guid>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[systems-thinking]]></category>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 01 Jun 2026 07:26:51 GMT</pubDate>
            <atom:updated>2026-06-01T07:26:51.956Z</atom:updated>
            <content:encoded><![CDATA[<p>A cache time to live (TTL) set to 60 seconds. A <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">read replica</a> running about 90 seconds behind. Both settings were reasonable in isolation. Together, they created a stale-read window after every write.</p><p>We were serving reads from the replica, and cache refills came from that same lagging path. So even after a write committed on the primary, order totals could still show yesterday’s pricing for more than a minute. Downstream systems treated the mismatch as a data inconsistency, and reconciliation logic started flagging records that were valid but temporally out of sync.</p><p>That kind of failure changed what I look for in <a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">System Design interviews</a>.</p><p>I no longer optimize for candidates who can design a clean component in isolation. I look for engineers who ask about contracts between components,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/the-spectrum-of-failure-models?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> failure boundaries</a>, and what happens when two reasonable defaults interact under load. That instinct usually comes from seeing real systems fail at the seams, not from drawing neat diagrams.</p><p>Production systems usually do not break because a single component fails. They break when retries, replicas, caches, brokers, and services interact under stress. That is the reasoning I want to test in interviews.</p><blockquote><strong>Note:</strong> Boundary failures often slip past unit tests and simplified load tests because each component is exercised in isolation. They usually surface only when realistic failure conditions cross service boundaries.</blockquote><p>The following diagram shows where production failures usually occur and where interview answers often stop.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*JYsh5itCIq-LI1-jkzY-3w.png" /><figcaption>Where production failures emerge vs. where interview answers usually focus</figcaption></figure><p>With that framing established, the next pattern I look for is how candidates reason about amplification effects.</p><h3>Retry behavior exposed second-order thinking</h3><p>An engineer sees a transient failure and reaches for the obvious fix of adding a retry. At the component level, that instinct makes sense. But uncoordinated retries against a saturated dependency do not heal the system. They amplify load, exhaust connection pools, and turn a brief <a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">latency spike</a> into a broader outage.</p><p>The strongest candidates do not stop at the retry itself. They ask whether the operation is <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">idempotent</a>, whether the retry budget fits inside the latency budget, and whether the backoff policy includes jitter.</p><p>Capped exponential backoff with jitter is often a reasonable starting point, but the right values still depend on the service-level objective (SLO), the cost of failure, and how quickly the downstream service can recover.</p><p>They also ask about the recovery window. If a dependency needs a minute to stabilize under load, an aggressive retry policy can keep reintroducing traffic and stretch the incident out.</p><p>That is the reasoning pattern I look for in interviews. Strong candidates ask what the fix will do to connection pools, dependency saturation, and recovery time. Knowing the <a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker">circuit breaker pattern</a> by name is not the signal. Reasoning about amplification effects before touching the code is.</p><blockquote><strong>Watch out:</strong> Exponential backoff without jitter can create retry waves at scale. Clients back off on similar schedules, hit the same ceiling, and reproduce the original load spike together.</blockquote><p>That second-order habit becomes even more critical when the system is mid-transition.</p><h3>Partial rollouts revealed transition-state judgment</h3><p>Some of the most painful incidents I have seen did not happen in the old state or the new state. They happened in the transition window where both coexisted.</p><p>A schema migration where old readers and new writers interpret the same column differently. A rolling deploy where version N and version N+1 disagree on a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/data-serialization-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">serialization format</a> for ten minutes. These are common failure modes in non-atomic deployments unless compatibility is designed explicitly.</p><p>How candidates reason about them is one of the most reliable hiring signals I have found.</p><p>Candidates who think only in finished-state diagrams design systems that look correct on a whiteboard but break during rollout.</p><p>The candidates I want to hire can name the dangerous intermediate states without prompting. They talk through rollback sequencing, identify the point after which a revert requires compensating work, and treat <a href="https://www.educative.io/courses/distributed-systems-practitioners/maintaining-backwards-compatibility?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">backward and forward compatibility</a> as first-class constraints.</p><blockquote><strong>Practical tip:</strong> Treat schema changes as compatibility transitions, not single deploys. Expand first, keep reads and writes compatible across versions, backfill carefully, and contract only after all callers have moved. Never change interpretation and data in the same deploy.</blockquote><p>In practice, different systems make different trade-offs during the transition window. Some availability-sensitive systems accept temporary inconsistency and design rollback around it. Others use feature flags or stricter rollout controls to avoid exposing the new path too early.</p><p>The signal I care about in interviews is not whether a candidate picks one universal rule. It is whether they can reason clearly about those trade-offs instead of assuming the deploy will be atomic.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*moCrrMwGDxqfF9E9QoRdwg.png" /><figcaption>The highest-risk part of a rollout is the mixed-version coexistence window</figcaption></figure><p>Transition-state reasoning requires imagining the system mid-flight. Queue lag is what you find when you look at the system after the rollout or launch is over, and most engineers who have not carried a pager have never looked.</p><h3>Queue lag separated feature builders from system owners</h3><p>Queue depth and <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/observability-in-distributed-systems-metrics-logs-and-traces?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">consumer lag</a> are among the most revealing signals in a broker-based system, and among the easiest to miss if you have mostly worked on feature delivery. That difference becomes obvious when I ask what they would monitor after launch.</p><p>Backlog growth usually appears as an operational signal before it becomes a user-facing error. If you are not watching lag and retention headroom, you may not notice until SLOs are at risk or recovery gets expensive. Strong teams catch this earlier by alerting on lag growth, not just the absolute number.</p><p>Engineers who have owned production systems design for that from the start. They add backpressure to slow producers when lag crosses a threshold, use <a href="https://www.rabbitmq.com/docs/dlx">dead-letter queues</a> to isolate poison messages, and tie consumer auto-scaling to lag metrics rather than relying on CPU or memory alone.</p><p>That changed how I structure interview questions. I no longer ask only how a candidate would design the system. I ask what they would monitor after it ships. The answer is often the clearest signal of whether someone has operated a system or only drawn one.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LdnDtbWfSUIuyEfxgYTgrw.png" /><figcaption>Queue lag often appears as an operational signal before it becomes a user-facing failure</figcaption></figure><p>All of these signals converge on a single underlying quality that I now treat as the real bar.</p><h3>Evaluating ownership reasoning became the real bar</h3><p>Distributed systems degrade when interface ownership, source-of-truth decisions, and failure responsibility stay vague. Each team assumes another layer will handle the hard edge case. I have seen that pattern in both small teams and larger platform organizations.</p><p>I look for candidates who ask who owns the contract between two services, what happens when one side changes it unilaterally, and who gets paged when boundary latency or error rates breach the agreed service objective. I also want them to name which trade-off matters most for the workload, whether that is consistency,<a href="https://www.educative.io/courses/grokking-the-system-design-interview/availability?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> availability</a>, latency, or cost.</p><blockquote><strong>Note:</strong> Vague ownership is a<a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096"> System Design</a> risk as well as a process problem. If no one can answer who owns the retry contract between two services, that boundary will fail under load.</blockquote><p>This framework is not perfect. I once hired an engineer who asked all the right boundary questions but struggled to ship incrementally because they over-indexed on failure analysis and under-indexed on pragmatic scoping. No hiring signal is complete.</p><p>But ownership reasoning has been the most reliable lens I have found for predicting who will thrive in complex, failure-prone environments.</p><h3>Distributed systems hiring lessons at scale</h3><p><a href="https://www.educative.io/courses/distributed-systems-practitioners?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_june&amp;eid=5082902844932096">Distributed systems</a> survive because someone owns the boundaries and names the trade-offs. Engineering teams survive for the same reason.</p><p>Hiring has started to look similar to me. The strongest signals usually show up in how candidates reason about failure modes, transition states, and ownership boundaries when the system is under stress, not when the diagram is clean. No signal is perfect, but that has been the most reliable lens I have found.</p><p>The pattern has been consistent. Candidates who ask the best questions about what happens when things go wrong usually bring better production judgment with them. The<a href="https://sre.google/sre-book/table-of-contents"> Google Site Reliability Engineering (SRE) Book</a> is still a useful reference here, especially the chapters on toil and postmortem culture.</p><p>The best hiring decision I can make is not finding the smartest engineer in the room. It is finding the one who asks the best questions about what happens when systems start to fail.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c2eb526cec7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Designing GenAI pipelines in 2026 that survive long-term scale]]></title>
            <link>https://medium.com/@fahimulhaq/designing-genai-pipelines-that-survive-long-term-scale-db5b6c7d56dc?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/db5b6c7d56dc</guid>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[generative-ai-tools]]></category>
            <category><![CDATA[genai]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 25 May 2026 06:43:15 GMT</pubDate>
            <atom:updated>2026-06-01T10:04:20.592Z</atom:updated>
            <content:encoded><![CDATA[<p>The fastest way to ship a <a href="https://www.educative.io/courses/generative-ai-system-design?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">generative AI</a> feature is usually the fastest way to create rewrite debt. That often means calling a model endpoint directly from application code, wrapping it in a prompt template, and deploying before the system has stable boundaries around model access, retries, and response handling.</p><p>I have built systems that way under delivery pressure, and it works until it doesn’t. Provider contracts change, output schemas drift, or tool-call payloads shift, and suddenly multiple services need coordinated fixes.</p><p>That is the real problem. Speed to first demo is not the same as production readiness.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2mexTgmxAvybC5T04JN-3g.png" /><figcaption>Direct provider coupling versus gateway-contained integration</figcaption></figure><p>Without a <a href="https://www.envoyproxy.io/">model gateway</a>, every consumer depends directly on provider behavior. A gateway gives them one normalized interface for auth, routing, retries, and response shaping, so provider changes are contained to one layer instead of rippling through the system.</p><p>The <a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">System Design</a> shortcuts you take in a prototype often become the incidents you deal with at scale.</p><h3>Shared capacity vs. workload-class separation</h3><p>A shared gateway and worker pool usually look efficient early on, when chat requests, background enrichment, and evaluation jobs are still small enough to coexist. The problem starts when teams treat those workloads as one traffic class even though they have very different latency targets, retry behavior, and scheduling needs.</p><p>The warning signs are usually easy to miss:</p><ul><li><strong>Tail latency spikes on interactive paths:</strong> Chat requests start queueing behind long-running batch work that consumes shared concurrency.</li><li><strong>Retry storms from batch jobs:</strong> Enrichment jobs with aggressive retry settings amplify load during contention, especially when retries are poorly staggered.</li><li><strong>Uneven API utilization:</strong> Aggregate token throughput still looks healthy even while interactive SLOs are already slipping.</li></ul><p>I have seen this failure pattern in production. In one production system I worked on, chat traffic and batch enrichment shared the same workers for a period. When enrichment volume spiked during a content update, chat latency jumped from roughly 400 ms to more than 3 seconds. We missed it for nearly two weeks because the overall throughput looked stable.</p><p>That is the trap. Shared capacity can hide user-facing degradation behind healthy aggregate metrics. The fix is separating workload classes before contention turns into an incident. Interactive traffic, batch enrichment, and evaluation jobs need their own queues, concurrency limits, and retry policies. Otherwise, your timeouts and retries will always be wrong for at least one class of work.</p><h3>Retrieval coupling failures vs. graceful degradation</h3><p>This is one of the most underestimated failure modes in <a href="https://www.educative.io/courses/generative-ai-system-design/introduction-to-rag?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">retrieval-augmented generation</a> pipelines. When retrieval, reranking, and generation share one tightly coupled request path, a slowdown in retrieval consumes the shared timeout budget and starts dragging the rest of the pipeline down with it.</p><p>The failure sequence during a reindex window is usually predictable:</p><ul><li><strong>Retrieval latency spikes:</strong> Reindexing competes with live query traffic for I/O and CPU on the <a href="https://www.pinecone.io/">vector store</a>.</li><li><strong>Retries amplify the load:</strong> Timed-out retrieval calls are retried against an already stressed system, especially when retry intervals are aggressive or poorly staggered.</li><li><strong>Downstream capacity gets squeezed:</strong> Worker slots, request deadlines, and outbound client capacity get consumed waiting on retrieval, so generation starts failing even when the model itself is healthy.</li></ul><p>I have seen this pattern in <a href="https://www.educative.io/courses/distributed-systems-practitioners?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">distributed systems</a> long before GenAI. The mechanics are the same here. One slow dependency consumes the headroom for every stage behind it.</p><blockquote><strong>Watch out:</strong> Generation errors during a reindex window are usually a retrieval-coupling problem, not an inference problem. Check retrieval latency before escalating to the model provider.</blockquote><p>That failure path is easier to see when the stages are drawn separately.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*FaiS_VHS5RtNyFCefY9umQ.png" /><figcaption>Failure propagation in coupled RAG versus isolated pipeline with graceful degradation</figcaption></figure><p>The fix is to separate the stages so they can scale, fail, and shed load independently. Retrieval should not be able to stall generation indefinitely. Put queue boundaries between stages and open a <a href="https://martinfowler.com/bliki/CircuitBreaker.html">circuit breaker</a> when retrieval latency or error rate crosses a threshold.</p><p>Then fall back deliberately instead of letting the whole request path collapse. That can mean serving <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-cache?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">cached retrieval results</a>, skipping reranking, lowering retrieval depth, or returning a reduced answer.</p><h3>Versioned artifact registries vs. mixed-index correctness</h3><p>This is one of the hardest failure modes to detect because inference still appears to work. <a href="https://www.educative.io/courses/generative-ai-system-design/preliminary-machine-learning-concepts?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">Embedding models</a> change, <a href="https://www.educative.io/courses/generative-ai-system-design/key-concepts-in-designing-genai-systems?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">chunking strategies</a> evolve, and metadata schemas get revised as teams tune retrieval quality. The mistake is treating reindexing as a data refresh instead of a versioned deployment.</p><p>The trouble starts when old and new artifacts coexist without explicit version boundaries. A query can hit chunks produced by different chunkers, embeddings generated by different models, or metadata shaped by different schemas. The model still returns an answer, but retrieval becomes inconsistent. That is when duplicate chunks, stale citations, and unexplained quality regressions start showing up in production.</p><blockquote><strong>Note:</strong> If retrieval quality drops after a tuning cycle and prompt changes do nothing, check for mixed artifact versions before blaming the model.</blockquote><p>The fix is to treat every retrieval artifact as a deployable version. That includes the embedding model, chunking configuration, metadata schema, and index generation. A <a href="https://www.educative.io/courses/generative-ai-system-design/regression-testing-frameworks-for-generative-ai-applications?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">versioned artifact registry</a> makes those combinations explicit, auditable, and reproducible by recording which embedding model, chunking configuration, metadata schema, and index build identifier produced each retrieval artifact.</p><p>Version boundaries are what keep retrieval quality debuggable over time. Without version boundaries, index state becomes hard to reconstruct, and rollback becomes guesswork. With them, teams can reindex safely, validate new artifacts before cutover, and avoid the mixed-index windows that make model output look unreliable even when inference is technically fine.</p><h3>Versioned stages vs. end-to-end rewrites</h3><p>A sustainable GenAI pipeline separates <a href="https://www.educative.io/courses/generative-ai-system-design/agentic-ai-architecture-system-design?utm_campaign=persona_gen_ai_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">ingestion</a>, retrieval, orchestration, and inference behind explicit contracts because each stage evolves on its own cadence. You should be able to change the model provider without touching retrieval, or reindex data without rewriting orchestration. That sounds obvious until you have lived through a coordinated rewrite because it was not true.</p><p>The core building blocks are:</p><ul><li><strong>Model gateways:</strong> These normalize provider interfaces so migrations and contract changes are absorbed in one layer.</li><li><strong>Versioned artifact registries:</strong> These treat indexes and retrieval artifacts as immutable deployments with rollback paths.</li><li><strong>Queue-based stage isolation:</strong> This gives each stage its own scaling, retry behavior, and failure boundary.</li><li><strong>Blue-green index swaps:</strong> These reduce mixed-version cutover risk by shifting traffic between fully built index generations.</li><li><strong>Replayable ingestion jobs:</strong> These make recovery from partial failures predictable, as long as the jobs are <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">idempotent</a>.</li></ul><blockquote><strong>Practical tip:</strong> Blue-green index swaps need extra storage during the transition window. Budget for that cost explicitly.</blockquote><p>That architecture is more complex than a prototype needs to be, and queue boundaries add latency, replay logic, and more operational surface area.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8czw2BB3zC_UBLuSda9VhA.png" /><figcaption>Modular GenAI pipeline with versioned stages, queue boundaries, and isolated failure domains</figcaption></figure><p>Once multiple teams, providers, or data sources start evolving independently, those boundaries become operationally necessary. Without them, isolated change turns into an end-to-end rewrite.</p><h3>Observability vs. silent pipeline decay</h3><p>A better architecture still fails long-term if teams only watch uptime and monthly billing. The real problem is slower and harder to spot. Token counts creep up, retrieval freshness drifts, and output quality declines release by release, even when the system still looks healthy from the outside.</p><p>The signals that catch this drift early are specific:</p><ul><li><strong>Per-stage latency budgets:</strong> Track retrieval, reranking, orchestration, and generation separately so one slow stage does not hide inside end-to-end averages.</li><li><strong>Queue backlog and growth rate:</strong> Rising queue depth, especially on interactive paths, reveals contention before tail latency turns into an incident.</li><li><strong>Token-cost alerts:</strong> Watch token growth by route, model, or prompt version so context bloat and routing changes show up before billing does.</li><li><strong>Retrieval freshness </strong><a href="https://sre.google/sre-book/service-level-objectives/"><strong>service level indicators (SLI)</strong></a><strong>:</strong> Measure how far the searchable index lags behind the source of truth, not just whether the index is up.</li><li><strong>Evaluation-set regressions:</strong> Run a fixed, versioned held-out set across releases so output drift becomes visible before users feel it.</li></ul><blockquote><strong>Practical tip:</strong> Treat retrieval freshness as a first-class SLI from the start. It is much cheaper to detect stale indexes in metrics than through user complaints.</blockquote><p>This kind of observability is broader than traditional monitoring. You are not only tracking whether the pipeline is up. You are tracking whether cost, freshness, latency, and quality are drifting apart over time.</p><p>That is what keeps a good architecture from quietly turning into rewrite debt over time. By the time a GenAI system feels unreliable, the real failure is often months of unnoticed decay that nobody instrumented for.</p><h3>Conclusion</h3><p>The GenAI pipelines that break at scale usually do not fail for exotic reasons. They fail because System Design shortcuts around coupling, shared capacity, retrieval correctness, and weak observability survive into production long after the system has outgrown them.</p><p>The fix is to put the right boundaries in place before the next predictable failure arrives. That is different from overengineering day one.</p><p>Separate interactive and batch workloads before contention hides behind healthy aggregate metrics. Version retrieval artifacts like deployable assets, not background data. Instrument for drift early. By the time a rewrite feels necessary, the real failure is usually months of unnoticed decay.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=db5b6c7d56dc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[The hidden signals in a great System Design interview answer in 2026]]></title>
            <link>https://medium.com/@fahimulhaq/the-hidden-signals-in-a-great-system-design-interview-answer-0a47e99737ab?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/0a47e99737ab</guid>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[design-systems]]></category>
            <category><![CDATA[system-design-concepts]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 18 May 2026 07:35:04 GMT</pubDate>
            <atom:updated>2026-06-01T10:04:08.906Z</atom:updated>
            <content:encoded><![CDATA[<p>Most candidates I interview draw a clean architecture within the first five minutes. <a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-load-balancers?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">Load balancer</a>, cache, database, queue. The diagram looks reasonable. And yet, thirty minutes later, I often have very little signal on whether that person can design and operate a production system. What matters is not the diagram. It is whether the reasoning is visible.</p><p>The mistake I see most often is treating a plausible diagram as the goal. The real goal is traceable reasoning, where every major choice connects back to a stated constraint. If a candidate adds a cache, I want to know what access pattern justifies it. If they add replicas, I want to know what consistency trade-off they are accepting.</p><p>The strongest candidates slow down before touching the whiteboard. They ask about read-to-write ratio, consistency requirements, <a href="https://www.educative.io/courses/grokking-the-system-design-interview/put-back-of-the-envelope-numbers-in-perspective?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">latency budget</a>, and regional traffic distribution. Clarifying requirements is not a warm-up. It determines whether the rest of the design holds together.</p><p>I have seen engineers skip this step and go straight to familiar building blocks. They add caches without knowing whether the workload has enough read locality to benefit. They add replicas without knowing whether the application can tolerate <a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">replica lag</a>.</p><p>In one review, that missing justification pushed the team toward <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-replication?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">synchronous replication</a> on the write path. It looked safer on paper, but it added enough write latency to hurt response time in the region where it mattered most. The problem was not the component. It was the lack of an explicit requirement behind the choice.</p><p>Without early requirement work, an interviewer cannot tell whether a candidate is reasoning from constraints or replaying a memorized case study. That distinction is the real evaluation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*7tYRNYYkwsj35XUgSQv0SQ.png" /><figcaption>Requirement-driven design versus memorized architecture in System Design interviews</figcaption></figure><p>A clean diagram can still hide weak reasoning, and the most common place that weakness surfaces is in what the candidate never said out loud.</p><h3>Explicit assumptions versus hidden contradictions</h3><p>The most consequential part of a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">System Design</a> answer is often not the component choice. It is the unstated assumption underneath it.</p><p>A common example is a candidate assuming writes stay low and sizing the <a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-databases?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">database</a> around that. When write volume spikes during a promotion or backfill, the design breaks. The weakness is not the sizing decision itself. It is the assumption behind it that stayed invisible.</p><p>The strongest candidates make assumptions explicit and tie the design back to them. They say, “I’m assuming a 10:1 read-to-write ratio, and if writes rise sharply, I would add a queue on the write path to absorb bursts and protect the database.” That shows both what the design is optimized for and how it changes when the premise breaks.</p><blockquote><strong>Practical tip:</strong> State each assumption with its consequence. “I’m assuming X. If X changes, component Y or decision Z changes in this specific way.” That structure signals adaptability, not just familiarity with components.</blockquote><p>Once an assumption changes, the candidate should point to exactly what moves. Maybe the write path needs buffering. Maybe replica reads are no longer acceptable because lag now affects read-after-write behavior. Maybe the read model holds, but the sync path needs tighter guarantees. The goal is not to predict every future condition. It is to make the redesign traceable.</p><p>That is what interviewers are testing here. They want to see whether the design can be revised without slipping into hand-waving.</p><h3>Pushback handling versus design collapse</h3><p>The highest-signal moment in an interview often comes when the interviewer pushes back. I am not looking for instant correctness. I am looking for controlled revision when I raise hot partitions, retry storms, or elevated downstream error rates.</p><p>Take partitioning as an example. A candidate chooses user ID as the <a href="https://www.educative.io/courses/grokking-the-system-design-interview/data-partitioning?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">partition key</a>, and I push back on hot accounts creating skewed write traffic. A strong response has three steps.</p><ul><li>First, identify the speculative decision. “My partition key was user ID, which creates a hot partition for high-activity accounts.”</li><li>Second, revise it. “I would switch to a composite key such as user ID plus a time bucket, assuming the read path can tolerate scatter-gather across recent buckets.”</li><li>Third, trace the impact. “That spreads write pressure out, but it increases read amplification and can raise p99 latency because reads now need scatter-gather across partitions. It also pushes complexity into downstream aggregation because results have to be merged across buckets.”</li></ul><blockquote><strong>Practical tip:</strong> When you get pushed back, do not restart the whole design. Name the decision under pressure, revise it, and trace what that change does to the rest of the system.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/916/1*W2hzrEFtM3FxZaDfGI1OiA.png" /><figcaption>Controlled revision versus design collapse under interviewer pushback</figcaption></figure><p>That trace is the signal. It shows the candidate understands not just the fix, but the new <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/system-design-trade-offs?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">trade-offs</a> the fix introduces. The weak pattern is either collapse or rigidity. Strong candidates revise the invalid part, keep the grounded parts, and trace the impact downstream.</p><h3>Layered trade-offs versus component name-dropping</h3><p>Controlled revision only works if the original trade-off was clear enough to revise. Weak answers list a gateway, broker, database, and cache without explaining what pressure each one absorbs.</p><p>Saying “I’d use<a href="https://redis.io/"> Redis</a> here” tells me the candidate knows Redis exists. It does not tell me why a cache belongs on this path, what it costs, or what breaks when it fails. What I want is a simple structure for every major choice. Upside, cost, and failure behavior.</p><p>“I’d add a cache here because the read path is much hotter than the write path. The upside is lower read latency and less database load. The cost is invalidation complexity and memory pressure. A likely failure mode is a cache stampede on miss unless requests are coalesced.”</p><p>The same structure applies elsewhere. A durable write path improves durability but adds synchronous write latency and recovery complexity. An async worker with a <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-messaging-queue?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">dead-letter queue</a> absorbs bursty work but adds monitoring and replay overhead, with hidden business failure if the backlog accumulates unnoticed.</p><p>A denormalized read store lowers read latency but adds write amplification and sync complexity. When change propagation lags or breaks, reads return stale results.</p><blockquote><strong>Practical tip:</strong> For every component you draw, ask three questions. What pressure does this address? What does it cost? What breaks first when it fails? If you cannot answer all three, the component is not justified yet.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*t7853HIP6kjgDhwDCxzy4w.png" /></figure><p>That is the difference between a layered design answer and a list of familiar technologies. A strong answer makes every component earn its place.</p><h3>Operational maturity versus static diagram quality</h3><p>A neat architecture feels shallow if it only describes steady-state behavior. What makes an answer memorable is whether the candidate reasons about rollout, degradation, and partial failure.</p><p>A few scenarios reveal that quickly.</p><ul><li>During a rolling deploy, old and new code run at the same time. If a schema change is not backward-compatible, old code may fail to read new records, and new code may mishandle data written in the old format.</li><li>During a live schema migration, the system usually needs an expand-contract rollout, and sometimes a temporary dual-write or dual-read phase. The real requirement is compatibility across versions while traffic is still flowing.</li><li>During partial downstream degradation, one dependency starts returning errors or timing out. Without backoff, jitter, and some form of load shedding or circuit breaking, retries amplify the failure instead of containing it.</li></ul><blockquote><strong>Watch out:</strong> A retry policy without <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">backoff and jitter</a> under sustained failure will increase load, not reduce it. In a deep call chain, that can turn one unhealthy dependency into a broader outage.</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5vQW4b1hvQUay0WZJsXjYg.png" /><figcaption>Operational scenarios beyond steady state</figcaption></figure><p>The same pattern shows up elsewhere. A cold cache after deployment can stampede the database. Async replication can break read-your-own-write expectations. Version skew between API servers and background workers can produce inconsistent behavior.</p><p>None of that appears in a static diagram, but it often determines whether a design holds up once deployed.</p><h3>What interviewers remember</h3><p>Four signals separate a forgettable System Design answer from one that gives an interviewer real confidence. Traceable reasoning from requirements, explicit assumptions, controlled revision under pushback, and operational maturity beyond the diagram.</p><p>Those signals matter because they reveal how the candidate thinks, not just what systems they have seen before.</p><p>The candidates I remember make their thinking visible. They clarify constraints before drawing components. They state assumptions before building on them. They revise one part of the design without losing the rest. They notice the operational risks that only appear once the system is deployed, stressed, or partially failing.</p><p>Perfection is not the bar. It is visible reasoning, clear trade-offs, and the willingness to say what you do not know yet.</p><p>A useful way to practice is to force that structure into every <a href="https://www.educative.io/mock-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">mock interview</a>. Start with constraints. Name your assumptions. Attach a trade-off and a failure mode to each major component. When challenged, revise the part under pressure and trace what changes downstream. That is the kind of answer experienced interviewers remember.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0a47e99737ab" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[When scalable architecture hurts more than it helps]]></title>
            <link>https://medium.com/@fahimulhaq/when-scalable-architecture-hurts-more-than-it-helps-efa242145ebd?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/efa242145ebd</guid>
            <category><![CDATA[system-design-interview]]></category>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[design-systems]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 11 May 2026 05:40:39 GMT</pubDate>
            <atom:updated>2026-06-01T10:05:04.611Z</atom:updated>
            <content:encoded><![CDATA[<p>I recently reviewed an architecture designed for 50,000 requests per second. The product’s actual traffic was nowhere close. In practice, the request volume was low enough that the team paid for distributed coordination long before it saw any real benefit.</p><p>I keep seeing this pattern in design reviews, especially on systems that have not yet hit any measured scaling limit. Teams build for a hypothetical future load, then spend months carrying the operational cost of machinery they do not yet need.</p><p>I made the same mistake early in my career. Building for scale felt responsible at the time. After a few SEV-1 incidents, I learned that premature <a href="https://www.educative.io/courses/grokking-the-system-design-interview/scalability?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">scalability</a> is just another form of technical debt. It shows up in slower deploys, harder debugging, and more on-call overhead.</p><p>In over-engineered systems, that operational burden often outweighs the infrastructure cost itself. The most resilient systems I’ve worked on stayed simple until traffic patterns, ownership boundaries, or fault-isolation needs made distribution necessary.</p><p>On paper, the design looked disciplined. In production, the trade-offs were much harder to justify.</p><h3>A distributed architecture looked responsible on paper</h3><p>I’ve watched this pattern repeat itself. A team starts with a modular monolith, then splits it into six services during design review. They add<a href="https://kafka.apache.org/"> Kafka</a>,<a href="https://redis.io/"> Redis</a>, and a config service, and the design starts to look production-ready. For a product doing around 80 requests per second with a small team, though, that setup usually adds more operational cost than benefit.</p><p>This is the distributed monolith pattern in practice. The services are deployed separately, but they still depend on each other in ways that matter during incidents. Some boundaries are coupled through <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-communication-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">synchronous calls</a> between services. Others are coupled through shared schemas, coordinated releases, or compatibility windows for contract changes.</p><p>The diagram looks decomposed. The failure story usually is not.</p><blockquote>At low traffic, those service boundaries often add coordination faster than they add meaningful scaling or resilience.</blockquote><p>At that stage, the team is managing more deployment pipelines, more health checks, more inter-service hops, and more <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-distributed-logging?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">fragmented logs</a>. Even routine schema changes get harder to roll out safely because one contract change may require dual reads, dual writes, or carefully sequenced deployments.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*l92CBInjwTd-gLfMxoGGPw.png" /><figcaption>Operational overhead in modular monoliths and microservices at low traffic</figcaption></figure><p>A side-by-side comparison makes that overhead easier to see. At the same traffic level, the modular monolith keeps the request path easier to follow and the operational surface smaller. The distributed version spreads the same workload across more deployables, more infrastructure, and more coordination points.</p><p>At this scale, that trade rarely improves resilience. The services do not fail independently in ways that materially help the team. Instead, they share failure domains through synchronous dependencies, shared data assumptions, and release coupling. The team inherited those failure modes long before it needed the headroom.</p><h3>Debugging cost grew faster than complexity</h3><p>When a request crosses multiple services and queues, debugging stops being local. What would have been a straightforward trace in a modular monolith becomes a cross-system investigation spanning services, retry paths, logs, dashboards, and team boundaries.</p><p>I’ve been on incident calls where one bad <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">retry policy</a> caused more damage than the original bug. A consumer retried too aggressively, without enough backoff or jitter. Broker load increased, consumer progress slowed, <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-distributed-messaging-queue?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">queue lag</a> grew, and downstream services were pushed past their timeout budgets.</p><blockquote>In <a href="https://www.educative.io/courses/distributed-systems-practitioners?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">distributed systems</a>, debugging cost rarely grows in proportion to the number of services. Each added boundary creates more places for latency, retries, and ownership gaps to hide the root cause.</blockquote><p>That is why the mean time to recovery (MTTR) rises so quickly. Engineers are not just inspecting more components. They are reconstructing causality across asynchronous steps and partial failures.</p><p>The pattern is familiar. Queue lag rises. Downstream response times drift. Requests begin exceeding timeout budgets. Gateway errors appear first, while the initiating fault sits deeper in the pipeline. By the time traces are correlated, the failure has often spread beyond the service where it started.</p><p>Observability helps, but it does not remove the architectural cost.<a href="https://opentelemetry.io/"> OpenTelemetry</a>, end-to-end context propagation, and searchable distributed traces can reduce diagnosis time. They do not change the fact that a prematurely distributed system is harder to reason about under pressure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*cqO1tt3ds_H4U5P43lKNjQ.png" /><figcaption>Failure propagation in a prematurely distributed system</figcaption></figure><p>The issue was not that the architecture could never work. The issue was that the team introduced enough coordination and operational complexity to make failures much harder to contain and explain.</p><h3>Simpler systems outperformed in production</h3><p>The team that replaced the distributed design did not give up on scale. They moved to a simpler architecture because it was easier to operate, easier to debug, and better matched the workload they actually had.</p><p>I’ve seen modular applications backed by<a href="https://www.postgresql.org/"> PostgreSQL</a> handle more traffic than many teams expect, especially when query paths are understood and write contention is controlled. At low to moderate scale, a well-tuned monolith often gives better reliability and faster iteration without the coordination cost of distributed systems.</p><p>Teams often add infrastructure before they have a concrete reason to. At this stage, it usually shows up in familiar ways:</p><ul><li>Kafka is useful when you need replay, durable event delivery, or independently scaled consumers. If the real need is reliable background work, a <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/communication-patterns-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">transactional outbox</a> is often enough.</li><li>Redis is useful when you need shared cache state, cross-instance coordination, or low-latency shared data. If repeated reads are already handled by an in-process cache, and some staleness is acceptable, Redis may add more operational work than value.</li></ul><blockquote>Start with the simplest architecture that meets today’s requirements. Add Kafka, Redis, or service boundaries only when a measured constraint makes them necessary.</blockquote><p>I see the same pattern in <a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">System Design interviews</a>. Stronger answers usually start with a modular monolith, then define the conditions that would justify decomposition later.</p><p>That is the same framework I trust in production. Decompose when the current design is failing a real constraint. Maybe the <a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-databases?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">database primary</a> is saturating after query tuning. Maybe p99 latency keeps missing the service’s SLO. Maybe shared ownership is slowing deploys. Those are real reasons to distribute. Hypothetical scale is not.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wZKGkiN9pC6mq56cSO30Og.png" /><figcaption>Monolith decomposition decision framework flowchart</figcaption></figure><p>Start simple. Measure where the system breaks under real load. Then decompose at the boundary with the clearest observed pain.</p><h3>Architectural restraint as operational discipline</h3><p>Not distributing is an active engineering decision. You are choosing a smaller <a href="https://www.educative.io/courses/grokking-the-system-design-interview/fault-tolerance?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">failure surface</a>, lower coordination cost, and a system that the team can still reason about end-to-end. That only works if the team is clear about which signals would justify a different architecture.</p><p>The best teams make those triggers explicit. They do not decompose because the architecture looks dated or because a distributed design feels safer in theory. They decompose when a measured constraint keeps showing up in production, and simpler fixes no longer work.</p><p>In practice, the triggers are usually familiar:</p><ul><li>The database primary is still saturating after query tuning and read scaling</li><li>p99 latency keeps missing the service’s actual service level objective (SLO)</li><li><a href="https://www.educative.io/courses/grokking-the-system-design-interview/maintainability?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">Shared ownership</a> turns routine deploys into coordination work</li><li>One workload’s CPU, memory, or failure behavior starts affecting unrelated request paths</li></ul><blockquote>Define decomposition triggers before you need them. When pressure builds, the team should be following a plan, not debating architecture in the middle of an incident.</blockquote><p>This discipline keeps the system easier to reason about. It reduces the number of dashboards, runbooks, alert surfaces, and rollback paths the team has to maintain. It also makes incident response less dependent on undocumented knowledge scattered across a few engineers.</p><p>The lesson I keep coming back to is simple. Scalable architecture helps only when its complexity matches the problem in front of you. If the workload, ownership model, and failure cost do not justify that complexity yet, the disciplined choice is usually to wait.</p><p>The goal is not to avoid complexity forever. It is to introduce it only when throughput limits, <a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_may&amp;eid=5082902844932096">latency</a> budgets, ownership friction, or isolation requirements have been validated by production evidence.</p><h3>Complexity survives longer than context</h3><p>One lesson I’ve learned repeatedly is that architecture tends to outlive the assumptions that created it. The engineers who introduced a service boundary usually understand why it exists, what problem it solved, and which trade-offs were accepted along the way. A few years later, those engineers may have moved teams or left the company entirely.</p><p>The system remains, but the context often disappears.</p><p>This is where premature complexity becomes particularly expensive. A new engineer joining the team does not inherit only the code. They inherit deployment pipelines, retry policies, ownership boundaries, operational runbooks, and years of architectural decisions that may no longer be documented clearly. Every additional service, queue, and dependency becomes another concept that must be understood before meaningful changes can be made safely.</p><p>I’ve found that maintainability is often a stronger argument for simplicity than infrastructure cost. The question is not only whether the current team can operate the system effectively. It is whether the next team can understand it without requiring months of historical context.</p><p>Architectures that evolve in response to real constraints tend to age more gracefully because each layer of complexity has a clear purpose. Architects who introduce complexity early often leave behind systems where the rationale is much harder to recover than the implementation itself.</p><h3>Wrapping up</h3><p>The strongest architectures are not the ones that anticipate every possible future. They are the ones that remain understandable, adaptable, and operationally sustainable as requirements evolve. Simplicity is not the absence of sophistication. It is the discipline of introducing complexity only when evidence demands it. Teams that follow that principle build systems that scale when necessary, remain maintainable over time, and avoid paying the operational cost of capabilities they do not yet need.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=efa242145ebd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Most GenAI systems collapse under real traffic. Here’s why.]]></title>
            <link>https://medium.com/@fahimulhaq/most-genai-systems-collapse-under-real-traffic-heres-why-895b93856895?source=rss-71fb82f73ada------2</link>
            <guid isPermaLink="false">https://medium.com/p/895b93856895</guid>
            <category><![CDATA[genai]]></category>
            <category><![CDATA[design-systems]]></category>
            <dc:creator><![CDATA[Fahim ul Haq]]></dc:creator>
            <pubDate>Mon, 04 May 2026 06:46:06 GMT</pubDate>
            <atom:updated>2026-06-01T10:02:57.783Z</atom:updated>
            <content:encoded><![CDATA[<p>The most misleading moment in a <a href="https://www.educative.io/courses/generative-ai-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">generative AI (GenAI)</a> project is when the prototype looks production-ready. I have seen this pattern repeatedly. A single model endpoint returning a clean two-second response with no contention can look ready for production. It is not a meaningful readiness signal. The real problem starts when a polished demo meets shared inference capacity under multi-user load in a real <a href="https://www.educative.io/courses/grokking-the-system-design-interview?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">production system</a>.</p><p>A two-second response serves as a single-user benchmark. It does not represent a system readiness signal. When dozens of concurrent users hit the same GPU-backed inference server, tail <a href="https://www.educative.io/courses/grokking-the-system-design-interview/best-practices-for-achieving-low-latency-in-system-design?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">latency</a> can rise sharply as requests queue behind a saturated worker, often reaching double-digit seconds. The bottleneck is usually GPU memory headroom, batching behavior, <a href="https://www.educative.io/courses/generative-ai-system-design/inference-optimization-in-genai-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">key-value cache (KV cache)</a> growth, and decode throughput rather than CPU scaling.</p><p>This mismatch hides the real concurrency limits from teams used to stateless services. A stateless web tier may degrade gradually under load. A model server is more likely to hit a hard concurrency boundary or accumulate queueing delay quickly. Teams often mistake low-latency single-request performance for production readiness, and that is usually where a polished demo stops being a useful signal.</p><p>The diagram below makes that contrast concrete before we go deeper into the failure modes.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8r02eaynqgmISwjl8utMCQ.png" /><figcaption>Single-user demo path versus 50-user production GPU inference contention</figcaption></figure><p>This pattern is common in architecture reviews for teams shipping their first GenAI system to production.</p><h3>Synchronous pipelines amplify latency across model stages</h3><p>One architectural mistake I see often in GenAI deployments is chaining retrieval, large language model (LLM) inference, and reranking into a single <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/synchronous-vs-asynchronous-communication-in-distributed-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">synchronous pipeline</a>. Many teams assume stage latencies are simply additive. That assumption usually breaks under load.</p><p>A 300ms slowdown in retrieval does more than add 300ms to the final response. In a synchronous chain, it causes queueing at downstream stages and can push end-to-end p99 into the timeout range. Upstream services start timing out even when no single stage looks catastrophic in isolation.</p><p>This is also where <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/observability-in-distributed-systems-metrics-logs-and-traces?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">observability</a> breaks down. Unless you instrument every stage boundary, no single service in the <a href="https://www.educative.io/courses/distributed-systems-practitioners?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">distributed system</a> exposes the full latency path. That makes the bottleneck harder to isolate and increases mean time to recovery.</p><p>The trade-offs across these architectures require explicit evaluation:</p><ul><li><strong>Synchronous orchestration:</strong> Simple to build and easy to reason about locally. Under load, one slow stage can widen the blast radius across the whole request path.</li><li><strong>Asynchronous task queues:</strong> Better failure isolation and lower mean time to recovery (MTTR). They require stateful coordination, idempotency handling, and active queue monitoring.</li><li><strong>Stateful orchestration with fallbacks:</strong> Each stage reports health independently, and the orchestrator can reroute to a fallback such as a smaller model, cached response, or async queue instead of failing the entire pipeline. This improves recovery at the cost of higher operational complexity.</li></ul><p>The diagram below shows the difference between a tightly coupled synchronous path and a stage-isolated design with queues or orchestration boundaries. That shift is usually what determines whether one slow component becomes a pipeline-wide failure.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xuh7UI1uzWlTA6Ij4tASSw.png" /><figcaption>Synchronous pipeline coupling versus stage-isolated orchestration</figcaption></figure><p>Instrument latency at every stage boundary. Decouple stages so a slow reranker does not cascade into a full pipeline failure. Stage-level backpressure is what turns a hard failure boundary into controlled degradation.</p><p>The table below structures this trade-off across the architectures you are most likely to evaluate.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ixylXyzK5sF6xcy77RPxPQ.png" /></figure><p>With pipeline coupling addressed, the next failure mode lives one layer deeper, inside the GPU itself.</p><h3>GPU memory limits create hard concurrency cliffs</h3><p>GPU-backed model servers behave very differently from stateless microservices. On an<a href="https://www.nvidia.com/en-us/data-center/a100/?utm_source=chatgpt.com"> A100-class </a>GPU, a 13B model may support only a small number of concurrent generations before memory headroom is exhausted. The exact ceiling depends on context length, quantization, batch shape, serving engine, and KV cache growth.</p><p>GPU capacity behaves like a cliff rather than a gradual slope. Requests beyond the memory ceiling can trigger admission rejection, request preemption, or sharp error spikes. Inference bottlenecks here usually come from memory-bound scheduling and KV cache pressure rather than generic compute scaling limits.</p><blockquote><strong>Operational note:</strong> GPU memory limits do not fail gracefully. Once memory headroom is gone, a new request can trigger rejection, preemption, or instability across active work.</blockquote><p>I encountered this early in an inference deployment where the service appeared healthy until memory pressure caused abrupt failures. There was little warning once the concurrency limit was crossed.</p><p>GPU memory management in GenAI is a <a href="https://www.educative.io/courses/generative-ai-system-design/back-of-the-envelope-calculations-for-the-model-deployment?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">capacity planning</a> and scheduling problem. Profile the safe concurrency ceiling for each model and context window to account for KV cache growth. Enforce that limit in the gateway or serving scheduler. If you miss those boundaries early, you build toward an incident.</p><h3>Uncontrolled retries and lack of backpressure amplify load</h3><p>A localized slowdown in inference can turn into a system-wide outage when clients retry against already saturated GPUs. A request times out. The client retries, and other clients do the same. Effective traffic rises while the serving path is already capacity-constrained.</p><blockquote><strong>Operational note:</strong> Against bounded GPU capacity, there is no immediate serving headroom to absorb a retry spike. Queue depth grows, latency rises further, and more requests hit their timeout budgets. Each retry amplifies the next.</blockquote><p>I have seen incidents where the model, infrastructure, and retrieval stack all looked suspicious at first, but the real cause was a retry policy with no <a href="https://www.educative.io/courses/grokking-system-design-fundamentals/retry-mechanisms-backoff-strategies-and-idempotency?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">exponential backoff</a>, no jitter, and no retry budget. That policy treated inference like an elastic stateless API, which is the wrong assumption for a bounded serving path.</p><p>The failure here is architectural rather than operational. If the gateway cannot reject, queue, or reroute work under load, retries magnify the exact condition they are supposed to recover from.</p><p>The diagram below shows how a retry loop turns localized slowness into a broader failure pattern, and where <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/?utm_source=chatgpt.com">admission control</a> breaks that loop.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*aESqEgZi8bpObrlrywkgzw.png" /><figcaption>Retry storm feedback loop versus admission control intervention</figcaption></figure><p>Design retries for bounded inference capacity. Use capped exponential backoff, jitter, retry budgets, and gateway-level backpressure. Without those controls, a slow model server does not stay slow for long. It becomes unstable.</p><h3>Admission control and workload routing stabilize inference systems</h3><p>In <a href="https://www.educative.io/courses/grokking-system-design-fundamentals?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">System Design</a> terms, admission control starts by treating model serving as a bounded resource rather than a stateless worker pool. In practice, that means profiling safe concurrency, memory headroom, and batch behavior, then enforcing those limits at the gateway. If the ceiling is guessed instead of measured, teams either recreate the memory cliff or leave expensive GPU capacity idle.</p><p>Standard <a href="https://www.educative.io/courses/grokking-the-system-design-interview/introduction-to-load-balancers?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">load balancing</a> is not enough on a saturated serving path. A router that only spreads traffic by connection count or round-robin logic cannot tell whether a GPU worker is already near its safe execution limit. GenAI serving needs stateful routing based on signals such as in-flight request count, queue depth, admission tokens, and, where available, memory headroom.</p><blockquote><strong>Practical tip:</strong> Use <a href="https://docs.vllm.ai/en/latest/?utm_source=chatgpt.com">vLLM’s</a> built-in concurrency controls and continuous batching to improve utilization within a profiled limit. Continuous batching helps the server fill partial batches dynamically, which can reduce latency variance under load. Pair it with a stateful gateway that tracks in-flight work per GPU instance.</blockquote><p>A stable serving path combines three controls. The gateway enforces a concurrency cap. Excess work is queued, rejected, or routed to a fallback path such as a smaller model or deferred async processing. Latency and queue depth then guide later tuning instead of static guesswork.</p><p>The trade-off is explicit. Some requests will wait longer, be downgraded, or be shed under load. That is usually the right trade-off. A bounded system that deliberately degrades is more reliable than one that tries to admit everything and fails unpredictably.</p><p>The table below summarizes the operational impact of that shift:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*afyY_Q5FDIQmKRETOWTfzQ.png" /></figure><p>Profile safe concurrency per model, context window, and batch shape. Enforce it in the gateway or serving scheduler. If the system cannot reject, queue, or reroute work cleanly under pressure, it is not ready for production traffic.</p><h3>Not all inference requests deserve equal priority</h3><p>One lesson that becomes obvious in production is that treating every request equally is often the fastest path to widespread degradation. During periods of high demand, inference capacity becomes a scarce resource. The question is no longer whether requests can be processed. The question is which requests should be processed first.</p><p>Consider a system serving both interactive user queries and offline summarization jobs. If both workloads share the same serving path, a large batch job can consume capacity that would otherwise be available to latency-sensitive user traffic. From the user’s perspective, the system appears slow even though GPUs remain fully utilized.</p><p>This is why mature GenAI platforms introduce quality-of-service tiers. Interactive requests receive higher scheduling priority, tighter latency budgets, and access to fallback models when capacity becomes constrained. Lower-priority workloads may be queued, throttled, or deferred entirely until resources become available.</p><p>The important insight is that admission control answers the question of whether a request enters the system. Quality-of-service policies answer the question of which requests matter most when resources are limited. Together, they allow platforms to degrade intentionally rather than uniformly. Inference capacity becomes a managed resource allocated according to business value instead of simple arrival order.</p><h3>Resilient GenAI architectures prioritize resource boundaries</h3><p>Production GenAI failures rarely come from the model alone. They usually come from treating bounded inference systems like stateless services. The failure modes in this post, including synchronous coupling, GPU memory cliffs, and retry amplification, all trace back to that mistake.</p><p>The systems that hold up under load make capacity limits visible early. They profile safe concurrency before load testing, enforce it at the gateway, and design <a href="https://www.educative.io/courses/generative-ai-system-design/a-6-step-framework-for-designing-genai-systems?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">fallback paths</a> with the same care as the primary path. Backpressure, asynchronous processing, and <a href="https://www.educative.io/courses/grokking-the-system-design-interview/system-design-the-rate-limiter?utm_campaign=persona_system_design_q2_26&amp;utm_source=medium&amp;utm_medium=text&amp;utm_content=fahim_persona_blog_may&amp;eid=5082902844932096">admission control</a> are not operational add-ons. They are part of the serving design.</p><p>That is the real dividing line between a polished demo and a production system. A stable GenAI stack knows when to queue work, when to reject it, and when to downgrade gracefully instead of pushing the serving path past its limits.</p><p>If you want these systems to survive real traffic, start with the hardware constraints. Do not design around the happy path first. Profile the concurrency ceiling. Instrument every stage boundary. Design retries, routing, and fallback behavior before rollout, not after the first incident.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=895b93856895" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>