<?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"><channel><title><![CDATA[ScaleDojo-Engineering]]></title><description><![CDATA[ScaleDojo-Engineering]]></description><link>https://scaledojo-engineering.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 28 Oct 2026 12:07:00 GMT</lastBuildDate><atom:link href="https://scaledojo-engineering.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Consistent Hashing Explained: Add Servers Without Reshuffling Everything (with Python)]]></title><description><![CDATA[Imagine you run a distributed cache with four servers. Whenever a request arrives, you need to decide which server should store the corresponding key. A…]]></description><link>https://scaledojo-engineering.hashnode.dev/consistent-hashing-explained-add-servers-without-reshuffling-everything-with-python</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/consistent-hashing-explained-add-servers-without-reshuffling-everything-with-python</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Wed, 28 Oct 2026 10:00:54 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/5c7e4b80-f0a9-4b44-9483-6574556c51e0.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine you run a distributed cache with four servers. Whenever a request arrives, you need to decide which server should store the corresponding key.</p>
<p>A simple solution is to use modulo hashing:</p>
<pre><code class="language-python">server = hash(key) % 4
</code></pre>
<p>This works until your application grows and you add a fifth server.</p>
<p>Now your formula becomes:</p>
<pre><code class="language-python">server = hash(key) % 5
</code></pre>
<p>The problem? Most keys may now map to different servers. Your cache loses much of its usefulness, requests fall through to the database, and database traffic can spike unexpectedly.</p>
<p>This is where <strong>consistent hashing</strong> comes in.</p>
<p>Consistent hashing is a technique used in distributed systems to minimize how many keys need to move when servers are added or removed.</p>
<p>In this guide, we'll explore why modulo hashing breaks during scaling, how a hash ring works, why virtual nodes improve load distribution, and how to implement consistent hashing in Python.</p>
<h2>1. Why Does Hash Modulo N Break?</h2>
<p>The modulo approach assigns a key to a server using the remainder of its hash divided by the number of servers.</p>
<pre><code class="language-python">server_index = hash(key) % number_of_servers
</code></pre>
<p>For example:</p>
<ul>
<li>Four servers produce indices from <code>0</code> to <code>3</code>.</li>
<li>Five servers produce indices from <code>0</code> to <code>4</code>.</li>
</ul>
<p>When the number of servers changes, the remainder changes for many keys.</p>
<p>Let's demonstrate the problem using a simplified example:</p>
<pre><code class="language-python">keys = range(100_000)

moved = sum(
    1 for key in keys
    if key % 4 != key % 5
)

print(f"Keys moved: {moved / len(keys):.1%}")
</code></pre>
<p>Output:</p>
<pre><code class="language-text">Keys moved: 80.0%
</code></pre>
<p>This example uses the keys themselves as hash values to make the calculation easy to understand. With well-distributed hash values, adding one server to a modulo-based arrangement of N servers typically remaps approximately N/(N+1) of the keys.</p>
<p>That means adding a fifth server to four existing servers can remap around 80% of the keys.</p>
<p>For a distributed cache, this can cause a large increase in cache misses. For a sharded database, it can mean substantial data redistribution.</p>
<p><strong>The goal:</strong> Add or remove servers while moving as few keys as possible.</p>
<h2>2. What Is Consistent Hashing?</h2>
<p>Consistent hashing places both servers and keys on the same logical hash ring.</p>
<p>Imagine a circle representing the hash space. Every server and every key is assigned a position on that circle using a hash function.</p>
<p>To determine which server owns a key:</p>
<ol>
<li>Hash the server identifiers and place them on the ring.</li>
<li>Hash the key to find its position.</li>
<li>Move clockwise from the key's position.</li>
<li>Assign the key to the first server encountered.</li>
<li>If the search passes the end of the ring, wrap around to the beginning.</li>
</ol>
<p>This creates a stable mapping between keys and servers.</p>
<h3>What happens when a server is added?</h3>
<p>Suppose a new server is inserted into the ring.</p>
<p>It takes responsibility for keys in the portion of the ring between its position and the preceding server's position. Most other keys remain assigned to their existing servers.</p>
<p>When a server is removed, its keys move to the next server clockwise.</p>
<p>This is the main advantage of consistent hashing: <strong>membership changes generally affect only a portion of the key space instead of remapping almost everything.</strong></p>
<p>For a ring with N evenly distributed servers, adding one server is expected to move approximately 1/(N+1) of the keys under the usual uniform-distribution assumptions.</p>
<h2>3. Why Do We Need Virtual Nodes?</h2>
<p>A basic hash ring places each physical server at a single position. This can create uneven partitions.</p>
<p>For example, one server might own a large section of the ring, while another owns a much smaller section. Even if both servers have identical capacity, their key distributions could differ significantly.</p>
<p>Virtual nodes help reduce this imbalance.</p>
<p>Instead of assigning one position to each physical server, assign multiple positions to every server.</p>
<p>For example:</p>
<pre><code class="language-text">cache-1#0
cache-1#1
cache-1#2
...
cache-1#99
</code></pre>
<p>Each identifier represents a virtual node belonging to the same physical server.</p>
<p>With 100 virtual nodes per server, the ring has 100 positions per physical server. These additional positions tend to produce a more balanced distribution of keys.</p>
<p>Virtual nodes also provide flexibility:</p>
<ul>
<li>More virtual nodes generally improve distribution.</li>
<li>Servers with higher capacity can receive more virtual nodes.</li>
<li>Removing a physical server removes all of its virtual nodes.</li>
<li>The ring becomes larger, increasing memory use and membership-update work.</li>
</ul>
<p>Virtual nodes improve balance statistically; they do not guarantee equal traffic or eliminate hot keys.</p>
<h2>4. Implement Consistent Hashing in Python</h2>
<p>Let's build a simple consistent hash ring using Python's <code>hashlib</code> and <code>bisect</code> modules.</p>
<p>We'll use MD5 to generate stable hash positions. MD5 is used here for distribution, not security.</p>
<p>import bisect import hashlib</p>
<p>class ConsistentHashRing: def <strong>init</strong>(self, nodes=None, vnodes=100): if vnodes &lt; 1: raise ValueError("vnodes must be at least 1")</p>
<pre><code class="language-plaintext">    self.vnodes = vnodes
    self.ring = []
    self.owner = {}

    for node in nodes or []:
        self.add_node(node)

def _hash(self, value: str) -&gt; int:
    digest = hashlib.md5(value.encode()).digest()
    return int.from_bytes(digest, "big")

def add_node(self, node: str):
    for i in range(self.vnodes):
        pos = self._hash(f"{node}#{i}")

        if pos in self.owner:
            raise ValueError(
                "Hash collision detected; use a collision-handling strategy"
            )

        bisect.insort(self.ring, pos)
        self.owner[pos] = node

def remove_node(self, node: str):
    for i in range(self.vnodes):
        pos = self._hash(f"{node}#{i}")

        if self.owner.get(pos) != node:
            raise ValueError(f"Node not found on ring: {node}")

        self.ring.remove(pos)
        del self.owner[pos]

def get_node(self, key: str) -&gt; str:
    if not self.ring:
        raise ValueError("No nodes in the ring")

    pos = self._hash(key)

    # Find the first ring position at or after the key.
    # Wrap around to the first position if necessary.
    idx = bisect.bisect_left(self.ring, pos) % len(self.ring)

    return self.owner[self.ring[idx]]
</code></pre>
<p>Understanding the implementation</p>
<p>_hash()</p>
<p>Converts an identifier into a stable integer position using the full MD5 digest.</p>
<p>add_node()</p>
<p>Creates virtual nodes, calculates their positions, and inserts those positions into the sorted ring.</p>
<p>remove_node()</p>
<p>Removes the virtual nodes associated with a physical server.</p>
<p>get_node()</p>
<p>Finds the first server position at or after the key's hash position. If the key lies beyond the last position, the modulo operation wraps the search to the beginning.</p>
<h3>What is the time complexity?</h3>
<p>Let V represent the total number of virtual nodes across all physical servers.</p>
<ul>
<li>Hashing a key: O(1) for a fixed-size digest.</li>
<li>Looking up a key: O(log V) using binary search.</li>
<li>Inserting a virtual node into a Python list: O(V) in the worst case because elements may need to shift.</li>
<li>Removing a virtual node from a Python list: O(V) in the worst case.</li>
</ul>
<p>The lookup is efficient, but Python's list-based implementation is not optimized for frequent server membership changes.</p>
<h2>5. Test the Ring: Add a Server</h2>
<p>Now let's create four cache servers, assign keys to them, and observe what happens when a fifth server joins.</p>
<pre><code class="language-python">ring = ConsistentHashRing(
    ["cache-1", "cache-2", "cache-3", "cache-4"],
    vnodes=100
)

keys = [f"user:{i}" for i in range(10_000)]

before = {
    key: ring.get_node(key)
    for key in keys
}

ring.add_node("cache-5")

after = {
    key: ring.get_node(key)
    for key in keys
}

moved = sum(
    1 for key in keys
    if before[key] != after[key]
)

print(f"Keys moved: {moved / len(keys):.1%}")
</code></pre>
<p>The program calculates the actual movement percentage for this particular implementation and key set. Run it to obtain the result rather than assuming a fixed percentage.</p>
<p>With four original servers and a fifth server added, the expected movement is approximately 20% under the usual uniform-distribution assumptions. The exact result varies with virtual-node placement and the tested keys.</p>
<p>Unlike modulo hashing, consistent hashing moves keys to the new server without randomly redistributing all the remaining keys among the original servers.</p>
<p>You can also test removal:</p>
<pre><code class="language-python">ring.remove_node("cache-5")

restored = {
    key: ring.get_node(key)
    for key in keys
}

print("Mapping restored:", before == restored)
</code></pre>
<p>Expected output:</p>
<pre><code class="language-text">Mapping restored: True
</code></pre>
<p>This holds for this example because the original servers and their ring positions have not changed. In a real distributed system, moving data between servers is a separate operation from changing the ring's logical mapping.</p>
<ol start="6">
<li>Where Is Consistent Hashing Used?</li>
</ol>
<p>Distributed caches</p>
<p>Consistent hashing helps cache clients map keys to cache servers while minimizing remapping when the server pool changes.</p>
<p>Distributed databases</p>
<p>Distributed databases can use consistent-hashing-style partitioning to distribute keys across nodes. Dynamo is a well-known example of a distributed key-value store that uses consistent hashing concepts.</p>
<p>Read the Dynamo paper summary on ScaleDojo to learn more about the design.</p>
<p>Content delivery networks</p>
<p>A CDN can use hashing-based routing to distribute objects across cache nodes and reduce unnecessary movement when infrastructure changes.</p>
<p>Distributed storage systems</p>
<p>Consistent hashing can help determine which nodes should be responsible for particular keys or objects.</p>
<p>For fault tolerance, systems may replicate data to multiple distinct nodes. Replication is a separate design decision; assigning a key to one node does not automatically make its data resilient to failures.</p>
<ol start="7">
<li>Trade-Offs You Should Know</li>
</ol>
<p>Consistent hashing reduces unnecessary movement, but it does not eliminate every distributed-systems challenge.</p>
<p>Data still needs to move. When a new server takes ownership of keys, the corresponding data may need to be copied or reconstructed.</p>
<p>Virtual nodes consume resources. More virtual nodes increase ring size and membership-update costs.</p>
<p>Hot keys remain a problem. A key receiving disproportionately high traffic can overload its owner even when keys are distributed evenly.</p>
<p>Load balancing is not guaranteed. Virtual nodes improve statistical distribution, but actual traffic and key sizes can still vary.</p>
<p>Alternatives exist. Rendezvous hashing and jump consistent hashing offer other approaches to distributing keys, each with different properties and trade-offs.</p>
<h2>8. Consistent Hashing Interview Tips</h2>
<p>If an interviewer asks you to design a distributed cache or sharded storage system, explain the concept in this order:</p>
<ol>
<li>Explain why <code>hash(key) % N</code> causes extensive remapping when N changes.</li>
<li>Draw a hash ring and explain clockwise ownership.</li>
<li>Demonstrate how adding or removing a server affects only part of the ring.</li>
<li>Introduce virtual nodes to improve distribution.</li>
<li>Discuss replication, hot keys, data migration, and operational trade-offs.</li>
</ol>
<p>A strong system design answer explains not just how consistent hashing works, but also why it is useful and where it falls short.</p>
<h2>9. Learn More About Consistent Hashing</h2>
<p>Want to explore the algorithm beyond the theory?</p>
<ul>
<li><a href="https://scaledojo.dev/wiki/consistent-hashing">Consistent Hashing Wiki on ScaleDojo</a> — review the core concept.</li>
<li><a href="https://scaledojo.dev/hld/learn/unique-ids-distributing-data/consistent-hashing-revisited">Consistent Hashing in the HLD Course</a> — explore its role in high-level system design.</li>
<li><a href="https://scaledojo.dev/forge/learn/algorithms-behind-caching/consistent-hashing-scaling-a-cache">Scale a Cache with Consistent Hashing</a> — practise implementing the algorithm.</li>
</ul>
<h2>Conclusion</h2>
<p>Consistent hashing is a practical technique for distributing keys across changing server pools without remapping most of the key space every time infrastructure changes.</p>
<p>By combining a hash ring with virtual nodes, you can improve key distribution and make scaling more manageable.</p>
<p>The important takeaway is simple: <strong>consistent hashing minimizes unnecessary key movement, while virtual nodes help improve balance.</strong> Together, they provide a useful foundation for designing scalable distributed caches, storage systems, and other distributed applications.</p>
]]></content:encoded></item><item><title><![CDATA[Chaos Engineering for Beginners: How to Break Your System on Purpose (Safely)]]></title><description><![CDATA[Your system works fine on a normal day. The real question is what happens on a bad one: a server goes down, a dependency slows down, or the network starts…]]></description><link>https://scaledojo-engineering.hashnode.dev/chaos-engineering-for-beginners-how-to-break-your-system-on-purpose-safely</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/chaos-engineering-for-beginners-how-to-break-your-system-on-purpose-safely</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Mon, 26 Oct 2026 07:50:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/a92dd9bc-aa43-473b-bfce-c9d4080260c3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Your system works fine on a normal day. The real question is what happens on a bad one: a server goes down, a dependency slows down, or the network starts dropping packets.</p>
<p>You can wait for production to answer that question at 3 a.m. Or you can test it yourself in a controlled environment before your users notice a problem.</p>
<p>That second approach is called chaos engineering.</p>
<p>This guide explains chaos engineering in simple terms and walks you through a five-step method for running your first experiment safely.</p>
<h3>What Is Chaos Engineering?</h3>
<p>Chaos engineering is the practice of deliberately introducing controlled failures into a system to understand how it behaves and discover weaknesses before real incidents occur.</p>
<p>Netflix helped popularize this approach with Chaos Monkey, a tool designed to terminate instances so engineers could build systems that tolerate unexpected failures.</p>
<p>You don't need a large infrastructure or a complex tool to get started. A small, carefully planned experiment involving a single service is a good first step.</p>
<p>The key word is controlled. Chaos engineering isn't about randomly breaking things. It's about testing a specific assumption, observing the result, and improving the system.</p>
<h3>The 5-Step Method for Chaos Engineering</h3>
<ol>
<li><strong>Define Your Steady State</strong></li>
</ol>
<p>Before introducing a failure, define what a healthy system looks like.</p>
<p>For example:</p>
<ul>
<li>95th-percentile latency stays below 300 ms.</li>
<li>Error rate remains below 1%.</li>
<li>Orders continue to complete at the expected rate.</li>
</ul>
<p>These measurements give you a baseline. Without them, you won't know whether your experiment has affected the system.</p>
<ol start="2">
<li><strong>Write a Hypothesis</strong></li>
</ol>
<p>A hypothesis describes what you expect to happen when something fails.</p>
<p>For example:</p>
<p>"If one cache node becomes unavailable, the application will continue serving requests, with an error rate below 1% and latency below 300 ms."</p>
<p>This turns a failure scenario into a measurable experiment.</p>
<ol start="3">
<li><strong>Limit the Blast Radius</strong></li>
</ol>
<p>Start small and reduce the risk of affecting users.</p>
<ul>
<li>Use a test or staging environment.</li>
<li>Test one service or instance at a time.</li>
<li>Define a clear stop condition, such as aborting if the error rate exceeds 5%.</li>
<li>Prepare a kill switch to stop the experiment.</li>
<li>Inform the team before running the test.</li>
</ul>
<p>Never introduce failures into production without appropriate authorization, monitoring, and rollback procedures.</p>
<ol start="4">
<li><strong>Inject the Failure</strong></li>
</ol>
<p>Now introduce one controlled failure.</p>
<p>For example, stop one test instance, add latency to a dependency, or simulate a network timeout.</p>
<p>Watch your dashboards while the experiment runs. Pay attention to latency, error rates, resource usage, and whether the system recovers automatically.</p>
<ol start="5">
<li><strong>Observe, Fix, and Repeat</strong></li>
</ol>
<p>Compare the actual result with your hypothesis.</p>
<p>Did the system behave as expected? Did requests fail? Did latency increase? Did alerts trigger?</p>
<p>If you discover a weakness, fix it and rerun the same experiment. The goal is not simply to find a problem but to verify that the improvement works.</p>
<h3>Failure Types to Try First</h3>
<h3>Here are five useful scenarios for beginners:</h3>
<ul>
<li><strong>Kill an instance:</strong> Check whether traffic moves to healthy instances.</li>
<li><strong>Add latency to a dependency:</strong> Find out whether requests time out or wait too long.</li>
<li><strong>Make a dependency unavailable:</strong> Verify that the application has a fallback.</li>
<li><strong>Increase CPU usage or fill a test disk:</strong> Check resource limits, monitoring, and alerts.</li>
<li><strong>Introduce network delays or packet loss:</strong> See whether retries make the situation worse.</li>
</ul>
<p>Start with one scenario, measure the results, and increase complexity only when your system and safety procedures are ready.</p>
<h3>A Worked Example: Testing a Checkout Service</h3>
<p>Imagine an e-commerce application with a checkout service that depends on two other services:</p>
<ul>
<li><strong>Payment service:</strong> Processes payments and is essential to completing an order.</li>
<li><strong>Recommendation service:</strong> Displays suggested products and is optional to checkout.</li>
</ul>
<p><strong>The steady state</strong></p>
<p>Your checkout service normally succeeds 99.9% of the time, with 95th-percentile latency below 400 ms.</p>
<p><strong>The hypothesis</strong></p>
<p>"If the recommendation service becomes slow, checkout will continue working because recommendations are optional."</p>
<p><strong>The experiment</strong></p>
<p>In a test environment, introduce a two-second delay to the recommendation service.</p>
<p><strong>The result</strong></p>
<p>Checkout becomes slow or times out because the application waits for recommendations before continuing.</p>
<p>The experiment has exposed a design weakness: an optional feature is affecting a critical user journey.</p>
<p><strong>The fix: Use a timeout and fallback</strong></p>
<p>The checkout service should not wait indefinitely for recommendations. Instead, it should use a strict time limit and continue without personalized recommendations when the dependency is slow or unavailable.</p>
<p>The improved logic is simple:</p>
<ol>
<li>Send a request to the recommendation service.</li>
<li>Wait for a maximum of 300 milliseconds.</li>
<li>If recommendations arrive on time, display them.</li>
<li>If the request times out or fails, show default recommendations or skip them.</li>
<li>Continue the checkout process without blocking the purchase.\</li>
</ol>
<p>This is an example of graceful degradation: an optional feature can fail without taking down the core service.</p>
<p>You can strengthen the design further with a circuit breaker. When the recommendation service repeatedly fails, the circuit breaker temporarily stops sending requests to it, preventing additional load and giving the service time to recover.</p>
<p>Finally, rerun the same experiment. If checkout remains responsive when recommendations are delayed, you have evidence that your fix works.</p>
<h3><strong>How to Keep Chaos Engineering Safe</strong></h3>
<p>Chaos engineering is useful only when experiments are designed carefully.</p>
<ul>
<li><strong>Start outside production:</strong> Use a test environment before considering production experiments.</li>
<li><strong>Establish observability:</strong> Monitor latency, errors, traffic, resource usage, and service health.</li>
<li><strong>Define abort conditions:</strong> Decide in advance when to stop the experiment.</li>
<li><strong>Test one failure at a time:</strong> This makes it easier to identify the root cause.</li>
<li><strong>Practice game days:</strong> Schedule sessions where the team tests failure scenarios and rehearses incident response.</li>
<li><strong>Document every experiment:</strong> Record the hypothesis, setup, results, findings, and fixes.</li>
</ul>
<p>A successful experiment doesn't necessarily mean nothing failed. It means you learned something useful without causing unacceptable harm.</p>
<h3><strong>Common Chaos Engineering Mistakes</strong></h3>
<h3>Starting Without a Hypothesis</h3>
<p>If you don't know what you're testing, the results will be difficult to interpret.</p>
<p><strong>Making the Blast Radius Too Large</strong></p>
<p>Testing several services simultaneously can make failures harder to diagnose. Start with one service and expand gradually.</p>
<p><strong>Ignoring the Findings</strong></p>
<p>Discovering a weakness is only the beginning. Assign the fix, implement it, and verify the result with another experiment.</p>
<p><strong>Running Experiments Only Once</strong></p>
<p>Applications change as new features, dependencies, and infrastructure are introduced. Regular experiments help verify that resilience hasn't regressed.</p>
<h3>Why Chaos Engineering Matters in System Design Interviews</h3>
<p>Interviewers often ask questions such as, "How would you verify that your system is resilient to failures?"</p>
<p>Chaos engineering gives you a practical way to answer.</p>
<p>Explain that you would:</p>
<ol>
<li>Define measurable steady-state conditions.</li>
<li>Form a hypothesis about a potential failure.</li>
<li>Limit the blast radius and establish abort conditions.</li>
<li>Inject one controlled failure.</li>
<li>Observe the results, fix weaknesses, and repeat the experiment.</li>
</ol>
<p>You can also discuss timeouts, retries, circuit breakers, redundancy, and graceful degradation.</p>
<p>This demonstrates that you understand not only how to design a system but also how to validate its behavior when dependencies fail.</p>
<h3><strong>Learn More</strong></h3>
<h3>Want to explore resilience and system design further?</h3>
<ul>
<li><a href="https://scaledojo.dev/hld/learn/observability-operating-at-scale/chaos-engineering">Chaos Engineering — ScaleDojo HLD Course</a></li>
<li><a href="https://scaledojo.dev/hld/learn/distributed-transactions-resilience/circuit-breakers-retries-bulkheads">Circuit Breakers, Retries, and Bulkheads — ScaleDojo</a></li>
<li><a href="https://scaledojo.dev/blogs/circuit-breaker-and-bulkhead-stopping-cascading-failures">Circuit Breaker and Bulkhead Patterns</a></li>
<li><a href="https://scaledojo.dev/">ScaleDojo</a> — explore system-design learning and practice resources.</li>
</ul>
<h2>Final Thoughts</h2>
<p>Chaos engineering helps you discover what happens when systems encounter unexpected failures, rather than assuming everything will always work as planned.</p>
<p>Start with a clear hypothesis, introduce one controlled failure, observe the results, and fix what you discover. Repeat the process as your system evolves.</p>
<p>The goal isn't to break your system. It's to make sure your system can handle being broken.</p>
]]></content:encoded></item><item><title><![CDATA[How to Design a Rate Limiter: Algorithms, Architecture, and System Design Trade-Offs]]></title><description><![CDATA[Imagine your API suddenly receives thousands of requests from a single client. Application servers become overloaded, response times increase, and…]]></description><link>https://scaledojo-engineering.hashnode.dev/how-to-design-a-rate-limiter-algorithms-architecture-and-system-design-trade-offs</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/how-to-design-a-rate-limiter-algorithms-architecture-and-system-design-trade-offs</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Fri, 23 Oct 2026 11:25:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/d340e301-833b-438e-90fa-465e9a0d1ec4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine your API suddenly receives thousands of requests from a single client. Application servers become overloaded, response times increase, and legitimate users start experiencing failures.</p>
<p>A rate limiter helps prevent this by controlling how frequently a client can access a service.</p>
<p>Rate limiting is commonly used to protect APIs, control resource consumption, reduce abuse, and maintain service availability. It is also a popular System Design interview topic because it combines algorithms, distributed systems, performance, and trade-offs.</p>
<p>In this article, we'll explore common rate-limiting algorithms, design a scalable architecture, implement a basic token bucket, and discuss the decisions that matter in a real interview.</p>
<h3><strong>1. Define the Requirements</strong></h3>
<p>Before choosing an algorithm, clarify what the rate limiter needs to accomplish.</p>
<p><strong>Functional requirements</strong></p>
<ul>
<li>Limit requests by user, API key, or IP address.</li>
<li>Support configurable limits for different clients or endpoints.</li>
<li>Allow requests within the configured limit.</li>
<li>Reject excess requests with an appropriate response.</li>
<li>Optionally provide information about remaining capacity and retry timing.</li>
</ul>
<p><strong>Non-functional requirements</strong></p>
<ul>
<li>Low latency, because rate limiting happens on the request path.</li>
<li>Scalability across multiple application servers.</li>
<li>Reliable enforcement under concurrent requests.</li>
<li>High availability and predictable failure behavior.</li>
</ul>
<p>For example, imagine an API that allows each API key to make 100 requests per minute. Once the limit is reached, additional requests may receive an HTTP 429 Too Many Requests response.</p>
<p>The exact policy depends on the use case. A public search API, a payment endpoint, and an internal service may require different limits.</p>
<h3>2. Understand the Main Rate-Limiting Algorithms</h3>
<p>Different algorithms provide different levels of accuracy, complexity, and burst tolerance.</p>
<p><strong>Fixed Window Counter</strong></p>
<p>The fixed window algorithm counts requests within a predefined interval.</p>
<p>For example, a client may be allowed 100 requests between 10:00 and 10:01. The counter resets at the start of the next minute.</p>
<p>Advantages</p>
<ul>
<li>Simple to implement.</li>
<li>Low memory usage.</li>
<li>Easy to operate.</li>
</ul>
<p><strong>Limitation</strong></p>
<p>A client could send 100 requests just before the window ends and another 100 immediately afterward. This creates a burst of 200 requests in a very short period.</p>
<p>Fixed windows are useful when simplicity matters and boundary bursts are acceptable.</p>
<p><strong>Sliding Window Log</strong></p>
<p>This algorithm records request timestamps and removes entries outside the rolling time window.</p>
<p>For a limit of 100 requests per minute, the system counts requests made during the previous 60 seconds before deciding whether to accept another request.</p>
<p><strong>Advantages</strong></p>
<ul>
<li>Accurate rolling-window enforcement.</li>
<li>Avoids the fixed-window boundary problem.</li>
</ul>
<p><strong>Limitations</strong></p>
<ul>
<li>Stores individual request timestamps.</li>
<li>Can consume significant memory at high traffic volumes.</li>
</ul>
<p>Use this approach when precise enforcement is important and the storage overhead is acceptable.</p>
<p>Token Bucket</p>
<p>The token bucket algorithm maintains a bucket with a maximum capacity. Tokens are replenished at a configured rate, and each request consumes one token.</p>
<p>For example, a bucket might hold 100 tokens and replenish at 10 tokens per second. A client can make a burst of requests while tokens are available, but its sustained request rate is controlled by the replenishment rate.</p>
<p>Advantages</p>
<ul>
<li>Supports controlled bursts.</li>
<li>Controls the long-term request rate.</li>
<li>Works well for many API rate-limiting use cases.</li>
</ul>
<p>Limitations</p>
<ul>
<li>Requires maintaining bucket state.</li>
<li>Distributed implementations need safe, atomic state updates.</li>
</ul>
<p><strong>Sliding Window Counter</strong></p>
<p>This algorithm combines the previous window's count with the current window's count, weighting the previous count according to the portion of the time window that overlaps.</p>
<p>It reduces memory usage compared with storing every request timestamp, but produces an approximation of the rolling request count.</p>
<p>It can be a useful compromise between fixed-window simplicity and sliding-log precision.</p>
<h3>3. Design the High-Level Architecture</h3>
<p>A distributed rate limiter often sits at an API gateway or within request-processing middleware.</p>
<p>The request flow looks like this:</p>
<p><strong>Client → API Gateway → Rate Limiter ↔ Redis → Application Services → Database / External Services</strong></p>
<p>Here's how the components work:</p>
<ol>
<li><strong>Client:</strong> Sends a request through a web app, mobile app, or API client.</li>
<li><strong>API Gateway:</strong> Handles request routing and identifies the applicable rate-limit policy.</li>
<li><strong>Rate Limiter:</strong> Checks whether the client has exceeded its allowed request limit.</li>
<li><strong>Redis:</strong> Stores shared counters or token-bucket state so multiple application instances can enforce a common limit.</li>
<li><strong>Application Services:</strong> Process requests that pass the rate-limit check.</li>
<li><strong>Database and External Services:</strong> Support the application after the request is allowed.</li>
</ol>
<p>If the client exceeds the configured limit, the rate limiter can return an HTTP <code>429 Too Many Requests</code> response instead of forwarding the request.</p>
<p>For example, a policy might allow 100 requests per minute per API key. The actual limit and algorithm depend on the application's requirements.</p>
<p>This architecture helps illustrate the main components, but it also introduces trade-offs. Redis adds network latency and becomes another dependency, while concurrent requests require atomic updates to prevent the limit from being exceeded.</p>
<h3>4. Implement a Basic Token Bucket</h3>
<p>The token bucket algorithm can be expressed with a few variables:</p>
<ul>
<li><code>capacity</code>: The maximum number of tokens.</li>
<li><code>tokens</code>: The currently available tokens.</li>
<li><code>refill_rate</code>: The number of tokens added per second.</li>
<li><code>last_refill</code>: The last time the bucket was updated. When a request arrives, calculate how much time has passed, replenish the bucket, and check whether at least one token is available.</li>
</ul>
<p>Here is simplified Python pseudocode:</p>
<pre><code class="language-python">def allow_request(bucket, now):
    elapsed = now - bucket.last_refill

    bucket.tokens = min(
        bucket.capacity,
        bucket.tokens + elapsed * bucket.refill_rate
    )

    bucket.last_refill = now

    if bucket.tokens &gt;= 1:
        bucket.tokens -= 1
        return True

    return False
</code></pre>
<p>This illustrates the algorithm, but it is not a production-ready distributed implementation. A real implementation needs appropriate locking or atomic updates, safe time handling, persistent shared state where required, and a defined policy for storage failures.</p>
<p>For example, two concurrent requests must not both consume the same final token. A shared implementation might use an atomic Redis operation or a server-side script to calculate and update the bucket state as one logical operation.</p>
<h3>5. Handle Distributed Systems Challenges</h3>
<p>Choosing an algorithm is only part of designing a reliable rate limiter.</p>
<p><strong>Concurrent requests</strong></p>
<p>Two requests might read the same counter before either updates it. Both could be allowed even when only one should pass.</p>
<p>Use an atomic operation or an appropriate server-side script to make checking and updating the rate-limit state a single logical operation.</p>
<p><strong>Store failures</strong></p>
<p>If Redis becomes unavailable, should the application reject requests or temporarily allow traffic?</p>
<p>A fail-open policy favors availability but may allow excessive traffic. A fail-closed policy preserves enforcement but may block legitimate requests during an outage.</p>
<p>The right choice depends on the endpoint, security requirements, and consequences of overload.</p>
<p><strong>Hot keys</strong></p>
<p>A very popular client or endpoint may concentrate traffic on one key or storage partition. Monitor request distribution and latency, and consider partitioning or alternative approaches when necessary.</p>
<p><strong>Monitoring</strong></p>
<p>Track the following metrics:</p>
<ul>
<li>Allowed and rejected requests.</li>
<li>Rate-limiter latency.</li>
<li>Storage errors and timeouts.</li>
<li>Unexpected traffic spikes.</li>
<li>The number of requests exceeding configured limits.</li>
</ul>
<p>These metrics help engineers detect abuse, diagnose performance problems, and adjust policies.</p>
<ol start="6">
<li>Compare the Main Trade-Offs</li>
</ol>
<table style="min-width:75px"><colgroup><col style="min-width:25px"></col><col style="min-width:25px"></col><col style="min-width:25px"></col></colgroup><tbody><tr><td><p>Design choice</p></td><td><p>Benefit</p></td><td><p>Trade-off</p></td></tr><tr><td><p>Fixed window vs. sliding window</p></td><td><p>Simplicity vs. more accurate rolling limits</p></td><td><p>Boundary bursts vs. added complexity</p></td></tr><tr><td><p>Local counters vs. shared state</p></td><td><p>Low latency vs. consistent enforcement across servers</p></td><td><p>Limits may vary per instance vs. dependency latency</p></td></tr><tr><td><p>Exact logs vs. approximate counters</p></td><td><p>Precise enforcement vs. lower memory use</p></td><td><p>Higher storage overhead vs. approximation</p></td></tr><tr><td><p>Fail-open vs. fail-closed</p></td><td><p>Availability vs. stronger enforcement</p></td><td><p>Possible overload vs. blocked legitimate traffic</p></td></tr></tbody></table>

<p>There is no universally correct choice. The design should reflect the expected traffic, acceptable latency, required accuracy, and operational constraints.</p>
<h3>7. How to Approach This in a System Design Interview</h3>
<p>If an interviewer asks you to design a rate limiter, use a structured approach rather than immediately choosing an algorithm.</p>
<p>Step 1: Clarify requirements. Determine whether limits apply per user, API key, IP address, or endpoint. Ask whether bursts are allowed.</p>
<p>Step 2: Estimate traffic. Establish request volume, peak load, and the number of clients whose state must be tracked.</p>
<p>Step 3: Select an algorithm. Explain why a token bucket, fixed window, or sliding window fits the requirements.</p>
<p>Step 4: Draw the architecture. Identify where the limiter runs, where state is stored, and how requests reach application services.</p>
<p>Step 5: Explore failure cases. Discuss concurrency, storage outages, hot keys, and multi-region behavior if relevant.</p>
<p>Step 6: Explain trade-offs. Justify the design and describe what you would change if traffic increased tenfold.</p>
<p>Practicing this complete process is more useful than memorizing a single architecture diagram. You can also use <a href="https://scaledojo.dev/challenges">ScaleDojo's System Design challenges</a> to practice applying these concepts to structured design problems.</p>
<p>Conclusion</p>
<p>A rate limiter may appear to be a simple request counter, but a production-ready design involves algorithm selection, distributed state, concurrency, failure handling, and observability.</p>
<p>Start with the requirements, choose the simplest algorithm that satisfies them, and introduce additional complexity only when the workload demands it.</p>
<p>For System Design interviews, focus not only on drawing the architecture but also on explaining the reasoning behind each decision. That is what turns a familiar concept into a defensible engineering design.</p>
]]></content:encoded></item><item><title><![CDATA[How Uber-Style Apps Find Your Driver in Seconds: A System Design Breakdown]]></title><description><![CDATA[You tap "Request ride." A few seconds later, a driver is on the way. It feels simple. Behind that tap sit thousands of moving drivers and one hard…]]></description><link>https://scaledojo-engineering.hashnode.dev/how-uber-style-apps-find-your-driver-in-seconds-a-system-design-breakdown</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/how-uber-style-apps-find-your-driver-in-seconds-a-system-design-breakdown</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Mon, 12 Oct 2026 12:28:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/84db66d0-17ad-4842-b0e2-f23e00a35c35.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You tap "Request ride." A few seconds later, a driver is on the way. It feels simple. Behind that tap sit thousands of moving drivers and one hard question: which nearby driver should get this request?</p>
<p>Below is a simplified design of how a ride-hailing app can solve it. It's a learning model, not Uber's exact internals, but the building blocks are the ones used in real systems.</p>
<p><strong>The problem in one line</strong></p>
<p>Take a rider's location, find nearby available drivers, pick a good one, and do it fast, even when a whole city is requesting rides at once.</p>
<p><strong>Step 1: Drivers keep sending their location</strong></p>
<p>Every online driver's app sends a GPS update every few seconds. Do the math with example numbers: if 1 million drivers are online and each sends an update every 4 seconds, that's 250,000 updates per second.</p>
<p>This system is write-heavy.</p>
<p>So updates should be tiny messages sent over a persistent connection (WebSocket or MQTT), and they should land in a message queue like Kafka first. The queue absorbs spikes so the location store never gets hammered directly.</p>
<p><strong>Step 2: Keep only the latest location, in memory</strong></p>
<p>For matching you only need to know where a driver is right now. Yesterday's location is useless here. So keep the latest position in a fast in-memory store such as Redis, with an expiry time. If a driver goes offline and stops sending updates, the entry expires and they quietly disappear from the pool.</p>
<p>The full location history can flow from the same queue into cheaper storage for analytics and trip records.</p>
<p><strong>Step 3: Find nearby drivers without scanning everyone</strong></p>
<p>Calculating the distance from the rider to every driver in the city is far too slow. The trick is a geospatial index.Divide the map into cells, and track which drivers are in which cell. Common choices:</p>
<p>• <strong>Geohash:</strong> encodes a location into a short string, and nearby places share a prefix</p>
<p>• <strong>Quadtree:</strong> splits busy areas into smaller squares automatically</p>
<p>• <strong>Hexagonal grids (like H3, open-sourced by Uber):</strong> every neighbour is the same distance away, which makes "nearby" cleaner</p>
<p>To find candidates, look up the rider's cell and its neighbours. That's a few quick lookups instead of a city-wide scan. Then rank the handful of candidates by real road distance or ETA from a maps service, not by straight-line distance. A driver across a river may be close on the map and far by road.</p>
<p><strong>Step 4: Offer the ride, safely</strong></p>
<p>Send the request to the best driver with a short timeout. If they decline or don't answer, move to the next one.ScaleDojo | Blog Articles | Draft for publishing</p>
<p>One thing must never happen: two riders getting the same driver. Make the driver's state change atomic.Move a driver from <em>available</em> to <em>offered</em> using a compare-and-set or a lock in the store, so only one request can win.</p>
<p>• <strong>Available:</strong> online and free to take a ride. Next state: Offered.</p>
<p>• <strong>Offered:</strong> a request has been sent and we are waiting for the driver to respond. Next state: On trip if accepted, or back to Available if declined or timed out.</p>
<p>• <strong>On trip:</strong> passenger on board. Next state: Available when the trip ends.</p>
<p><strong>Step 5: Demand and surge pricing</strong></p>
<p>When more riders than drivers are in one area, prices can rise to balance supply and demand. A stream-processing job counts requests and available drivers per cell over a short time window and publishes a multiplier. It's a nice extension to mention once the core design is solid.</p>
<p><strong>Scaling and failure</strong></p>
<p>• <strong>Shard by city or region.</strong> A rider in Mumbai never needs drivers in Delhi.</p>
<p>• <strong>Keep services stateless</strong> behind load balancers so you can add more of them quickly.</p>
<p>• <strong>Driver loses signal?</strong> The expiry removes them, and the app re-registers when it reconnects.</p>
<p>• <strong>Hot spots</strong> (say, a stadium after a match) can overload one cell. Use smaller cells or split busy ones dynamically.</p>
<p>• <strong>Matching service crashes?</strong> State lives in the store, so another instance can pick up and retry.</p>
<p><strong>The trade-offs to mention in an interview</strong></p>
<p>• Update frequency vs cost: more frequent updates mean fresher data and a heavier write load</p>
<p>• Straight-line distance vs ETA: faster to compute vs more accurate</p>
<p>• Strong consistency on driver assignment vs raw speed: here, correctness wins</p>
<p>Want to practice this one hands-on? ScaleDojo has a detailed walkthrough: <a href="https://scaledojo.dev/blogs/design-uber-real-time-ride-matching-at-global-scale">Design Uber: Real-Time Ride Matching at Global Scale</a></p>
]]></content:encoded></item><item><title><![CDATA[10 LLD Interview Questions: Parking Lot, Elevator, Splitwise & More - With a Practical Practice Guide]]></title><description><![CDATA[Low level design (LLD) rounds are where a lot of otherwise strong engineers quietly fail. You know the design patterns. You have read the books. But when…]]></description><link>https://scaledojo-engineering.hashnode.dev/10-lld-interview-questions-parking-lot-elevator-splitwise-more-with-a-practical-practice-guide</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/10-lld-interview-questions-parking-lot-elevator-splitwise-more-with-a-practical-practice-guide</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Fri, 02 Oct 2026 12:59:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/fabac38a-c7b3-461f-9240-a591f2c0f2c7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Low level design (LLD) rounds are where a lot of otherwise strong engineers quietly fail. You know the design patterns. You have read the books. But when the interviewer says, "Design a parking lot," your mind goes blank and you start drawing random boxes. This guide covers ten LLD interview questions that show up again and again, including the kind Amazon LLD interview questions tend to follow, what interviewers are really checking, and a repeatable method to practice them.</p>
<h3>What is an LLD round, really?</h3>
<p>In a low level design interview you are asked to design the classes, interfaces, and relationships for a small, well-defined system. Unlike high level design, there is no talk of load balancers or sharding. The interviewer wants to see clean object-oriented thinking: sensible entities, clear responsibilities, and code structure that survives a changing requirement.</p>
<p>Typically you get 40 to 60 minutes. You are expected to clarify requirements, identify core entities, define their relationships, write key class skeletons or method signatures, and sometimes implement one critical flow in code.</p>
<h3>The 10 LLD interview questions</h3>
<ol>
<li><p><strong>Parking lot system</strong> The classic. Model vehicles, spots of different sizes, tickets, entry and exit gates, and pricing. Interviewers like to ask: what changes if we add electric vehicle charging spots? A Strategy pattern for pricing and a clean spot-allocation interface usually impress.</p>
</li>
<li><p><strong>Elevator system</strong> Tests state management and scheduling. You need an elevator state (idle, moving up, moving down), a request queue, and a scheduling algorithm. Be ready to discuss multiple elevators and how a controller assigns requests.</p>
</li>
<li><p><strong>Splitwise (expense sharing)</strong> Focus on users, groups, expenses, and different split types (equal, exact, percentage). The Strategy pattern fits split types naturally. Expect a follow-up on simplifying debts so fewer transactions are needed.</p>
</li>
<li><p><strong>Library management system</strong> A good warm-up: books, copies, members, loans, fines, and reservations. The trap is confusing a Book (the title) with a BookCopy (the physical item). Interviewers watch for exactly this.</p>
</li>
<li><p><strong>Rate limiter (class design)</strong> Here you design the classes behind algorithms like token bucket or sliding window. Define a common RateLimiter interface and plug different algorithms in. This one tests whether you can keep the design extensible.</p>
</li>
<li><p><strong>LRU cache</strong> A hash map plus a doubly linked list. It looks like a data structure question but it is also an LLD question: how do you make the eviction policy swappable (LRU, LFU, FIFO)?</p>
</li>
<li><p><strong>Tic-tac-toe or chess</strong> Board, players, moves, and win conditions. For chess, the interesting part is piece behavior: each piece type knows its own valid moves. Polymorphism is the star here.</p>
</li>
<li><p><strong>Hotel booking system</strong> Rooms, room types, reservations, availability search, and payments. Pay attention to concurrency: two people booking the last room at once is a favorite follow-up.</p>
</li>
<li><p><strong>Notification service</strong> Channels (email, SMS, push), templates, user preferences, and retries. Observer and Factory patterns both have natural homes here.</p>
</li>
<li><p><strong>ATM machine</strong> A state machine problem: card inserted, PIN entered, transaction selected, cash dispensed. The State pattern is the textbook answer, and the interviewer will likely ask about failure handling mid-transaction.</p>
</li>
</ol>
<h3>What interviewers actually check</h3>
<p>● <strong>Requirement clarification.</strong> Do you ask questions before designing, or do you start coding immediately?</p>
<p>● <strong>Entity identification.</strong> Are your classes the real nouns in the problem, and does each have one clear responsibility?</p>
<p>● <strong>Relationships.</strong> Do you choose composition over inheritance where it makes sense, and can you explain why?</p>
<p>● <strong>Extensibility.</strong> When they say, "Now add a new vehicle type," does your design bend or break?</p>
<p>● <strong>Pattern usage.</strong> Patterns used where they fit are a plus. Patterns forced everywhere are a red flag.</p>
<p>● <strong>Code quality.</strong> Meaningful names, small methods, and no giant God class that does everything.</p>
<h3>A 40-minute method for any LLD question</h3>
<p>1 <strong>Minutes 0 to 5: Requirements.</strong> Ask who the users are, what the core features are, and what is out of scope. Write the agreed requirements down.</p>
<p>2 <strong>Minutes 5 to 12: Entities and relationships.</strong> List nouns, group them, and sketch how they relate. Decide what is a class, an interface, or an enum.</p>
<p>3 <strong>Minutes 12 to 25: Class skeletons.</strong> Write fields and method signatures for the key classes. Focus on public behavior, not private details.</p>
<p>4 <strong>Minutes 25 to 35: One critical flow in code.</strong> Pick the most important operation (for example, parking a vehicle) and implement it end to end.</p>
<p>5 <strong>Minutes 35 to 40: Extend.</strong> Add a new requirement to your own design and note what has to change. If the answer is "a lot," refactor.</p>
<h3>Common mistakes that cost offers</h3>
<p>● Jumping straight into code without clarifying requirements.</p>
<p>● Creating one huge class that holds all the logic.</p>
<p>● Over-engineering with five design patterns when two classes would do.</p>
<p>● Ignoring edge cases like invalid input, full capacity, or concurrent access.</p>
<p>● Never discussing trade-offs or alternatives.</p>
<h3>How to practice so it actually sticks</h3>
<p>Reading solutions teaches recognition, not recall. You nod along, think "that makes sense," and then freeze in the real interview. The fix is active practice: pick one problem, set a 40-minute timer, and design from a blank page. Only then compare your answer to a reference solution and note the gaps.</p>
<p>Do this daily with a new problem for two weeks and you will feel the difference. Platforms with hands-on labs make this easier because you can design, get feedback, and iterate in one place. If you want a structured way to do it, ScaleDojo has practice <a href="https://scaledojo.dev/lld/learn/welcome/what-lld-interviews-actually-test"><strong>LLD interview questions hands-on</strong></a> where each problem is broken into requirement, design, and implementation steps.</p>
<h3>Final tip</h3>
<p>Whatever method you use, always end a practice session by adding one new requirement to your own design and watching what breaks. That single habit trains the exact skill interviewers test most: designing code that can change. If you want more reps on realistic problems, try the <a href="https://scaledojo.dev/lld"><strong>low level design labs</strong></a> and work through two or three questions a week</p>
]]></content:encoded></item><item><title><![CDATA[Why Your LLD Mock Interviews Feel Too Easy (And How to Fix Them)]]></title><description><![CDATA[I did a handful of LLD mock interviews with a friend before my first real one, and every single one of them felt weirdly easy compared to the actual…]]></description><link>https://scaledojo-engineering.hashnode.dev/why-your-lld-mock-interviews-feel-too-easy-and-how-to-fix-them</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/why-your-lld-mock-interviews-feel-too-easy-and-how-to-fix-them</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Tue, 29 Sep 2026 07:08:30 GMT</pubDate><content:encoded><![CDATA[<p>I did a handful of LLD mock interviews with a friend before my first real one, and every single one of them felt weirdly easy compared to the actual interview. It took me a while to figure out why-and once I did, fixing it made a bigger difference than any amount of additional reading.</p>
<h2>The problem isn't the practice. It's what's missing from it.</h2>
<p>A mock interview with a friend who also wants to pass their own interviews has a built-in conflict of interest nobody talks about: neither of you wants to be the difficult one. Your "interviewer" hints when you're stuck instead of letting you sit in the discomfort. They accept your first reasonable-sounding answer instead of asking "why not composition instead of inheritance here?" They're rooting for you, which is kind, and also exactly why the practice doesn't transfer.</p>
<p>Real interviewers don't have that conflict of interest. They're specifically paid to find the edges of your understanding, not to make you feel good about your design.</p>
<h2>The three things a friendly mock interview almost never does</h2>
<p><strong>It doesn't push back on your first answer.</strong> In a real interview, giving a reasonable design is the starting point, not the finish line-the interviewer's next move is almost always a follow-up that tests whether you actually understand the trade-off or just landed on something that works. Friendly mocks tend to accept the first answer and move on.</p>
<p><strong>It doesn't introduce a requirement change mid-design.</strong> Real interviewers frequently add a constraint partway through-"actually, this also needs to support concurrent bookings"-specifically to see if your class structure survives the change or needs a rewrite. Mock interviews with a peer rarely do this, because it feels like moving the goalposts rather than a legitimate test.</p>
<p><strong>It doesn't sit in silence when you're stuck.</strong> This is the big one. A friend watching you struggle will almost always jump in with a hint before the silence gets uncomfortable. A real interviewer will let it sit, because how you behave when you're genuinely stuck-do you think out loud, do you ask a clarifying question, do you freeze completely is real signal they're trying to observe.</p>
<h2>How to fix this without needing a stranger to interview you</h2>
<p><strong>Set a rule before you start: no hints for at least 90 seconds of silence.</strong> This alone changes the entire character of the practice. Sitting in that discomfort and practicing what you actually do with it — think out loud, ask a question, try something even if you're not sure -is the skill, and it only gets built by refusing to shortcut past it.</p>
<p><strong>Assign your "interviewer" one job: find the weakest part of every answer.</strong> Not to be mean -to simulate the actual incentive structure of a real interview, where the interviewer's job is literally to find the edges of your understanding, not to make you feel good.</p>
<p><strong>Script in at least one requirement change per session.</strong> Pick it in advance, unknown to the candidate, and introduce it once they're about ten minutes in. This is the single most realistic thing you can add to a peer mock interview, and it's almost never included by default.</p>
<p><strong>Time-box it strictly, with no pausing to "look something up."</strong> Real interviews don't pause. Practicing with the safety net removed is uncomfortable specifically because that discomfort is the actual thing being trained.</p>
<h2>Why even a fixed mock interview has a ceiling</h2>
<p>Even with all of this, a peer mock interview has a structural limit: your friend's feedback is only as good as their own understanding, and a peer who hasn't seen hundreds of LLD answers can't tell you with confidence what a senior engineer would actually flag about your class structure. That's a different kind of feedback than "did this feel hard," and it's genuinely hard to manufacture between two people prepping for the same interview.</p>
<p>That gap is what pushed me toward practicing on <a href="https://scaledojo.dev/lld">ScaleDojo's LLD Schema Design Lab</a> alongside peer mocks -it scores your actual design decisions and tells you specifically what to fix, which is closer to the calibrated feedback a real interviewer gives than two candidates guessing at each other's blind spots.</p>
<p>If your LLD mock interviews have been feeling easier than they should, it's probably not because you're more ready than you think. It's because the format is quietly protecting you from the exact discomfort the real interview won't.</p>
]]></content:encoded></item><item><title><![CDATA[I Solved the Same System Design Problem 3 Different Ways-Here's What Actually Changed]]></title><description><![CDATA[I picked one problem-design a rate limiter-and deliberately solved it three times with three different constraints, to see how much a "correct" system…]]></description><link>https://scaledojo-engineering.hashnode.dev/i-solved-the-same-system-design-problem-3-different-ways-here-s-what-actually-changed</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/i-solved-the-same-system-design-problem-3-different-ways-here-s-what-actually-changed</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Tue, 29 Sep 2026 06:43:10 GMT</pubDate><content:encoded><![CDATA[<p>I picked one problem-design a rate limiter-and deliberately solved it three times with three different constraints, to see how much a "correct" system design answer actually shifts depending on what you're optimizing for. The results made something click that reading a single model solution never did: there's rarely one right architecture, only the right architecture for a specific set of priorities.</p>
<h2>Attempt 1: optimize for simplicity and correctness</h2>
<p><strong>Constraint:</strong> single-service application, moderate traffic, small team maintaining it.</p>
<p>I reached for a fixed window counter stored in Redis-increment a counter per user per time window, reject requests once the count exceeds the limit, expire the key at the window boundary. It's simple to implement, easy for anyone on the team to reason about, and correct for the stated constraints.</p>
<p><strong>The trade-off I accepted:</strong> fixed windows allow a burst at the boundary-a user can send their full limit right at the end of one window and immediately their full limit again at the start of the next, effectively doubling their allowed rate for a brief moment. For this context, that's a completely acceptable trade-off. Simplicity won, correctly.</p>
<h2>Attempt 2: optimize for fairness under bursty traffic</h2>
<p><strong>Constraint:</strong> same basic system, but now the product team is complaining that the boundary-burst behavior is letting power users game the limit meaningfully.</p>
<p>Here the fixed window stopped being good enough, and I moved to a sliding window log or sliding window counter approach-tracking requests within a rolling window rather than a fixed calendar-aligned one. This closes the boundary-burst problem almost entirely.</p>
<p><strong>The trade-off I accepted:</strong> more memory and computation per check, since you're now tracking a rolling history instead of a single counter. For a system where fairness matters more than raw efficiency, that's the right call. Notice that this wasn't a "better" answer than Attempt 1 in the abstract-it was better for a specific complaint that Attempt 1's context didn't have.</p>
<h2>Attempt 3: optimize for massive scale across distributed nodes</h2>
<p><strong>Constraint:</strong> now imagine this rate limiter has to run consistently across hundreds of distributed API gateway nodes, globally, with the limit enforced per-user regardless of which node handles the request.</p>
<p>This is where both previous answers actually break down, not because they're wrong in principle but because a per-node counter doesn't give you a global limit, and a naive shared Redis instance becomes a single point of failure and a latency bottleneck at that scale. This is where a token bucket algorithm with a centralized, sharded rate-limiting service (or a local-approximation strategy with periodic synchronization) becomes the right shape-accepting some imprecision in exchange for the system staying available and fast even when a shared source of truth would otherwise buckle.</p>
<p><strong>The trade-off I accepted:</strong> the global limit is no longer perfectly precise at any given instant- there's a small window where the true count across all nodes could drift slightly from the enforced limit. At this scale, that imprecision is a completely reasonable price for availability and latency.</p>
<h2>What actually changed across the three attempts</h2>
<p>The core concept-"count requests, reject over a threshold"-never changed. What changed every time was which trade-off I was willing to accept, and that was entirely driven by a constraint that wasn't in the original one-line problem statement. None of the three answers is "the" correct rate limiter design. Each one is correct for its specific context and wrong for the other two.</p>
<h2>Why this matters more than memorizing "the" answer</h2>
<p>This is exactly what a live interview is actually testing, and it's the reason memorizing a single clean answer to "design a rate limiter" is a fragile strategy. The moment an interviewer changes one constraint-"now imagine this needs to work across multiple data centers"-a memorized answer doesn't flex, because it was never built around reasoning about trade-offs in the first place, just around reproducing a solution.</p>
<p>The skill worth building is the one this exercise trains directly: given the same problem, being able to identify which constraint just changed and update your architecture's priorities accordingly, live, instead of reaching for the one design you've rehearsed.</p>
<p>I've been running this same exercise-same problem, deliberately shifted constraints-using <a href="https://scaledojo.dev/lab"><strong>ScaleDojo's HLD Architecture Lab</strong></a><strong>,</strong> since its challenges are structured around exactly this kind of constraint variation rather than a single fixed version of each classic problem.</p>
<p>If you've only ever solved "design a rate limiter" once and moved on, it's worth going back and solving it again under a deliberately different constraint. The difference between those two answers is closer to what the real interview is testing than either answer alone.</p>
]]></content:encoded></item><item><title><![CDATA[Low Level Design Practice: How to Actually Get Good at It]]></title><description><![CDATA[Most LLD advice tells you what to study-SOLID principles, design patterns, classic problems-without saying much about how to actually practice in a way…]]></description><link>https://scaledojo-engineering.hashnode.dev/low-level-design-practice-how-to-actually-get-good-at-it</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/low-level-design-practice-how-to-actually-get-good-at-it</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Thu, 24 Sep 2026 13:35:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/7f4bca3c-1a9a-43ec-b121-dce6974a3f3f.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most LLD advice tells you what to study-SOLID principles, design patterns, classic problems-without saying much about how to actually practice in a way that transfers to a real interview. Here's the practice method that made the biggest difference for me, not just a list of topics.</p>
<h3>Why reading LLD write-ups doesn't build the skill</h3>
<p>You can read ten solutions to "design a parking lot" and walk away able to explain why each class exists. That's recognition, not production. The actual interview skill is starting from a blank whiteboard and building that same structure yourself, under time pressure, without a reference answer to check against as you go. Those are different skills, and only one of them gets tested live.</p>
<h3>The practice method that actually worked</h3>
<ol>
<li><p><strong>Time-box the blank-page struggle.</strong> Pick a problem, set a 20-minute timer, and start from nothing-no notes, no reference solutions open. The discomfort of not knowing where to start is the exact discomfort a real interview produces, and getting used to sitting in it matters more than any specific pattern you memorize.</p>
</li>
<li><p><strong>Narrate your reasoning out loud, even alone.</strong> LLD interviews are graded on reasoning, not just the final class diagram. Practicing silently teaches you to produce a diagram. Practicing out loud teaches you to explain why each class and relationship exists, which is the actual thing being evaluated.</p>
</li>
<li><p><strong>Deliberately apply one principle at a time.</strong> Instead of trying to remember all of SOLID simultaneously, run through a design once checking only for single responsibility violations, then again checking only for whether your abstractions would survive a specific extension. This catches more than trying to hold every principle in your head at once from the start.</p>
</li>
<li><p><strong>Get pushed back on, not just checked.</strong> This is the piece hardest to replicate alone. Reading a checklist against your own design tells you if something's technically wrong. It doesn't replicate an interviewer asking "why did you choose composition here instead of inheritance?" and expecting you to defend it on the spot.</p>
</li>
</ol>
<h3>A rotation that covers the real range</h3>
<p>Don't drill the same problem type repeatedly. Rotate across:</p>
<ul>
<li><p>A "model the real world" problem (parking lot, elevator, library system)</p>
</li>
<li><p>A concurrency-flavored problem (rate limiter, thread-safe cache)</p>
</li>
<li><p>A "messy requirements" problem (splitwise-style expense splitting, where clarifying questions matter as much as the classes)</p>
</li>
<li><p>An extension-focused problem (chess game, card game framework) where you have to justify why your design would survive a plausible new requirement</p>
</li>
</ul>
<h3>Where I closed the "get pushed back on" gap</h3>
<p>Practicing alone gets you most of the way, but the missing piece-someone specifically telling you where a senior engineer would flag your design-is hard to replicate solo. I used <a href="https://scaledojo.dev/lld">ScaleleDojo's LLD Schema Design Lab</a> for that part specifically: you build the design on an interactive canvas, submit it, and get scored feedback on the actual decisions, which is closer to the live-pushback experience than self-checking against a reference solution.</p>
<p>If your LLD prep has mostly been reading solutions and nodding along, switching to timed, narrated, blank-page practice-even without a person watching-is the single change most likely to show up as improvement in your next real interview.</p>
]]></content:encoded></item><item><title><![CDATA[Object-Oriented Design Principles Every LLD Interview Tests]]></title><description><![CDATA[Most LLD prep jumps straight to "design a parking lot" or "design a vending machine" without stopping to ask: what is the interviewer actually grading…]]></description><link>https://scaledojo-engineering.hashnode.dev/object-oriented-design-principles-every-lld-interview-tests</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/object-oriented-design-principles-every-lld-interview-tests</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Sun, 20 Sep 2026 07:03:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/2b969f95-d615-4ebb-9974-5569ee6b1129.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most LLD prep jumps straight to "design a parking lot" or "design a vending machine" without stopping to ask: what is the interviewer actually grading while you draw those classes? The answer is almost always object-oriented design principles — and if you can't name why a design follows them, the classes on the whiteboard don't matter much.</p>
<p>Here's a breakdown of the principles that come up constantly, and what interviewers are actually listening for when they ask about them.</p>
<h2>Encapsulation: more than "make fields private"</h2>
<p>Most candidates know encapsulation means hiding internal state. Fewer can explain why it matters in a specific design. If your <code>Order</code> class exposes a public <code>items</code> list that anyone can mutate directly, you've lost control over invariants like "an order can't be empty" or "total must match the sum of item prices." Encapsulation isn't a style rule — it's how you protect the rules your system depends on.</p>
<p><strong>What interviewers listen for:</strong> can you point to a specific invariant your encapsulation is protecting, not just say "it's good practice."</p>
<h2>Abstraction: hiding complexity, not hiding classes</h2>
<p>Abstraction gets confused with encapsulation constantly. Abstraction is about exposing a simple interface while hiding how the work actually happens. A <code>PaymentProcessor</code> interface with a <code>charge(amount)</code> method is abstraction — the caller doesn't need to know if it's calling Stripe, Razorpay, or a mock in tests.</p>
<p><strong>What interviewers listen for:</strong> whether your abstractions are stable. If adding a new payment provider means changing code all over your system instead of just adding one new class, the abstraction was drawn in the wrong place.</p>
<h2>The Single Responsibility Principle, applied honestly</h2>
<p>Everyone can recite "a class should have one reason to change." Almost nobody applies it under interview pressure, because the honest version requires you to notice when you're about to violate it — usually while you're mid-design and it's tempting to just add one more method to a class that already does too much.</p>
<p>A classic tell: a <code>User</code> class that handles authentication, sends emails, and calculates loyalty points. Three reasons to change, three responsibilities, three future bugs waiting to happen when one gets modified and breaks the others.</p>
<h2>Open/Closed: designing for the extension you haven't been asked for yet</h2>
<p>This is the principle candidates struggle with most, because it requires anticipating change without over-engineering for hypothetical futures that never arrive. The skill isn't "add every extension point possible" — it's identifying the one or two axes your interviewer's problem is actually likely to extend along.</p>
<p>For a parking lot problem, vehicle types are an obvious extension axis (cars, motorcycles, trucks). Pricing strategies are another. If your class hierarchy makes adding a new vehicle type require editing five existing classes, that's a real signal, not a nitpick.</p>
<h2>Liskov Substitution: the one that silently breaks systems</h2>
<p>This principle rarely gets asked about directly, but it gets violated constantly, and interviewers notice. If a subclass changes the behavior a caller expects from the parent class — a <code>Square</code> that extends <code>Rectangle</code> but breaks <code>setWidth</code>/<code>setHeight</code> independence — you've created a class that's technically a subtype but functionally a landmine.</p>
<p><strong>What interviewers listen for:</strong> whether you catch this yourself when sketching an inheritance hierarchy, without being prompted.</p>
<h2>Composition over inheritance: knowing when to reach for which</h2>
<p>Interviewers love asking "would you use inheritance or composition here?" because there's rarely one right answer -there's a right answer for the specific constraints of the problem. Inheritance models a true "is-a" relationship with shared behavior. Composition models "has-a" and tends to age better when requirements shift, because you're not locked into a rigid hierarchy.</p>
<p>A <code>Car</code> "is-a" <code>Vehicle</code> -inheritance is reasonable. A <code>Car</code> "has-a" <code>Engine</code> -composition, not inheritance, because a car doesn't behave like an engine, it uses one.</p>
<h2>Why reading these definitions doesn't make them stick</h2>
<p>You can memorize every principle above and still freeze the first time you're asked to design something from scratch, because recognizing a principle in a textbook example and applying it correctly to a novel problem, live, are different skills. The gap only closes with repetition-designing schemas and class hierarchies yourself, getting told specifically where a principle was violated, and doing it again.</p>
<p>That loop is what <a href="https://scaledojo.dev/lld"><strong>ScaleDojo's LLD Schema Design Lab</strong></a> is built around progressive missions on an interactive canvas where you apply these principles to real problems and get scored, specific feedback instead of a vague pass/fail.</p>
<p>If OOD principles have been the part of your LLD prep you've been reading about instead of practicing, that's usually the highest-leverage gap to close before your next interview.</p>
]]></content:encoded></item><item><title><![CDATA[Concurrency Interview Questions Every LLD Round Should Prepare You For]]></title><description><![CDATA[If you've sat through a handful of Low-Level Design interviews, you've probably noticed a pattern: no matter what the actual problem is a parking lot, a…]]></description><link>https://scaledojo-engineering.hashnode.dev/concurrency-interview-questions-every-lld-round-should-prepare-you-for</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/concurrency-interview-questions-every-lld-round-should-prepare-you-for</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Thu, 17 Sep 2026 14:16:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/e6858119-bba2-494a-9a94-1166bd9e6772.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you've sat through a handful of Low-Level Design interviews, you've probably noticed a pattern: no matter what the actual problem is a parking lot, a rate limiter, a ride-sharing dispatch system -the interviewer eventually circles back to one question:</p>
<p>"Okay, now how would you make this thread-safe?"</p>
<p>Concurrency isn't a side topic in system design interviews anymore. It's one of the areas that tests whether you can think beyond class diagrams and consider what happens when multiple requests interact with the same system at the same time.</p>
<p>I've gone through enough LLD discussions both as a candidate and while reviewing mock interviews to notice that the same handful of concurrency questions show up repeatedly, even when the underlying design problem is completely different. Here's a breakdown of what those questions are, why they matter, and how to approach them instead of simply memorizing answers.</p>
<h2>Why Concurrency Questions Keep Showing Up in LLD Rounds</h2>
<p>LLD interviews are meant to simulate real engineering problems, and real applications are almost never single-threaded. The moment you design something like an inventory system, a booking engine, or a cache, you're implicitly deciding how multiple users or processes will interact with the same piece of shared state.</p>
<p>Interviewers care less about whether you can define a mutex and more about whether you notice shared state in the first place.</p>
<p>For example, imagine a ticket-booking system where only one seat is left, and two requests arrive almost simultaneously:</p>
<pre><code class="language-java">if (seatsAvailable &gt; 0) {
    seatsAvailable--;
    bookSeat();
}
</code></pre>
<p>At first glance, the code looks reasonable. But what happens when two threads execute it at the same time? Both threads might see <code>seatsAvailable = 1</code>, both pass the condition, and both attempt to book the same seat. That's a race condition and catching it yourself, before the interviewer has to point it out, is an important LLD skill.</p>
<h2>The Concurrency Questions That Come Up Again and Again</h2>
<h3>1. "Where's the Race Condition in This Design?"</h3>
<p>This is often one of the first concurrency questions you'll encounter. Suppose you're designing a ticket-booking system and have:</p>
<pre><code class="language-java">if (seatsAvailable &gt; 0) {
    seatsAvailable--;
}
</code></pre>
<p>The problem is the check-then-act sequence -two threads can perform the check before either one performs the decrement. A similar problem can happen with shared collections, counters, caches, inventory, or account balances.</p>
<p>A useful habit during LLD interviews is to ask yourself:</p>
<ul>
<li><p>What data is shared, and which fields are mutable?</p>
</li>
<li><p>Can multiple threads modify them?</p>
</li>
<li><p>Is there a check-then-act operation?</p>
</li>
<li><p>Is a shared collection being read and written concurrently?</p>
</li>
<li><p>What happens if two requests arrive at exactly the same time?</p>
</li>
</ul>
<p>You don't want concurrency to be something you think about only after the interviewer asks.</p>
<h3>2. "How Would You Make This Method Thread-Safe?"</h3>
<p>Once you've identified a race condition, the obvious follow-up is how you'd fix it -and this is where understanding the trade-offs matters.</p>
<p><strong>synchronized</strong> is the simplest solution:</p>
<pre><code class="language-java">public synchronized void bookSeat() {
    // booking logic
}
</code></pre>
<p>It's straightforward and useful when the critical section is small and contention isn't a major concern. However, locking a large method can unnecessarily limit concurrency.</p>
<p><strong>ReentrantLock</strong> gives you more control:</p>
<pre><code class="language-java">lock.lock();
try {
    // critical section
} finally {
    lock.unlock();
}
</code></pre>
<p>It provides capabilities like timed lock acquisition, fairness policies, and interruptible locking -at the cost of extra complexity and the responsibility of correctly releasing the lock.</p>
<p><strong>Atomic variables</strong> like <code>AtomicInteger</code>, <code>AtomicLong</code>, and <code>AtomicReference</code> provide thread-safe operations on individual variables without explicitly locking the whole method:</p>
<pre><code class="language-java">AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
</code></pre>
<p>The important interview skill isn't just knowing these APIs -it's explaining why one approach fits the particular problem better than the others.</p>
<h3>3. "What's the Difference Between volatile and Atomic Operations?"</h3>
<p>This is another area where candidates often get confused. <code>volatile</code> primarily provides visibility guarantees between threads:</p>
<pre><code class="language-java">private volatile boolean running = true;
</code></pre>
<p>When one thread changes <code>running</code>, other threads can observe the updated value. But <code>volatile</code> doesn't make compound operations atomic. Consider:</p>
<pre><code class="language-java">counter++;
</code></pre>
<p>This involves reading the value, adding one, and writing the result three steps that multiple threads can interfere with. An atomic counter avoids this:</p>
<pre><code class="language-java">AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
</code></pre>
<p>A useful way to remember the distinction: visibility is not the same as atomicity.</p>
<h3>4. "How Would You Design a Thread-Safe Singleton?"</h3>
<p>The Singleton pattern is a common LLD exercise because it touches several concurrency concepts at once. One implementation uses double-checked locking:</p>
<pre><code class="language-java">public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
</code></pre>
<p>The interesting part isn't memorizing the implementation it's answering when the interviewer asks, "Why does this need <code>volatile</code>?" That's where you need to understand visibility and instruction reordering rather than just reproducing a code snippet.</p>
<h3>5. "How Would You Implement Producer-Consumer?"</h3>
<p>Producer-consumer problems test something beyond mutual exclusion: coordination between threads. A producer generates work, a consumer processes it, and a queue sits between them:</p>
<pre><code class="language-plaintext">Producer
   |
   v
[ Queue ]
   |
   v
Consumer
</code></pre>
<p>You can implement coordination manually using <code>wait()</code> and <code>notify()</code>, but Java also provides higher-level abstractions like <code>BlockingQueue</code>:</p>
<pre><code class="language-java">BlockingQueue&lt;Task&gt; queue = new ArrayBlockingQueue&lt;&gt;(100);
</code></pre>
<p>Now you can reason about questions like: What happens when the queue is full? What happens when there's nothing to consume? How do producers and consumers coordinate without unsafe access to shared state? An interviewer may also ask about the difference between <code>notify()</code> and <code>notifyAll()</code> -the important part is understanding the coordination model, not memorizing individual methods.</p>
<h3>6. "How Does Deadlock Happen, and How Would You Avoid It?"</h3>
<p>Consider two threads:</p>
<pre><code class="language-plaintext">Thread A: Lock A → waiting for Lock B
Thread B: Lock B → waiting for Lock A
</code></pre>
<p>Neither thread can continue. This is a classic deadlock scenario, and a common way to reason about it is through its four necessary conditions: mutual exclusion, hold and wait, no preemption, and circular wait.</p>
<p>One common prevention strategy is consistent lock ordering. If your application always acquires Lock A before Lock B-instead of some code acquiring A→B and other code acquiring B→A -you eliminate the circular wait. During an interview, don't just say "use lock ordering." Explain which deadlock condition your fix actually breaks.</p>
<h3>7. "ExecutorService vs Raw Threads -What's the Difference?"</h3>
<p>An interviewer may ask why you wouldn't simply create a new thread for every task. The honest answer is that creating an uncontrolled number of threads gets expensive fast as workload increases. Java's <code>ExecutorService</code> and thread pools manage worker threads more systematically:</p>
<pre><code class="language-plaintext">Incoming Tasks
      |
      v
  Thread Pool
 [T1] [T2] [T3] [T4]
</code></pre>
<p>Instead of creating a new thread per task, a pool reuses a controlled number of worker threads. You should also understand why different executor configurations exist and what kind of workload each fits - the real question isn't "do you know ExecutorService," it's "why would you choose it here?"</p>
<h3>8. "When Would You Use a Reader-Writer Lock?"</h3>
<p>Not every shared resource has the same read/write pattern. Imagine a configuration cache where reads vastly outnumber writes. Using a single exclusive lock for every operation unnecessarily restricts concurrent readers. Java's <code>ReentrantReadWriteLock</code> separates read and write access, letting multiple readers proceed concurrently while writes still require exclusivity.</p>
<p>That doesn't automatically make it the right choice, though. The interviewer may push further: "Why is this better than a normal lock for this specific workload?" Your answer should connect the synchronization mechanism to the actual access pattern, not just name the API.</p>
<h3>9. "Design a Thread-Safe Rate Limiter"</h3>
<p>This problem combines several concurrency concepts at once. Suppose you want to allow 100 requests per minute per user, and hundreds of requests arrive simultaneously. Now you need to think about shared counters, atomic updates, time windows, concurrent access, synchronization, cleanup, throughput, and correctness-all together. A simple counter isn't enough.</p>
<p>The interviewer will likely keep pulling the thread: What happens if two requests arrive at exactly the same time? What happens if you have multiple application servers? Would an in-memory implementation still work? That's what makes rate limiting such a useful LLD problem- a relatively simple requirement quickly opens into concurrency, scalability, and distributed systems trade-offs.</p>
<h2>How to Prepare for Concurrency Questions</h2>
<p>Reading a list of questions helps you recognize common patterns, but reading alone isn't enough. Try implementing small systems and deliberately testing them under concurrent access:</p>
<ul>
<li><p><strong>Thread-safe counter</strong> - build one and hit it with updates from multiple threads.</p>
</li>
<li><p><strong>Ticket booking</strong> -create a booking system and try booking the same seat concurrently.</p>
</li>
<li><p><strong>LRU cache</strong> -implement one and think through concurrent reads and writes.</p>
</li>
<li><p><strong>Producer-consumer</strong> -build a bounded queue and test what happens when producers outrun consumers.</p>
</li>
<li><p><strong>Rate limiter</strong>- implement a simple one and fire simultaneous requests at it.</p>
</li>
</ul>
<p>After every implementation, ask yourself: "What happens if 100 threads execute this code at the same time?" That single question can change the way you approach LLD problems.</p>
<h2>The Real Skill Isn't Memorizing Concurrency APIs</h2>
<p>It's easy to memorize <code>synchronized</code>, <code>volatile</code>, <code>AtomicInteger</code>, <code>ReentrantLock</code>, deadlock conditions, <code>ExecutorService</code>, and <code>BlockingQueue</code>. But an LLD interview isn't a multiple-choice test. You're given a system, and then the interviewer changes the conditions -two users arrive simultaneously, a resource becomes shared, traffic increases, a method gets called concurrently, the application gets deployed across multiple servers.</p>
<p>That's when you need to reason about what actually happens. The real skill is being able to identify shared state, synchronization requirements, race conditions, and trade-offs while you're designing the system, not after.</p>
<h2>From Knowing to Building</h2>
<p>Reading about concurrency gives you the vocabulary. Building concurrent systems gives you the intuition -and that difference matters a lot in LLD preparation.</p>
<p>If you want hands-on practice alongside structured LLD problems, <a href="https://scaledojo.dev">ScaleDojo</a> has practical system-design exercises covering areas like LLD, HLD, API design, and GenAI systems.</p>
<p>The goal isn't to memorize every concurrency API. It's to develop the habit of asking, "What happens when multiple things happen at the same time?" Once that question becomes automatic, concurrency gets a lot easier to reason about during LLD interviews.</p>
]]></content:encoded></item><item><title><![CDATA[Designing a RAG Pipeline Is the New System Design Interview Question. Here's How to Actually Practice It.]]></title><description><![CDATA[A year or two ago, "design a URL shortener" and "design Twitter's feed" were the questions you drilled on repeat. They still show up. But if you've…]]></description><link>https://scaledojo-engineering.hashnode.dev/designing-a-rag-pipeline-is-the-new-system-design-interview-question-here-s-how-to-actually-practice-it</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/designing-a-rag-pipeline-is-the-new-system-design-interview-question-here-s-how-to-actually-practice-it</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Wed, 09 Sep 2026 13:45:06 GMT</pubDate><content:encoded><![CDATA[<p>A year or two ago, "design a URL shortener" and "design Twitter's feed" were the questions you drilled on repeat. They still show up. But if you've interviewed anywhere building AI features lately, there's a decent chance you've hit a newer one: "design a RAG system for our internal docs" or "how would you architect a chatbot that answers questions from our knowledge base."</p>
<p>And most system design prep hasn't caught up. You can find a hundred URL shortener walkthroughs. You'll struggle to find five decent RAG architecture breakdowns written for interview prep specifically.</p>
<p>So let's fix that gap.</p>
<h2>Why RAG questions are different from classic HLD questions</h2>
<p>A URL shortener question is mostly about read/write ratios, caching, and ID generation-data flows through fairly predictable paths. A RAG (Retrieval-Augmented Generation) question adds a layer most candidates haven't reasoned about before: how do you retrieve the <em>right</em> unstructured context, at low latency, at scale, and hand it to a model without breaking the bank or hallucinating garbage.</p>
<p>It pulls together pieces from a few different disciplines at once -search/retrieval systems, vector databases, classic distributed systems concerns (caching, scaling, latency budgets), and LLM-specific constraints (context window limits, prompt construction, cost per call). That combination is exactly why interviewers like asking it: it's genuinely hard to fake your way through if you haven't thought about it before.</p>
<h2>The core components you need to be able to draw</h2>
<p>If you strip a RAG system down to its essential pieces, you're looking at:</p>
<ol>
<li><p><strong>Ingestion pipeline</strong>-documents get chunked, embedded, and written into a vector store. The chunking strategy alone (fixed-size vs. semantic vs. sliding window) is a legitimate design decision an interviewer can push on.</p>
</li>
<li><p><strong>Vector database</strong>-stores embeddings and supports approximate nearest-neighbor search. Worth knowing the trade-offs between something like a managed vector DB versus bolting vector search onto Postgres with an extension, because interviewers love asking "why not just use X" questions.</p>
</li>
<li><p><strong>Retriever</strong>-takes the user's query, embeds it, and pulls the top-k most relevant chunks. This is where latency budgets get real: retrieval has to be fast enough that the whole pipeline doesn't feel sluggish before the LLM even starts generating.</p>
</li>
<li><p><strong>Ranker/re-ranker (optional but often expected)</strong>-a lightweight model that re-scores retrieved chunks for relevance before they get passed to the LLM, since raw vector similarity isn't always a great relevance signal.</p>
</li>
<li><p><strong>Prompt construction / context assembly</strong>-deciding how much retrieved content fits in the context window, how it's formatted, and what happens when retrieval returns too much or too little.</p>
</li>
<li><p><strong>LLM call + guardrails</strong>-the generation step itself, plus whatever's checking the output for hallucination, PII leakage, or off-topic responses before it reaches the user.</p>
</li>
<li><p><strong>Caching layer</strong>-for repeated or similar queries, since re-embedding and re-retrieving identical questions is wasted latency and cost.</p>
</li>
</ol>
<p>Most candidates can name two or three of these. Interviewers are listening for whether you can reason about the <em>interactions</em> between them-for instance, what happens to your latency budget if the re-ranker adds 200ms, or how stale embeddings get when the underlying documents change and nobody's invalidating the vector store.</p>
<h2>Where this gets genuinely hard (and interview-worthy)</h2>
<p>A few spots where I've seen candidates-myself included, the first time I sat down to actually design one of these-get stuck:</p>
<ul>
<li><p><strong>Chunking strategy trade-offs.</strong> Too small and you lose context; too large and you waste tokens and dilute relevance. There's no single right answer, which is exactly why it's a good interview probe.</p>
</li>
<li><p><strong>Staleness.</strong> If the source documents update, how does your system know to re-embed and re-index? Do you re-embed everything on a schedule, or track deltas?</p>
</li>
<li><p><strong>Cost at scale.</strong> Every retrieval-plus-generation call has a real dollar cost. What's your strategy when traffic spikes 10x-do you degrade gracefully (smaller context, cheaper model) or just eat the cost?</p>
</li>
<li><p><strong>Guardrails vs. latency.</strong> Every safety/relevance check you bolt on adds latency. Where do you put the line?</p>
</li>
</ul>
<p>None of this is exotic knowledge. It's just knowledge most system design prep simply doesn't cover yet, because the classic curriculum was written before RAG was a common interview topic.</p>
<h2>How I've been practicing this instead of just reading about it</h2>
<p>Reading a blog post like this one gets you the vocabulary. It doesn't get you the rep of actually laying out the pipeline yourself and having something push back on your design decisions. For that, I've been using the <a href="https://scaledojo.dev/genai"><strong>GenAI Systems Lab on ScaleDojo</strong></a><strong>,</strong> which has challenges specifically built around architecting RAG systems, autonomous AI agents, vector databases, model routers, and guardrails-with instant AI feedback on the trade-offs you pick, not just whether the diagram is "correct."</p>
<p>There's also an <a href="https://scaledojo.dev/genai">AI Lab</a> if you want to go one level deeper and actually implement algorithms like k-means or a scalar autograd engine from scratch in Python, no ML libraries allowed-useful if the interview loop expects you to understand what's happening under the retrieval and ranking steps, not just diagram them.</p>
<p>If RAG system design hasn't shown up in your interview loops yet, it's very likely to soon-AI features are becoming a standard part of the product surface at most companies, and interview questions tend to follow what teams are actually shipping. Worth building the rep now instead of hitting it cold.</p>
<p><em>If you've been asked a RAG or AI-pipeline system design question in an actual interview, I'd love to hear how it was framed-curious whether companies are asking this as a standalone question yet or folding it into a broader "design our AI feature" prompt.</em></p>
]]></content:encoded></item><item><title><![CDATA[API Design Lab: REST vs GraphQL vs gRPC for System Design Interviews

]]></title><description><![CDATA[Most system design interview questions about APIs get a surface-level answer: "I'd use REST." That's usually where the conversation ends for candidates who…]]></description><link>https://scaledojo-engineering.hashnode.dev/api-design-lab-rest-vs-graphql-vs-grpc-for-system-design-interviews</link><guid isPermaLink="true">https://scaledojo-engineering.hashnode.dev/api-design-lab-rest-vs-graphql-vs-grpc-for-system-design-interviews</guid><dc:creator><![CDATA[ScaleDojo]]></dc:creator><pubDate>Wed, 02 Sep 2026 13:25:53 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6a9819df983a45d50e418ac8/9df05ec0-ba37-4067-80d4-6cf5d8cdfcd6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most system design interview questions about APIs get a surface-level answer: "I'd use REST." That's usually where the conversation ends for candidates who haven't actually sat down and practiced defending that choice-and it's exactly where a good interviewer starts digging.</p>
<p>I want to walk through how to actually think about this, structured the way I'd want it drilled into me if I were prepping: not as trivia to memorize, but as a decision tree you can reason through live.</p>
<h2>Start with who's calling the API</h2>
<p>Before picking a protocol, the first system design interview question you should be asking yourself is: who are the clients? A mobile app on a flaky network has different needs than an internal service making thousands of calls per second to another internal service. Naming the client type out loud, before you touch the whiteboard, is the single easiest way to sound like you know what you're doing.</p>
<h2>REST: correct by default, wrong when you use it as a crutch</h2>
<p>REST is the safe pick, and it's usually the right one. Resource-based systems (users, orders, posts) map naturally onto it, it's cacheable at the HTTP layer, and every tool on earth understands it.</p>
<p>Where it breaks down: deeply nested data. If a client needs a post, its author, its comments, and each comment's author, REST forces you into either multiple round trips or an endpoint that dumps way more data than the client asked for. If your system has that shape, be ready for the interviewer to push on it-and have an answer beyond "REST is fine."</p>
<h2>GraphQL: solves over-fetching, creates new problems</h2>
<p>GraphQL exists specifically to let the client ask for exactly the fields it needs, in one request. It's a strong answer for systems with multiple client types pulling different slices of the same data.</p>
<p>But proposing GraphQL without acknowledging its costs is one of the fastest ways to lose points in a system design interview. Be ready for:</p>
<ul>
<li><p><strong>Caching</strong> -a single GraphQL endpoint breaks standard HTTP caching</p>
</li>
<li><p><strong>The N+1 query problem</strong> -a poorly written query can quietly hammer your database</p>
</li>
<li><p><strong>Rate limiting</strong> -every request hits the same endpoint, so limiting by route doesn't work anymore</p>
</li>
</ul>
<h2>gRPC: the one people forget to practice</h2>
<p>gRPC rarely gets asked about directly, but it's often the right call for service-to-service communication -not client-facing APIs. High-frequency internal calls (an order service hitting an inventory service constantly) benefit from gRPC's binary protocol and HTTP/2 multiplexing in a way REST and GraphQL can't match.</p>
<p>The strongest answer usually isn't "I'd use X." It's layering: REST or GraphQL at the edge for external clients, gRPC internally between services. Saying that out loud signals you're thinking in layers, not reaching for one tool and forcing it everywhere.</p>
<table>
<thead>
<tr>
<th>Factor</th>
<th>REST</th>
<th>GraphQL</th>
<th>gRPC</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Best For</strong></td>
<td>Public APIs, web &amp; mobile apps</td>
<td>Multiple client types needing different data</td>
<td>Internal service-to-service communication</td>
</tr>
<tr>
<td><strong>Data Fetching</strong></td>
<td>Fixed resource endpoints</td>
<td>Client chooses exact fields</td>
<td>RPC method calls</td>
</tr>
<tr>
<td><strong>Performance</strong></td>
<td>Good</td>
<td>Depends on query complexity</td>
<td>Excellent, low latency</td>
</tr>
<tr>
<td><strong>Caching</strong></td>
<td>Native HTTP caching</td>
<td>More complex caching</td>
<td>Limited</td>
</tr>
<tr>
<td><strong>Protocol</strong></td>
<td>HTTP/1.1 or HTTP/2</td>
<td>HTTP</td>
<td>HTTP/2</td>
</tr>
<tr>
<td><strong>Payload Format</strong></td>
<td>Usually JSON</td>
<td>JSON</td>
<td>Protobuf (binary)</td>
</tr>
<tr>
<td><strong>Learning Curve</strong></td>
<td>Easy</td>
<td>Moderate</td>
<td>Moderate to High</td>
</tr>
<tr>
<td><strong>Typical Use Case</strong></td>
<td>CRUD applications</td>
<td>Complex frontend applications</td>
<td>Microservices communication</td>
</tr>
<tr>
<td><strong>Main Advantage</strong></td>
<td>Simplicity and broad support</td>
<td>Flexible data retrieval</td>
<td>High performance and efficiency</td>
</tr>
<tr>
<td><strong>Main Drawback</strong></td>
<td>Over-fetching / under-fetching</td>
<td>N+1 queries, caching complexity</td>
<td>Less browser-friendly, more setup</td>
</tr>
</tbody></table>
<h2>A framework worth practicing until it's automatic</h2>
<ol>
<li><p>Who are the clients -web, mobile, third-party, internal services?</p>
</li>
<li><p>How nested is the data -flat leans REST, deeply nested and multi-client leans GraphQL?</p>
</li>
<li><p>What's the call frequency and latency profile -high-frequency internal leans gRPC?</p>
</li>
<li><p>What do you need at the edge -caching, rate limiting, and auth are easier with REST or GraphQL?</p>
</li>
</ol>
<h2>Why this only sticks with actual practice</h2>
<p>You can read a breakdown like this one and nod along, but the trade-offs only really click once you've had to defend a specific choice out loud, with someone pushing back on it. That's the gap between knowing the difference and being ready to justify one live.</p>
<p>This is exactly what pushed me to build through <strong>ScaleDojo's</strong> <a href="https://scaledojo.dev/api-design"><strong>API Design Lab</strong></a> -50 progressive levels that go from basic endpoint structure to pagination, nested resources, and error response modeling, with an AI review that tells you specifically what a senior engineer would have done differently. It's part of a broader set of labs covering HLD, LLD, and GenAI/RAG system design too, if API design isn't the only gap in your prep.</p>
<p>If system design interview questions on API design are the part of your prep you keep skipping, this is the one to stop skipping.</p>
]]></content:encoded></item></channel></rss>