<?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" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ System Design - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ System Design - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Thu, 18 Jun 2026 05:24:41 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/system-design/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Enterprise Knowledge Graphs for Scalable Digital Product Platforms ]]>
                </title>
                <description>
                    <![CDATA[ Enterprises are building more and more digital products that depend on real time intelligence. This means that being able to connect, contextualize, and reason over data has become a core capability.  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-optimize-enterprise-knowledge-graphs-for-scalable-digital-product-platforms/</link>
                <guid isPermaLink="false">6a26427ed198e572e0517866</guid>
                
                    <category>
                        <![CDATA[ data-engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ knowledge graph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ scalability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ graph database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kamal Kishore ]]>
                </dc:creator>
                <pubDate>Mon, 08 Jun 2026 04:18:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/95434417-9316-481d-b6db-5e9d01f0c971.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Enterprises are building more and more digital products that depend on real time intelligence. This means that being able to connect, contextualize, and reason over data has become a core capability.</p>
<p>Recommendation systems, fraud detection engines, personalization platforms, and enterprise search solutions all rely on integrating data from multiple systems while preserving context and relationships.</p>
<p>Enterprise Knowledge Graphs (EKGs) have emerged as a foundational architecture for addressing this challenge. By modeling enterprise data as entities and relationships, EKGs enable richer semantics, improved data discoverability, and more intelligent downstream decision making.</p>
<p>While the conceptual benefits of knowledge graphs are well understood, scaling them to production grade digital platforms remains complex. Graph systems that perform well at small or medium scale often struggle under high ingestion rates, complex traversal queries, and strict latency requirements.</p>
<p>This article outlines some practical, field tested strategies for optimizing enterprise knowledge graphs for real world scalability. Rather than presenting purely theoretical models, we'll focus on architectural patterns, operational lessons, and performance insights from large scale enterprise deployments.</p>
<h2 id="heading-what-well-cover">What We'll Cover:</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-why-scalability-becomes-the-core-challenge">Why Scalability Becomes the Core Challenge</a></p>
</li>
<li><p><a href="#heading-moving-beyond-a-single-graph-store-hybrid-architectures">Moving Beyond a Single Graph Store: Hybrid Architectures</a></p>
</li>
<li><p><a href="#heading-partitioning-for-scale-reducing-distributed-traversal-costs">Partitioning for Scale: Reducing Distributed Traversal Costs</a></p>
</li>
<li><p><a href="#heading-managing-semantic-inference-without-sacrificing-performance">Managing Semantic Inference Without Sacrificing Performance</a></p>
</li>
<li><p><a href="#heading-improving-query-performance-with-smarter-planning">Improving Query Performance with Smarter Planning</a></p>
</li>
<li><p><a href="#heading-observability-as-a-first-class-requirement">Observability as a First Class Requirement</a></p>
</li>
<li><p><a href="#heading-impact-on-digital-product-platforms">Impact on Digital Product Platforms</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>This is an architectural guide intended for data engineers, platform architects, and developers managing production-grade graph systems. To get the most out of this article, you should have the following:</p>
<h3 id="heading-conceptual-knowledge"><strong>Conceptual Knowledge</strong></h3>
<ul>
<li><p>A solid understanding of Enterprise Knowledge Graphs (EKGs) and the fundamental differences between RDF triple stores and Labeled Property Graphs (LPGs).</p>
</li>
<li><p>Familiarity with distributed systems concepts, including data partitioning, semantic inference, and event-driven architectures.</p>
</li>
</ul>
<h3 id="heading-technical-background"><strong>Technical Background</strong></h3>
<ul>
<li><p>Experience working with real-time data integration pipelines (such as CDC, Kafka, or Pulsar).</p>
</li>
<li><p>Familiarity with database observability, query execution planning, and general performance optimization techniques at scale.</p>
</li>
</ul>
<h2 id="heading-understanding-the-enterprise-knowledge-graph-ekg">Understanding the Enterprise Knowledge Graph (EKG)</h2>
<p>Before exploring how to scale these systems, it's helpful to understand exactly what a knowledge graph is and how it organizes information.</p>
<p>At its core, a knowledge graph is a data model that represents real-world entities and the complex relationships between them. Unlike traditional relational databases that lock data into rigid, disconnected tables, knowledge graphs store data as a flexible, interconnected network.</p>
<p>A knowledge graph is built on three fundamental components:</p>
<ul>
<li><p><strong>Nodes (Entities):</strong> The distinct objects, concepts, or people in your data ecosystem (for example a Customer, a Product, a Location).</p>
</li>
<li><p><strong>Edges (Relationships):</strong> The lines connecting the nodes that define how they interact (for example "PURCHASED," "LOCATED_IN," "MANUFACTURED_BY").</p>
</li>
</ul>
<p><strong>Properties:</strong> The descriptive metadata attached to nodes or edges (for example, a customer's signup date, or the price of a product).</p>
<h2 id="heading-our-running-example-the-global-electronics-supply-chain-graph">Our Running Example: The Global Electronics Supply Chain Graph</h2>
<p>To ground these concepts, we'll use a unified example throughout this article: an enterprise graph for a global electronics manufacturer managing product data, suppliers, and manufacturing compliance.</p>
<img src="https://cdn.hashnode.com/uploads/covers/6902fd055c9ea201c1fdc217/816a8985-93c2-4e0e-a085-87d3dd4e6fc7.png" alt="816a8985-93c2-4e0e-a085-87d3dd4e6fc7" style="display:block;margin:0 auto" width="1466" height="514" loading="lazy">

<ul>
<li><p>Nodes (Entities): Customer (Alice), Product (NeoPhone 15), Component (MX-200 Chip), Supplier (MaxSemi), and Region (EU).</p>
</li>
<li><p>Edges (Relationships): PURCHASED, PART_OF, SUPPLIES, and LOCATED_IN.</p>
</li>
<li><p>Properties: The NeoPhone 15 node has properties like price: 999 and sku: "NP15-01". The PURCHASED edge has a property of timestamp: 2026-06-03.</p>
</li>
</ul>
<p>Imagine you're building the data foundation for a retail recommendation engine. To build the graph, you move through a few distinct phases:</p>
<ol>
<li><p><strong>Establish ontology:</strong> First, you define the blueprint – the rules dictating what kinds of entities exist and how they are allowed to interact.</p>
</li>
<li><p><strong>Define the nodes:</strong> You integrate data to generate specific entity nodes, such as a Customer node for "Alice," a Product node for "Noise-Canceling Headphones," and a Brand node for "TechAudio."</p>
</li>
<li><p><strong>Map the edges:</strong> You connect these nodes based on user actions and inventory data. Alice VIEWED the Headphones. The Headphones are MANUFACTURED_BY TechAudio.</p>
</li>
</ol>
<p>Why does this matter? Because the data is natively structured as a relationship network, the system can rapidly execute context-rich queries.</p>
<p>If you want to know what else Alice might buy, you don't need to write a heavy, expensive SQL query that joins millions of rows across five different tables. Instead, the graph simply "walks" the pathways you've already built. It traverses from Alice, across the VIEWED edge to the Headphones, across the MANUFACTURED_BY edge to TechAudio, and can instantly return other products connected to that same brand.</p>
<p>By prioritizing the <em>relationships</em> between data points as much as the data points themselves, EKGs provide the contextual intelligence required for modern digital products.</p>
<h2 id="heading-why-scalability-becomes-the-core-challenge"><strong>Why Scalability Becomes the Core Challenge</strong></h2>
<p>Most enterprise knowledge graph initiatives begin with a limited scope, integrating a small number of datasets, enabling semantic search, or improving reporting accuracy. Early-stage deployments often succeed using a single graph database or RDF store.</p>
<p>Scalability challenges emerge when EKGs become production critical infrastructure, particularly when supporting customer facing or latency-sensitive applications. At this stage, multiple pressures converge:</p>
<ol>
<li><p>Rapid data growth as more systems and entities are integrated</p>
</li>
<li><p>Continuous ingestion from streaming pipelines and transactional systems</p>
</li>
<li><p>Increasing query complexity, including multi hop traversals</p>
</li>
<li><p>Strict response time requirements, often under tens of milliseconds</p>
</li>
<li><p>Inference overhead introduced by ontologies and reasoning engines</p>
</li>
</ol>
<p>Simply adding hardware or scaling nodes horizontally rarely resolves these issues. Performance degradation often results from architectural mismatches between graph workloads and system design.</p>
<h2 id="heading-moving-beyond-a-single-graph-store-hybrid-architectures">Moving Beyond a Single Graph Store: Hybrid Architectures</h2>
<h3 id="heading-the-limits-of-monolithic-graph-deployments">The Limits of Monolithic Graph Deployments</h3>
<p>RDF triple stores offer strong semantic expressiveness and standards compliance but may struggle with high volume transactional updates or deep real time traversals. Conversely, labeled property graph (LPG) databases often provide efficient traversal performance but lack native semantic reasoning capabilities.</p>
<p>Attempting to consolidate semantic modeling, inference, operational queries, and analytics into a single system frequently results in trade offs that affect performance, cost, or maintainability.</p>
<h3 id="heading-a-pragmatic-hybrid-model">A Pragmatic Hybrid Model</h3>
<p>A hybrid or polyglot architecture distributes responsibilities across systems optimized for specific workloads:</p>
<ol>
<li><p>Semantic layer (RDF / OWL): Ontology management, schema governance, reasoning workflows.</p>
</li>
<li><p>Operational graph layer (LPG): Real time traversals, recommendation engines, application queries.</p>
</li>
<li><p>Analytical stores: Aggregations, reporting, and historical analysis.</p>
</li>
</ol>
<p>To maintain consistency between the semantic layer (RDF/OWL) and the operational graph layer (LPG), many teams implement synchronization strategies like Change Data Capture (CDC) and event driven pipelines.</p>
<p>In this approach, updates in one layer are captured as events and propagated to the other layer in near real time using streaming platforms such as Kafka or Pulsar. For example, updates in the operational graph can trigger semantic updates, ensuring that ontologies and relationships remain aligned.</p>
<p>Some systems also use dual write patterns or scheduled reconciliation jobs to detect and resolve inconsistencies. In practice, event-driven synchronization combined with periodic validation provides a balance between real time accuracy and system reliability.</p>
<p>This separation isolates performance critical paths while preserving semantic richness where it adds value.</p>
<p>In production environments, hybrid architectures consistently demonstrate improved query latency and operational flexibility compared to monolithic graph deployments, particularly for traversal-heavy workloads. Some teams have also reported latency reductions of 30–60% when separating traversal-heavy workloads into LPG layers, compared to monolithic graph deployments.</p>
<p>This improvement is primarily due to reduced query complexity and optimized storage for specific access patterns.</p>
<h3 id="heading-in-practice-splitting-the-supply-chain-graph">In Practice: Splitting the Supply Chain Graph</h3>
<p>In a production-grade digital platform, a single database engine struggles to handle both semantic governance and high-speed operational queries on this data simultaneously.</p>
<p>Here is how the hybrid model divides the labor:</p>
<ul>
<li><p><strong>The Semantic layer (RDF/OWL):</strong> Manages strict ontological classification and compliance rules. For example, it defines the rule: <em>“If a Component is supplied by an entity in a country under a trade embargo, the final Product inherits a 'High Risk' compliance flag.”</em></p>
</li>
<li><p><strong>The Operational Layer (LPG):</strong> Optimized for fast, multi-hop traversals required by customer-facing apps. When Alice views the NeoPhone 15 on a mobile app, the system queries a Labeled Property Graph (like Neo4j) using a language like Cypher to instantly traverse from the product to its components for a real-time availability check:</p>
</li>
</ul>
<pre><code class="language-plaintext">MATCH (p:Product {id: 'NeoPhone15'})-[:HAS_COMPONENT]-&gt;(c:Component)
RETURN c.name, c.stock_level
</code></pre>
<h2 id="heading-partitioning-for-scale-reducing-distributed-traversal-costs">Partitioning for Scale: Reducing Distributed Traversal Costs</h2>
<p>As enterprise knowledge graphs outgrow single node capacity, distributed execution becomes necessary. Partitioning strategy then becomes a critical performance factor.</p>
<h3 id="heading-why-default-partitioning-often-fails">Why Default Partitioning Often Fails</h3>
<p>Many graph systems use hash-based or random partitioning to distribute data evenly across nodes. While this approach balances storage, it often fragments highly connected subgraphs. Even moderately complex traversals may then require excessive cross-node communication, increasing latency and reducing throughput.</p>
<h3 id="heading-topology-aware-partitioning">Topology-Aware Partitioning</h3>
<p>Topology-aware partitioning colocates frequently connected entities to minimize network hops during traversal. Common approaches include:</p>
<ol>
<li><p>Partitioning by business domain (for example, customers, products, organizations).</p>
</li>
<li><p>Community detection based clustering.</p>
</li>
<li><p>Partitioning informed by observed query patterns.</p>
</li>
</ol>
<p>In practice, teams can achieve topology-aware partitioning by first analyzing query patterns and identifying frequently traversed relationships. Based on this analysis, related entities are co-located within the same partition to minimize cross-partition queries.</p>
<p>Graph processing frameworks and database tools often provide built-in algorithms for community detection, which help group highly connected nodes. Teams can also monitor query performance over time and iteratively refine partitioning strategies to align with evolving workloads.</p>
<p>By combining domain driven design with continuous performance monitoring, teams can incrementally optimize graph layouts without requiring major architectural changes.</p>
<p>In production-inspired environments, topology-aware strategies significantly reduce traversal fan out and improve both median and tail latency under concurrent load.</p>
<p>Though repartitioning introduces operational complexity, the performance gains justify the effort once the knowledge graph becomes central to digital product delivery.</p>
<h3 id="heading-in-practice-partitioning-by-product-domain">In Practice: Partitioning by Product Domain</h3>
<p>Let’s look at what happens when our supply chain graph scales across multiple database nodes.</p>
<p>If we use <strong>Default Hash Partitioning</strong>, the graph is split randomly by node IDs. Alice might end up on Machine 1, the NeoPhone 15 on Machine 2, and the MX-200 Chip on Machine 3. A query tracking whether a component shortage affects Alice's order requires a slow, expensive network hop across three separate physical servers.</p>
<p>Using <strong>Topology-Aware Partitioning</strong>, we can configure the cluster to use the Region or Product_Line as a partitioning key.</p>
<ul>
<li><strong>Partition A (Europe Hub):</strong> Co-locates Region: EU, Product: NeoPhone 15, its internal MX-200 Chip, and local customer orders.</li>
</ul>
<p><strong>Result:</strong> A multi-hop traversal checking component supply chains for European customers happens entirely within local memory on a single machine, reducing query latency.</p>
<h2 id="heading-managing-semantic-inference-without-sacrificing-performance">Managing Semantic Inference Without Sacrificing Performance</h2>
<p>Semantic inference is a defining strength of EKGs but also a frequent source of scalability challenges.</p>
<h3 id="heading-the-inference-cost-problem">The Inference Cost Problem</h3>
<p>Applying full ontology reasoning at query time can dramatically increase computational overhead. In some systems, inference effectively multiplies graph size, increasing memory and CPU consumption. Not all inferred relationships are equally valuable for every workload.</p>
<h3 id="heading-strategies-for-selective-inference-and-materialization">Strategies for Selective Inference and Materialization</h3>
<p>Scalable EKG platforms typically adopt a selective strategy:</p>
<ol>
<li><p>Precompute and materialize frequently accessed inferences</p>
</li>
<li><p>Offload complex reasoning to batch or asynchronous pipelines</p>
</li>
<li><p>Disable low value inference paths in latency-sensitive workloads</p>
</li>
</ol>
<p>Hierarchical classifications and role-based relationships are often materialized ahead of time, while complex rule based reasoning is reserved for offline processing. This approach stabilizes query latency and reduces peak CPU utilization in enterprise deployments.</p>
<h3 id="heading-in-practice-materializing-the-compliance-path">In Practice: Materializing the Compliance Path</h3>
<p>Recall our semantic rule: <em>If a component has a supply risk, the final product inherits that risk.</em></p>
<ul>
<li><p><strong>The Scalability Bottleneck (Query-Time Inference):</strong> Every time an enterprise dashboard loads a product catalog of 10,000 items, the engine must recursively calculate: Product -&gt; Has Component -&gt; Supplied By -&gt; Supplier Country -&gt; Embargo List. Under high concurrent load, this calculation crashes performance.</p>
</li>
<li><p><strong>The Optimization (Materialization):</strong> We run an asynchronous batch job or Kafka consumer that listens for supplier updates. When a supplier's status changes, it computes the inference <em>once</em> and writes a direct property <code>is_high_risk: true</code> directly onto the Product node in the operational LPG.</p>
</li>
</ul>
<p>Now, the customer-facing application reads a simple, static property without running an expensive multi-hop recursive inference query during runtime.</p>
<h2 id="heading-improving-query-performance-with-smarter-planning">Improving Query Performance with Smarter Planning</h2>
<p>As query complexity increases, query planning becomes a decisive performance lever.</p>
<h3 id="heading-limitations-of-static-planning">Limitations of Static Planning</h3>
<p>Traditional graph engines often rely on static heuristics or limited statistics for execution planning. In dynamic enterprise environments where data distributions evolve, these heuristics frequently produce suboptimal execution plans, leading to unpredictable performance.</p>
<h3 id="heading-ml-assisted-query-optimization">ML-Assisted Query Optimization</h3>
<p>Machine learning techniques are increasingly being applied to query optimization, particularly for cardinality estimation. By learning from historical query execution data, ML models can predict plan costs more accurately than rule-based systems.</p>
<p>In controlled experiments and production pilots, ML-assisted planning has demonstrated substantial reductions in execution time for complex traversals, as well as improved consistency in response times.</p>
<p>While implementation requires operational maturity, this represents a promising direction for large scale graph optimization.</p>
<h3 id="heading-in-practice-optimizing-traversal-direction">In Practice: Optimizing Traversal Direction</h3>
<p>Consider this query on our data: <em>"Find all customers who purchased a product containing the MX-200 Chip."</em></p>
<p>There are two ways the graph execution planner can execute this:</p>
<ol>
<li><p><strong>Plan A:</strong> Start at Component: MX-200, find the products it belongs to, and then find the customers who bought those products.</p>
</li>
<li><p><strong>Plan B:</strong> Scan <em>all</em> Customer nodes in the database, look at their purchases, and filter for the ones containing the chip.</p>
</li>
</ol>
<p>If the MX-200 is a rare chip used in only one niche product, <strong>Plan A</strong> is incredibly fast. If it is a generic resistor used in millions of products, <strong>Plan B</strong> or a modified hybrid plan might be more efficient.</p>
<p>An ML-assisted query planner analyzes the real-time cardinality (the actual count) of the PART_OF and PURCHASED relationships in your specific database instance. It prevents the graph engine from choosing a disastrously slow traversal path when data distributions shift unexpectedly.</p>
<h2 id="heading-observability-as-a-first-class-requirement">Observability as a First Class Requirement</h2>
<p>Scalability can't be managed without deep observability.</p>
<h3 id="heading-beyond-infrastructure-metrics">Beyond Infrastructure Metrics</h3>
<p>Monitoring CPU and memory alone provides limited insight into graph-specific performance issues. Effective EKG observability includes:</p>
<ol>
<li><p>Query level latency metrics</p>
</li>
<li><p>Traversal depth and fan-out tracking</p>
</li>
<li><p>Inference cost monitoring</p>
</li>
<li><p>Partition imbalance detection</p>
</li>
</ol>
<h3 id="heading-closing-the-optimization-loop">Closing the Optimization Loop</h3>
<p>By continuously analyzing these signals, teams can iteratively refine partitioning strategies, caching policies, and materialization decisions. This feedback loop improves predictability and reduces production incidents.</p>
<p>In practice, strong observability often distinguishes proactive optimization from reactive firefighting.</p>
<h2 id="heading-impact-on-digital-product-platforms">Impact on Digital Product Platforms</h2>
<p>When applied collectively, these optimization strategies materially enhance scalability and reliability. Across enterprise deployments, teams commonly observe:</p>
<ol>
<li><p>Reduced latency in real time workloads</p>
</li>
<li><p>Improved ingestion throughput under sustained load</p>
</li>
<li><p>Linear or near linear scaling as datasets grow</p>
</li>
<li><p>Greater stability during traffic spikes</p>
</li>
</ol>
<p>These technical improvements translate directly into business outcomes: faster recommendations, more relevant search results, and increased confidence in deploying EKGs as mission critical infrastructure.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Enterprise knowledge graphs are no longer experimental. They're becoming the backbone of intelligent, data driven systems. As teams move toward AI-powered decision making, the role of knowledge graphs is expanding beyond storage into enabling context-aware reasoning and automation.</p>
<p>An optimized EKG isn't just a database – it acts as the connective tissue between data, models, and real world applications. It provides the structured context that modern AI systems, including agentic workflows and autonomous decision engines, rely on to operate effectively.</p>
<p>By adopting hybrid architectures, topology-aware partitioning, and intelligent query strategies, teams can build scalable and resilient graph systems that support both operational and analytical workloads.</p>
<p>Ultimately, organizations that invest in well-designed knowledge graph infrastructure will be better positioned to power the next generation of AI systems where retrieval, reasoning, and action are seamlessly integrated.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Software System Design ]]>
                </title>
                <description>
                    <![CDATA[ Level up your system design skills! We just published a course on the freeCodeCamp.org YouTube channel that progresses from foundational concepts to production-ready systems, covering databases, scali ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-software-system-design/</link>
                <guid isPermaLink="false">69e0e1d794b913c9133551e2</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 16 Apr 2026 13:19:19 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5f68e7df6dfc523d0a894e7c/faae70fb-e5b6-45b4-b02d-abdce412781d.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Level up your system design skills!</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that progresses from foundational concepts to production-ready systems, covering databases, scaling, and load balancing. You will learn practical techniques for building and securing APIs, including RESTful and GraphQL. This course was developed by Hayk Simonyan.</p>
<p>Here are the sections in this course:</p>
<ul>
<li><p>Introduction</p>
</li>
<li><p>Single Server Setup</p>
</li>
<li><p>Databases: SQL, NoSQL, Graph</p>
</li>
<li><p>Vertical vs Horizontal Scaling</p>
</li>
<li><p>Load Balancing</p>
</li>
<li><p>Health Checks</p>
</li>
<li><p>Single Point of Failure (SPOF)</p>
</li>
<li><p>API Design</p>
</li>
<li><p>API Protocols</p>
</li>
<li><p>Transport Layer: TCP, UDP</p>
</li>
<li><p>RESTful APIs</p>
</li>
<li><p>GraphQL</p>
</li>
<li><p>Authentication</p>
</li>
<li><p>Authorization</p>
</li>
<li><p>Security</p>
</li>
</ul>
<p>Watch the full course on <a href="https://www.youtube.com/watch?v=C842vFY5kRo">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/C842vFY5kRo" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Reliable AI Systems. ]]>
                </title>
                <description>
                    <![CDATA[ We've all been there: You open ChatGPT, drop a prompt. "Extract all emails from this sheet and categorize by sentiment." It gives you something close. You correct it, it apologizes, and gives you a ne ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-reliable-ai-systems/</link>
                <guid isPermaLink="false">69d7dc42fa7251682ed20d5b</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jide Abdul-Qudus ]]>
                </dc:creator>
                <pubDate>Thu, 09 Apr 2026 17:05:06 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/79cc7c0e-1348-4827-934d-a5677c74c362.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>We've all been there: You open ChatGPT, drop a prompt. "Extract all emails from this sheet and categorize by sentiment." It gives you something close. You correct it, it apologizes, and gives you a new version. You ask for a different format, and suddenly, it's lost all context from earlier, and you're starting over.</p>
<p>Errors like that could be fine for little tasks, but it's a disaster for production systems. The gap between "this worked in my ChatGPT conversation" and "this runs reliably in production" is massive. It's not closed by better prompts. It's closed by <strong>engineering.</strong></p>
<p>This article is about that engineering. You'll learn the architecture patterns, failure modes, and implementation strategies that separate AI experiments from AI products.</p>
<h2 id="heading-what-youll-learn">What You'll Learn</h2>
<p>In this tutorial, you'll learn how to:</p>
<ul>
<li><p>Understand why AI systems fail differently from traditional software</p>
</li>
<li><p>Identify and prevent the three critical failure modes in production AI</p>
</li>
<li><p>Implement the validator sandwich pattern for consistent outputs</p>
</li>
<li><p>Build observable pipelines with proper monitoring and alerting</p>
</li>
<li><p>Control costs at scale with rate limiting and circuit breakers</p>
</li>
<li><p>Design a complete production-ready AI architecture</p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most from this tutorial, you should have:</p>
<ul>
<li><p>Basic understanding of any programming language</p>
</li>
<li><p>Familiarity with REST APIs and asynchronous programming</p>
</li>
<li><p>Experience with at least one LLM API (OpenAI, Anthropic, or similar)</p>
</li>
<li><p>Node.js installed locally (optional, for running code examples)</p>
</li>
</ul>
<p>You don't need to be an expert in any of these. Intermediate knowledge is sufficient.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a href="#heading-what-makes-ai-systems-fundamentally-different">What Makes AI Systems Fundamentally Different</a></p>
</li>
<li><p><a href="#heading-failure-mode-1-inconsistent-outputs">Failure Mode #1: Inconsistent Outputs</a></p>
</li>
<li><p><a href="#heading-failure-mode-2-silent-failures">Failure Mode #2: Silent Failures</a></p>
</li>
<li><p><a href="#heading-failure-mode-3-uncontrolled-costs">Failure Mode #3: Uncontrolled Costs</a></p>
</li>
<li><p><a href="#heading-how-to-build-a-complete-production-architecture">How to Build a Complete Production Architecture</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-what-makes-ai-systems-fundamentally-different">What Makes AI Systems Fundamentally Different</h2>
<p>Traditional software is <strong>deterministic</strong>. You write <code>if (urgency &gt; 8) { return 'high' }</code> and it does exactly that, every single time. Same input, same output. Forever. You can write unit tests that cover every path. You can predict every failure mode.</p>
<p>AI systems, on the other hand, are <strong>probabilistic</strong>. You ask an large language model (LLM) to classify urgency and sometimes it says "high," sometimes "urgent," sometimes it gives you a 1–10 score, sometimes it writes a paragraph explaining its reasoning. Same input, different outputs, depending on temperature settings, model version, context window, and factors you can't fully control.</p>
<p>Here's what that looks like in practice:</p>
<table>
<thead>
<tr>
<th>Challenge</th>
<th>Traditional systems</th>
<th>AI systems</th>
</tr>
</thead>
<tbody><tr>
<td>Consistency</td>
<td>100% reproducible</td>
<td>Varies per request</td>
</tr>
<tr>
<td>Debugging</td>
<td>Stack traces, logs</td>
<td>"The model just changed its behaviour."</td>
</tr>
<tr>
<td>Testing</td>
<td>Unit tests cover all paths</td>
<td>Can't test all possible outputs</td>
</tr>
<tr>
<td>Deployment</td>
<td>Deploy once, works forever</td>
<td>Degrades over time (data drift)</td>
</tr>
<tr>
<td>Failure modes</td>
<td>Predictable, finite</td>
<td>Creative, infinite</td>
</tr>
</tbody></table>
<p>The engineering challenge is: <strong>how do you build reliability on top of inherent unpredictability?</strong></p>
<p>The answer is not "use a better model." The model is maybe 20% of the solution. The remaining 80% is the system you build around it.</p>
<h2 id="heading-failure-mode-1-inconsistent-outputs">Failure Mode #1: Inconsistent Outputs</h2>
<h3 id="heading-the-problem">The Problem</h3>
<p>You ask the AI to extract a customer email from a support ticket. Sometimes you get the email back. Sometimes you get just the name. Sometimes you get a phone number. The format changes every time. Same prompt, different outputs.</p>
<pre><code class="language-plaintext">Prompt: "Extract the customer email from this support ticket"

Output on Monday:    "john@example.com"
Output on Tuesday:   "Customer email: john@example.com (verified)"
Output on Wednesday:   "John Doe"
Output on Thursday: {
                       "customer_info": {
                         "email": "john@example.com"
                       }
                     }
</code></pre>
<p>All three outputs contain correct information, but you can't parse them programmatically. You can't route tickets, trigger workflow systems, or integrate with other code because your response data lacks consistency.</p>
<h3 id="heading-the-solution-the-validator-sandwich-pattern">The Solution: The Validator Sandwich Pattern</h3>
<p>The validator sandwich pattern (also called the guardrails pattern) ensures the AI system doesn't generate or process the wrong data by sandwiching your AI between two layers of deterministic code.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/613e8e5622b7a41dfe5fefa7/cbb83d63-6f97-4918-ae98-5a68e371284c.png" alt="Diagram showing three layers of the Validator Sandwich Pattern: Input Guardrails (top bun), LLM Processing (meat), and Output Guardrails (bottom bun) with arrows showing data flow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>Essentially, you have three layers:</p>
<ol>
<li><p><strong>The top bun</strong>: Input guardrails (deterministic)</p>
</li>
<li><p><strong>The meat</strong>: The LLM (probabilistic)</p>
</li>
<li><p><strong>The bottom bun</strong>: Output guardrails (deterministic)</p>
</li>
</ol>
<p>Let's break down each layer.</p>
<h3 id="heading-the-top-bun-input-guardrails">The Top Bun: Input Guardrails</h3>
<p>Before anything touches the AI, validate it. Reject garbage immediately, fail fast and cheaply. Here's a basic example with deterministic code that checks the data being received:</p>
<pre><code class="language-typescript">function validateTicketInput(raw): TicketInput {
  // Type checks
  if (!raw.email || typeof raw.email !== "string") {
    throw new ValidationError("Missing or invalid email");
  }

  // Format checks
  if (!isValidEmail(raw.email)) {
    throw new ValidationError(`Invalid email format: ${raw.email}`);
  }

  // Range checks
  if (!raw.body || raw.body.length &lt; 10) {
    throw new ValidationError("Ticket body too short to classify");
  }

  if (raw.body.length &gt; 10000) {
    throw new ValidationError("Ticket body exceeds max length");
  }

  // Return typed, validated input
  return {
    email: raw.email.toLowerCase().trim(),
    subject: raw.subject?.trim() || "No subject",
    body: raw.body.trim(),
    timestamp: new Date(raw.timestamp),
  };
}
</code></pre>
<p>This runs before the LLM is ever called. It's fast, cheap, and deterministic. It catches easy failures immediately.</p>
<h3 id="heading-the-meat-structured-outputs-from-the-llm">The Meat: Structured Outputs from the LLM</h3>
<p>Stop asking the AI for free text. Force it into a schema. Most modern APIs support this directly.</p>
<p>So what does "free text" mean? When you prompt an LLM without constraints, it returns unstructured natural language. The model decides the format. Sometimes it's a sentence, sometimes a paragraph, sometimes it adds extra context you didn't ask for. This makes programmatic parsing nearly impossible.</p>
<p>Forcing it into a schema, on the other hand, means that you explicitly tell the model: "Respond only with JSON matching this exact structure", for example. Modern LLM APIs have built-in features to enforce this. Instead of hoping the AI formats its response correctly, you make it structurally impossible for it to return anything else.</p>
<p>Here's the difference in practice:</p>
<p><strong>Without schema enforcement (free text):</strong></p>
<pre><code class="language-typescript">const response = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{
    role: "user",
    content: "Classify this support ticket as bug, billing, or feature request: " + ticketText
  }]
});

// Response could be:
// "This appears to be a billing issue"
// "billing"
// "Category: Billing (confidence: high)"
// { "type": "billing" }  &lt;- if you're lucky
</code></pre>
<p><strong>With schema enforcement:</strong></p>
<pre><code class="language-typescript">const response = await openai.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [{
    role: "user",
    content: "Classify this support ticket: " + ticketText
  }],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "ticket_classification",
      strict: true,
      schema: {
        type: "object",
        properties: {
          category: {
            type: "string",
            enum: ["bug", "billing", "feature", "other"]
          },
          confidence: {
            type: "number",
            minimum: 0,
            maximum: 1
          },
          priority: {
            type: "integer",
            minimum: 1,
            maximum: 5
          }
        },
        required: ["category", "confidence", "priority"],
        additionalProperties: false
      }
    }
  }
});

// Response is GUARANTEED to be:
// { "category": "billing", "confidence": 0.89, "priority": 2 }
</code></pre>
<p>The <code>response_format</code> parameter forces the model to output valid JSON matching your schema. If it can't, the API will retry internally until it does. You get predictable, parseable data every single time.</p>
<p>The key difference: you're making the AI conform to <strong>your</strong> format instead of hoping it does the right thing.</p>
<h3 id="heading-the-bottom-bun-output-guardrails">The Bottom Bun: Output Guardrails</h3>
<p>This is the most critical layer. LLMs will hallucinate. This layer catches those hallucinations before they break your database or confuse your users.</p>
<p>Guardrails are validation checks that run after the LLM responds. Think of them as safety barriers on a highway: they don't prevent the car from moving, but they can stop it from going off the road.</p>
<p>In AI systems, guardrails verify that:</p>
<ol>
<li><p>The output matches your expected schema</p>
</li>
<li><p>The data types are correct</p>
</li>
<li><p>The values fall within acceptable ranges</p>
</li>
<li><p>The business logic makes sense</p>
</li>
</ol>
<p>Alright, now you have a structured response. Now you'll want to validate it aggressively before you use it:</p>
<pre><code class="language-typescript">function validateClassification(raw): Classification {
  const required = ["category", "confidence", "priority", "reasoning"];
  for (const field of required) {
    if (raw[field] === undefined || raw[field] === null) {
      throw new ValidationError(`Missing required field: ${field}`);
    }
  }

  if (!["bug", "billing", "feature", "other"].includes(raw.category)) {
    throw new ValidationError(`Invalid category: ${raw.category}`);
  }

  if (typeof raw.confidence !== "number" || 
      raw.confidence &lt; 0 || raw.confidence &gt; 1) {
    throw new ValidationError(`Invalid confidence: ${raw.confidence}`);
  }

  if (!Number.isInteger(raw.priority) || 
      raw.priority &lt; 1 || raw.priority &gt; 5) {
    throw new ValidationError(`Invalid priority: ${raw.priority}`);
  }

  if (raw.category === "billing" &amp;&amp; raw.priority &gt; 3) {
    logger.warn("Suspicious: billing classified as low priority", raw);
  }

  return raw as Classification;
}
</code></pre>
<p>Validating aggressively means checking everything, not just schema compliance. You're validating:</p>
<ul>
<li><p><strong>Schema compliance</strong>: Does the JSON have the right fields?</p>
</li>
<li><p><strong>Type safety</strong>: Is "confidence" actually a number, not a string?</p>
</li>
<li><p><strong>Range validity</strong>: Is confidence between 0 and 1, not -5 or 999?</p>
</li>
<li><p><strong>Business logic</strong>: Does the combination of fields make sense for your domain?</p>
</li>
<li><p><strong>Confidence thresholds</strong>: Is the AI actually confident in this answer?</p>
</li>
</ul>
<p>If any validation fails, you don't silently accept bad data. You have three options:</p>
<ol>
<li><p><strong>Retry with a clearer prompt</strong>: Ask the model to try again with stricter instructions</p>
</li>
<li><p><strong>Escalate to human review</strong>: Log the failure and route to a review queue</p>
</li>
<li><p><strong>Use a fallback</strong>: Return a safe default value that requires human attention</p>
</li>
</ol>
<h3 id="heading-the-deterministic-rule">The Deterministic Rule</h3>
<p>Here's a rule to follow religiously:</p>
<blockquote>
<p><strong>If it can be solved with an if-statement, don't use AI.</strong></p>
</blockquote>
<p>Email format validation? Use regex. Date parsing? Use a date library. Checking if a string contains a keyword? Use a string method. Math? Use actual math.</p>
<p>AI is expensive and probabilistic. Traditional code is free, instant, and deterministic. Use AI for genuinely ambiguous tasks, extracting meaning from unstructured text, generating content, and reasoning about complex inputs. Let deterministic code handle everything else.</p>
<h2 id="heading-failure-mode-2-silent-failures">Failure Mode #2: Silent Failures</h2>
<h3 id="heading-the-problem">The Problem</h3>
<p>Model hallucinations are quite common in AI workflows, ranging from degraded accuracy to outdated training data to misclassification issues. This is the scariest failure mode because you don't know it's happening.</p>
<p>Consider accuracy drift. You trained your model on 2024 data. It's now mid-2026. Your vendors changed their invoice formats. Your classification accuracy has drifted from 95% down to 71%. You won't know until you do a quarterly audit. And by then, thousands of records have been processed incorrectly.</p>
<p>The principle is simple: <strong>you cannot fix what you cannot see.</strong></p>
<h3 id="heading-the-solution-observable-pipelines">The Solution: Observable Pipelines</h3>
<p>Every production AI system needs observability baked in from day one. Here's how this plays out in a production system:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/613e8e5622b7a41dfe5fefa7/746f2b2c-9825-46da-b0da-0154575a9dba.jpg" alt="Observable Pipeline Flow showing Input, LLM Processing, Confidence Gate and Monitoring Dashboard Flow" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>In the diagram above:</p>
<ol>
<li><p><strong>Input arrives</strong>: A user request comes in (support ticket, document, query). You log: request ID, timestamp, user ID, input hash (for deduplication).</p>
</li>
<li><p><strong>LLM Processing</strong>: The request goes to your AI model. You log which model was called, how long it took (latency), how many tokens used, what it cost, and critically, the confidence score.</p>
</li>
<li><p><strong>Confidence Gate</strong>: This is where you make a routing decision:</p>
<ul>
<li><p><strong>High confidence (&gt;0.8)</strong>: Auto-process and execute the action</p>
</li>
<li><p><strong>Medium confidence (0.6-0.8)</strong>: Send to human review queue</p>
</li>
<li><p><strong>Low confidence (&lt;0.6)</strong>: Immediate escalation + alert</p>
</li>
</ul>
</li>
<li><p><strong>Monitoring Dashboard</strong>: All this data flows into your observability tools, where you track trends over time.</p>
</li>
</ol>
<p>With monitoring, you can detect issues in your system and address them as soon as possible. Monitoring doesn't just catch problems. It gives you data to diagnose and fix them in hours instead of months.</p>
<h4 id="heading-what-youre-measuring-and-why">What you're measuring and why:</h4>
<table>
<thead>
<tr>
<th><strong>Metric</strong></th>
<th><strong>Why it Matters</strong></th>
</tr>
</thead>
<tbody><tr>
<td>Response Time</td>
<td>API Health, model issues</td>
</tr>
<tr>
<td>Confidence</td>
<td>Model degradation</td>
</tr>
<tr>
<td>Human Override Rate</td>
<td>Output quality problems</td>
</tr>
<tr>
<td>Error Rate</td>
<td>System Failures</td>
</tr>
<tr>
<td>Cost per Request</td>
<td>Budget control</td>
</tr>
<tr>
<td>Token Usage Trend</td>
<td>Prompt efficiency</td>
</tr>
</tbody></table>
<p>The goal is not to remove humans from the loop, it's to <strong>only involve humans when the system is genuinely uncertain.</strong></p>
<h2 id="heading-failure-mode-3-uncontrolled-costs">Failure Mode #3: Uncontrolled Costs</h2>
<h3 id="heading-the-problem">The Problem</h3>
<p>You test your workflow with 10 tickets. It works great and costs 50 cents. You deploy to production. 1,000 requests hit your API. Your bill: $500 for the day.</p>
<p>Or you write a retry loop incorrectly. It creates infinite API calls. Your bill: $5,000 for the day.</p>
<p>Or you're using the most expensive model for everything, including simple tasks that a cheaper model could handle.</p>
<p>The reality: <strong>"works for 10 requests" ≠ "works for 10,000 requests."</strong> Scale changes everything.</p>
<h3 id="heading-the-solution-gated-pipelines-with-circuit-breakers">The Solution: Gated Pipelines with Circuit Breakers</h3>
<p>To move from a fragile prototype to a robust production system, you must abandon the naive approach of directly connecting user inputs to LLM APIs. Instead, implement a <strong>gated pipeline</strong>.</p>
<p>Think of this architecture as a series of blast doors. A request must successfully pass through each gate before it earns the right to cost you money. If any gate closes, the request is rejected cheaply and quickly, protecting your budget and your upstream dependencies.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/613e8e5622b7a41dfe5fefa7/b24b1504-91c7-41e6-b582-996b8ab2d0eb.jpg" alt="Gated Pipeline Architecture" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>From the diagram above, these gates are:</p>
<ol>
<li><p>The rate limiter</p>
</li>
<li><p>The cache check</p>
</li>
<li><p>The request queue</p>
</li>
<li><p>The circuit breaker</p>
</li>
</ol>
<p>Let's examine each one.</p>
<h3 id="heading-gate-1-rate-limiting">Gate 1: Rate limiting</h3>
<p>The first line of defence stops abuse before it enters your system. In standard web development, rate limiting is about protecting the server CPU. In AI development, it's about protecting your wallet.</p>
<h3 id="heading-gate-2-cache-check">Gate 2: Cache check</h3>
<p>The cheapest LLM API call is the one you never have to make. Many AI requests are repeated or highly similar. Cache aggressively.</p>
<h3 id="heading-gate-3-request-queue">Gate 3: Request queue</h3>
<p>LLM APIs are not like standard REST APIs; requests often take 10–30 seconds to complete. If 500 users hit "submit" simultaneously, your server cannot open 500 simultaneous connections without crashing or hitting provider concurrency limits. A request queue solves this by batching requests and processing them at a controlled rate.</p>
<h3 id="heading-gate-4-circuit-breaker">Gate 4: Circuit breaker</h3>
<p>Retry logic is necessary for transient network blips, but it is destructive during a real outage. If an LLM provider is experiencing downtime and returning 500 errors, a naive retry loop will frantically hammer their API, wasting your money on failed requests.</p>
<h3 id="heading-how-to-implement-a-gated-pipeline">How to implement a gated pipeline</h3>
<p>Here's an example implementation showing all four gates working together:</p>
<p><strong>Step 1: Rate Limiter (using Redis)</strong></p>
<pre><code class="language-typescript">import { RateLimiterRedis } from "rate-limiter-flexible";
import Redis from "ioredis";

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: 6379
});

// Rate limiting per user
const userLimiter = new RateLimiterRedis({
  storeClient: redis,
  keyPrefix: "rl:user",
  points: 100,        
  duration: 3600,     
  blockDuration: 60   
});

// Rate limiting globally 
const globalLimiter = new RateLimiterRedis({
  storeClient: redis,
  keyPrefix: "rl:global",
  points: 1000,       
  duration: 3600      
});
</code></pre>
<p><strong>Step 2: Cache Layer</strong></p>
<pre><code class="language-typescript">import { createHash } from "crypto";

class AICache {
  private redis: Redis;
  private ttl: number = 3600; 

  hashInput(input: string): string {
    return createHash("sha256").update(input).digest("hex");
  }

  async get(input: string): Promise {
    const key = `ai:cache:${this.hashInput(input)}`;
    const cached = await this.redis.get(key);
    
    if (cached) {
      // Cache hit - free!
      await metrics.increment("ai.cache.hits");
      return JSON.parse(cached);
    }
    
    await metrics.increment("ai.cache.misses");
    return null;
  }

  async set(input: string, result: T): Promise {
    const key = `ai:cache:${this.hashInput(input)}`;
    await this.redis.setex(key, this.ttl, JSON.stringify(result));
  }
}
</code></pre>
<p><strong>Step 3: Request Queue</strong></p>
<pre><code class="language-typescript">import Queue from "bull";

const aiQueue = new Queue("ai-requests", {
  redis: {
    host: process.env.REDIS_HOST,
    port: 6379
  }
});

aiQueue.process(5, async (job) =&gt; {
  // Only 5 simultaneous LLM calls max
  const { ticket } = job.data;
  return await callLLM(ticket);
});

async function enqueueRequest(ticket: Ticket) {
  const job = await aiQueue.add(
    { ticket },
    {
      attempts: 3,
      backoff: {
        type: "exponential",
        delay: 2000
      }
    }
  );
  
  return job.finished(); 
}
</code></pre>
<p><strong>Step 4: Circuit Breaker</strong></p>
<pre><code class="language-typescript">enum CircuitState {
  CLOSED,   
  OPEN,     
  HALF_OPEN 
}

class CircuitBreaker {
  private state = CircuitState.CLOSED;
  private failures = 0;
  private lastFailureTime?: Date;
  private successesInHalfOpen = 0;

  private readonly failureThreshold = 3;
  private readonly openDurationMs = 5 * 60 * 1000; 
  private readonly halfOpenSuccesses = 2;

  async execute(
    fn: () =&gt; Promise,
    fallback?: () =&gt; T
  ): Promise {
    if (this.state === CircuitState.OPEN) {
      const elapsed = Date.now() - (this.lastFailureTime?.getTime() || 0);
      
      if (elapsed &lt; this.openDurationMs) {
        // Still in open state - use fallback or throw
        if (fallback) {
          logger.warn("Circuit OPEN - using fallback");
          return fallback();
        }
        throw new Error("Circuit breaker OPEN - service unavailable");
      }
      
      // Transition to half-open
      this.state = CircuitState.HALF_OPEN;
      logger.info("Circuit transitioning to HALF_OPEN");
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess() {
    if (this.state === CircuitState.HALF_OPEN) {
      this.successesInHalfOpen++;
      
      if (this.successesInHalfOpen &gt;= this.halfOpenSuccesses) {
        // Service recovered - close circuit
        this.state = CircuitState.CLOSED;
        this.failures = 0;
        this.successesInHalfOpen = 0;
        logger.info("Circuit CLOSED - service recovered");
      }
    } else {
      this.failures = 0;
    }
  }

  private onFailure() {
    this.failures++;
    this.lastFailureTime = new Date();

    if (this.state === CircuitState.HALF_OPEN) {
      // Failed during test - back to open
      this.state = CircuitState.OPEN;
      this.successesInHalfOpen = 0;
      logger.error("Circuit reopened during HALF_OPEN test");
    } else if (this.failures &gt;= this.failureThreshold) {
      // Too many failures - open circuit
      this.state = CircuitState.OPEN;
      logger.error(`Circuit OPEN after ${this.failures} failures`);
    }
  }
}
</code></pre>
<p><strong>Step 5: Putting it all together</strong></p>
<pre><code class="language-typescript">const cache = new AICache();
const circuitBreaker = new CircuitBreaker();

async function processWithGatedPipeline(ticket: Ticket) {
  try {
    await userLimiter.consume(ticket.userId);
    await globalLimiter.consume("global");
  } catch (error) {
    throw new Error("Rate limit exceeded. Please try again later.");
  }

  const cacheKey = ticket.body;
  const cached = await cache.get(cacheKey);
  if (cached) {
    logger.info("Cache hit - returning cached result");
    return cached;
  }

  const queuedResult = await enqueueRequest(ticket);

  const result = await circuitBreaker.execute(
    async () =&gt; {
      const classification = await callLLM(ticket);
      await cache.set(cacheKey, classification);
      return classification;
    },
    () =&gt; ({
      category: "other",
      confidence: 0,
      requiresHumanReview: true,
      reason: "service_unavailable"
    })
  );

  return result;
}
</code></pre>
<p>What this achieves:</p>
<ul>
<li><p><strong>Rate limiting</strong>: Prevents abuse and runaway costs</p>
</li>
<li><p><strong>Caching</strong>: 30-40% cost reduction on repeated queries</p>
</li>
<li><p><strong>Queueing</strong>: Prevents server overload during traffic spikes</p>
</li>
<li><p><strong>Circuit breaker</strong>: Fails fast during outages instead of wasting money on retries</p>
</li>
</ul>
<p>Each gate is cheap to operate. Together, they protect your system from the most common production failures.</p>
<h2 id="heading-how-to-build-a-complete-production-architecture">How to Build a Complete Production Architecture</h2>
<p>When you combine all three failure mode solutions-consistent outputs, observability, and cost control, you get a complete production architecture.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/613e8e5622b7a41dfe5fefa7/8c461611-3699-41b4-9f41-1b3e0ad6c22e.jpg" alt="Full Architecture" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>When you solve for all three major failure modes, inconsistent outputs, silent failures, and uncontrolled costs. You graduate from a simple script to a true enterprise-grade system. This architecture doesn't just generate text; it actively protects itself, manages resources, and learns from its mistakes.</p>
<h3 id="heading-the-complete-workflow-implementation">The Complete Workflow Implementation</h3>
<p>Here's how all the pieces we've covered fit together in a single workflow. This brings together the validation functions from Failure Mode #1, the observability from Failure Mode #2, and the gated pipeline from Failure Mode #3:</p>
<pre><code class="language-typescript">class TicketWorkflow {
  async processTicket(rawInput: unknown): Promise&lt;TicketResult&gt; {
    const requestId = generateId();
    const startTime = Date.now();

    try {
      // LAYER 1: Input validation + rate limiting + cache
      const ticket = validateTicketInput(rawInput);
      await rateLimiter.consume(ticket.userId);
      
      const cached = await cache.get(ticket.body);
      if (cached) return { ...cached, source: "cache" };

      // LAYER 2: AI processing with circuit breaker protection
      const classification = await circuitBreaker.execute(() =&gt; 
        classifyTicket(ticket)
      );

      // LAYER 3: Output validation + confidence routing
      const validated = validateClassification(classification);
      
      let action: string;
      if (validated.confidence &gt;= 0.8) {
        await sendToAgent(ticket, validated);
        action = "auto_assigned";
      } else {
        await sendToReviewQueue(ticket, validated);
        action = "needs_review";
      }

      // LAYER 4: Log everything for observability
      await logger.log({
        requestId,
        userId: ticket.userId,
        confidence: validated.confidence,
        action,
        latencyMs: Date.now() - startTime,
        cost: calculateCost(classification.tokensUsed)
      });

      await cache.set(ticket.body, validated);
      return { classification: validated, action };

    } catch (error) {
      await logger.logError(requestId, error);
      throw error;
    }
  }
}
</code></pre>
<p>What each layer does:</p>
<p><strong>Layer 1 (Input)</strong> protects your system from bad data and abuse:</p>
<ul>
<li><p>Validates the ticket has required fields (email, subject, body)</p>
</li>
<li><p>Checks rate limits (prevents one user from overwhelming the system)</p>
</li>
<li><p>Returns cached results if we've seen this exact ticket before</p>
</li>
</ul>
<p><strong>Layer 2 (Orchestration)</strong> is where the AI does its work:</p>
<ul>
<li><p>Calls the LLM with structured output requirements</p>
</li>
<li><p>Wrapped in a circuit breaker (fails fast if the API is down)</p>
</li>
<li><p>Uses the cheapest model that works (Haiku for classification)</p>
</li>
</ul>
<p><strong>Layer 3 (Validation)</strong> ensures the output is safe to use:</p>
<ul>
<li><p>Validates the response matches our schema</p>
</li>
<li><p>Routes based on confidence (high confidence → auto-assign, low → human review)</p>
</li>
<li><p>Never blindly trusts AI output</p>
</li>
</ul>
<p><strong>Layer 4 (Observability)</strong> tracks everything:</p>
<ul>
<li><p>Logs every request with latency, cost, and confidence scores</p>
</li>
<li><p>Sends metrics to your monitoring dashboard</p>
</li>
<li><p>Alerts on anomalies (confidence dropping, costs spiking)</p>
</li>
</ul>
<p>This architecture takes you from "it worked in my ChatGPT demo" to "it runs reliably at 10,000 tickets per day." The code is more complex than a simple API call, but the complexity is intentional. It's what makes the system production-ready.</p>
<h2 id="heading-conclusion-engineering-over-prompting">Conclusion: Engineering Over Prompting</h2>
<p>The teams winning with AI right now aren't winning because they have better models. They're winning because they've built better <strong>systems</strong> around imperfect models.</p>
<p>Any company can call the OpenAI API. The ones that pull ahead are the ones who wrap that API call in validation, observability, cost controls, and thoughtful architecture — the ones who treat AI as a component in an assembly line, not a creative partner in a conversation.</p>
<p>The three things every production AI system needs:</p>
<ol>
<li><p><strong>Structure</strong>: Validators, schemas, deterministic layers that enforce consistency and eliminate unpredictability at the edges.</p>
</li>
<li><p><strong>Visibility</strong>: Logging, monitoring, and alerting so you catch problems in hours, not months. Observable pipelines that let you see exactly what the system is doing and why.</p>
</li>
<li><p><strong>Control</strong>: Rate limits, caching, circuit breakers, and cost gates so scale doesn't turn your experiment into a budget emergency.</p>
</li>
</ol>
<p>Reliable AI workflows aren't about better prompts. They're about better architecture around unreliable components.</p>
<p>If you found this helpful, you can connect with me on <a href="https://www.linkedin.com/in/jideabdqudus/">LinkedIn</a> or subscribe to my <a href="https://www.abdulqudus.com/newsletter/">newsletter</a>. You can also visit my <a href="https://www.abdulqudus.com/">website.</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build AI Agents That Remember User Preferences (Without Breaking Context) ]]>
                </title>
                <description>
                    <![CDATA[ Why Personalization Breaks Most AI Agents Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time. In practice, personalization... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-ai-agents-that-remember-user-preferences-without-breaking-context/</link>
                <guid isPermaLink="false">698cc32db8fec0245bd9996d</guid>
                
                    <category>
                        <![CDATA[ ai agents ]]>
                    </category>
                
                    <category>
                        <![CDATA[ llm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ observability ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer Tools ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nataraj Sundar ]]>
                </dc:creator>
                <pubDate>Wed, 11 Feb 2026 17:58:05 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770832641633/da49bdca-617e-4272-b5b7-012f3c6c1d61.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-why-personalization-breaks-most-ai-agents"><strong>Why Personalization Breaks Most AI Agents</strong></h2>
<p>Personalization is one of the most requested features in AI-powered applications. Users expect an agent to remember their preferences, adapt to their style, and improve over time.</p>
<p>In practice, personalization is unfortunately also one of the fastest ways to break an otherwise working AI agent.</p>
<p>Many agents start with a simple idea: keep adding more conversation history to the prompt. This approach works for demos, but it quickly fails in real applications. Context windows grow too large. Irrelevant information leaks into decisions. Costs increase. Debugging becomes nearly impossible.</p>
<p>If you want a personalized agent that survives production, you need more than a large language model. You need a way to connect the agent to tools, manage multi-step workflows, and store user preferences safely over time – without turning your system into a tangled mess of prompts and callbacks.</p>
<p>In this tutorial, you’ll learn how to design a personalized AI agent using three core building blocks:</p>
<ul>
<li><p><strong>Agent Development Kit (ADK)</strong> to orchestrate agent reasoning and execution</p>
</li>
<li><p><strong>Model Context Protocol (MCP)</strong> to connect tools with clear boundaries</p>
</li>
<li><p><strong>Long-term memory</strong> to store preferences without polluting context</p>
</li>
</ul>
<p>Rather than focusing on setup commands or vendor-specific walkthroughs, we'll focus on the architectural patterns that make personalized agents reliable, debuggable, and maintainable.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578645884/2fd77443-31d5-4db3-98f0-bba685122a6f.png" alt="User preferences influence an AI agent’s personalized response" class="image--center mx-auto" width="1452" height="578" loading="lazy"></p>
<p><em>Figure 1 — Personalization influences agent responses</em></p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-personalized-means-in-a-real-ai-agent">What “Personalized” Means in a Real AI Agent</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-the-agent-architecture-fits-together">How the Agent Architecture Fits Together</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-design-the-agent-core-with-adk">How to Design the Agent Core with ADK</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-connect-tools-safely-with-mcp">How to Connect Tools Safely with MCP</a></p>
</li>
<li><p><a class="post-section-overview" href="#how-to-add-long-term-memory-without-polluting-context">How to Add Long-Term Memory Without Polluting Context</a></p>
<ul>
<li><a class="post-section-overview" href="#privacy-consent-and-lifecycle-controls-production-checklist">Privacy, Consent, and Lifecycle Controls (Production Checklist)</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#how-the-end-to-end-agent-flow-works">How the End-to-End Agent Flow Works</a></p>
</li>
<li><p><a class="post-section-overview" href="#common-pitfalls-youll-hit-and-how-to-avoid-them">Common Pitfalls You’ll Hit (and How to Avoid Them)</a></p>
</li>
<li><p><a class="post-section-overview" href="#what-you-learned-and-where-to-go-next">What You Learned and Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>To follow along with this tutorial, you should have:</p>
<ul>
<li><p>Basic familiarity with Python</p>
</li>
<li><p>A general understanding of how large language models work</p>
</li>
<li><p>Optional: a Google Cloud account if you want to run an end-to-end demo. Otherwise, you can follow the architecture and code patterns locally with stubs. We’ll avoid deep infrastructure setup and focus on design patterns rather than deployment mechanics.</p>
</li>
</ul>
<p>You don’t need prior experience with ADK or MCP. I’ll introduce each concept as it appears.</p>
<h2 id="heading-what-personalized-means-in-a-real-ai-agent"><strong>What “Personalized” Means in a Real AI Agent</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578714303/4d25a7e4-fcdd-4a1a-a12c-411e41f2021f.png" alt="An AI agent accesses external tools through a protocol boundary/control layer" class="image--center mx-auto" width="1382" height="670" loading="lazy"></p>
<p><em>Figure 2 — Keep preferences out of the prompt: agent ↔ tools across a protocol boundary</em></p>
<p>Before writing any code, it’s important to define what personalization means in an AI agent.</p>
<p>Personalization is not the same as “remembering everything.” In practice, agent state usually falls into three categories:</p>
<ol>
<li><p><strong>Short-term context:</strong> Information needed to complete the current task. This belongs in the prompt.</p>
</li>
<li><p><strong>Session state:</strong> Temporary decisions or selections made during a workflow. This should be structured and scoped to a session.</p>
</li>
<li><p><strong>Long-term memory:</strong> Durable user preferences or facts that should persist across sessions.</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577191953/3df5aa02-2eb9-4214-bbef-52f18ddb353a.png" alt="Three panels comparing short-term context, session state, and long-term memory" class="image--center mx-auto" width="946" height="510" loading="lazy"></p>
<p><em>Figure 3 — Three kinds of agent state: context (now), session (today), memory (always)</em></p>
<p>Most problems happen when these categories are mixed together.</p>
<p>If you store long-term preferences directly in the prompt, the agent’s behavior becomes unpredictable. If you store everything permanently, memory grows without bounds. If you don’t scope memory at all, unrelated sessions start influencing each other.</p>
<p>A well-designed, personalized agent treats memory as a first-class system component, not as extra text added to a prompt.</p>
<p>In the next section, we'll look at how to structure the agent so these concerns stay separated. </p>
<p>By the end of this tutorial, you’ll understand how to design a personalized AI agent that uses long-term memory safely, connects to tools through clear boundaries, and remains debuggable as it grows.</p>
<h2 id="heading-how-the-agent-architecture-fits-together"><strong>How the Agent Architecture Fits Together</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577351960/9b14cadf-d650-4098-8ce1-9fd706537bb9.png" alt="Reference architecture showing a user, an AI agent core, tools, a memory service, and an orchestration runtime" class="image--center mx-auto" width="1100" height="554" loading="lazy"></p>
<p><em>Figure 4 — Reference architecture: agent core + tools + memory service + orchestration runtime</em></p>
<p>The above diagram shows a high-level, personalized AI agent architecture. In it, an agent core handles reasoning and planning while interacting with a tool interface layer, a long-term memory service, and an orchestration runtime.</p>
<p>Let’s now understand the moving parts of a personalized agent and how they interact.</p>
<p>At a high level, the system has four responsibilities:</p>
<ol>
<li><p><strong>Reasoning</strong> – deciding what to do next</p>
</li>
<li><p><strong>Execution</strong> – calling tools and services</p>
</li>
<li><p><strong>Memory</strong> – storing and retrieving long-term preferences</p>
</li>
<li><p><strong>Boundaries</strong> – controlling what the agent is allowed to do</p>
</li>
</ol>
<p>A common mistake you’ll see is to blur these responsibilities together. For example, letting the model decide when to write memory, or allowing tools to execute actions without clear constraints.</p>
<p>Instead, you'll design the system so each responsibility has a clear owner. The core components look like this:</p>
<ul>
<li><p><strong>Agent core</strong>: Handles reasoning and planning</p>
</li>
<li><p><strong>Tools</strong>: Perform external actions (read or write)</p>
</li>
<li><p><strong>MCP layer</strong>: Defines how tools are exposed and invoked</p>
</li>
<li><p><strong>Memory services</strong>: Store long-term user data safely</p>
</li>
</ul>
<p>ADK sits at the center, orchestrating how requests flow between these components. The model never directly talks to databases or services. It reasons about actions, and ADK coordinates execution.</p>
<p>This separation makes the system easier to reason about, debug, and extend.</p>
<h2 id="heading-how-to-design-the-agent-core-with-adk"><strong>How to Design the Agent Core with ADK</strong></h2>
<p>Before we dive in, a quick note on what ADK is<strong>.</strong>  </p>
<p><strong>Agent Development Kit (ADK)</strong> is an agent orchestration framework – the glue code between a large language model and your application. Instead of treating the model as a black box that directly “does things”, ADK helps you structure the agent as a system:</p>
<ul>
<li><p>The model focuses on <strong>reasoning</strong> (turning user intent, context, and memory into a structured plan)</p>
</li>
<li><p>Your runtime stays in control of <strong>execution</strong> (deciding which tools can run, how they run, and what gets logged or persisted)</p>
</li>
</ul>
<p>In other words, ADK is what lets you take tool calling and multi-step workflows out of a giant prompt and turn them into a maintainable and testable architecture. In this tutorial, we’ll use ADK to refer to that orchestration layer. The same patterns apply if you use a different agent framework.</p>
<p><strong>Note:</strong> The following code snippets are simplified reference examples intended to illustrate architectural patterns. They’re not production-ready drop-ins.</p>
<p>Once you understand the architecture, you can start designing the agent core. The agent core is responsible for reasoning, not execution.</p>
<p>A helpful mental model is to think of the agent as a planner, not a doer. Its role is to interpret the user’s goal, consider available context and memory, and produce a structured plan that can later be executed in a controlled way.</p>
<p>To make this concrete, the following example shows how an agent can translate user input and memory into an explicit plan. In practice, ADK orchestrates this using a large language model, but the important idea is that the output is structured intent, not side effects.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example for illustration.</span>

<span class="hljs-keyword">from</span> dataclasses <span class="hljs-keyword">import</span> dataclass
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> List, Dict, Any

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Step</span>:</span>
    tool: str
    args: Dict[str, Any]

<span class="hljs-meta">@dataclass</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Plan</span>:</span>
    goal: str
    steps: List[Step]

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">build_plan</span>(<span class="hljs-params">user_text: str, memory: Dict[str, Any]</span>) -&gt; Plan:</span>
    <span class="hljs-comment"># In practice, the LLM produces this structure via ADK orchestration.</span>
    goal = <span class="hljs-string">f"Help user: <span class="hljs-subst">{user_text}</span>"</span>
    steps = []
    <span class="hljs-keyword">if</span> memory.get(<span class="hljs-string">"prefers_short_answers"</span>):
        steps.append(Step(tool=<span class="hljs-string">"set_style"</span>, args={<span class="hljs-string">"verbosity"</span>: <span class="hljs-string">"low"</span>}))
    steps.append(Step(tool=<span class="hljs-string">"search_docs"</span>, args={<span class="hljs-string">"query"</span>: user_text}))
    steps.append(Step(tool=<span class="hljs-string">"summarize"</span>, args={<span class="hljs-string">"max_bullets"</span>: <span class="hljs-number">5</span>}))
    <span class="hljs-keyword">return</span> Plan(goal=goal, steps=steps)
</code></pre>
<p>This example illustrates an important constraint: the agent produces a plan, but it doesn’t execute anything directly.</p>
<p>The agent decides <em>what</em> should happen and <em>in what order</em>, while ADK controls <em>when</em> and <em>how</em> each step runs. This separation lets you inspect, test, and reason about decisions before they result in real-world actions.</p>
<p>When personalization is involved, this distinction becomes critical. Preferences may influence planning, but execution should remain tightly controlled by the runtime.</p>
<p>Again, we can consider the agent to be a planner, not a doer.</p>
<p>It should not:</p>
<ul>
<li><p>Perform side effects directly</p>
</li>
<li><p>Write to databases</p>
</li>
<li><p>Call external APIs without supervision</p>
</li>
</ul>
<p>In ADK, this separation is natural. The agent produces intents and tool calls, while the runtime controls how and when those calls are executed.</p>
<p>This design has two major benefits:</p>
<ol>
<li><p><strong>Safety</strong> – you can restrict which tools the agent can access</p>
</li>
<li><p><strong>Debuggability</strong> – you can inspect decisions before execution</p>
</li>
</ol>
<p>When personalization is involved, this becomes even more important. Preferences influence reasoning, but execution should remain tightly controlled.</p>
<h2 id="heading-how-to-connect-tools-safely-with-mcp"><strong>How to Connect Tools Safely with MCP</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578793149/2e3f8282-341a-4f03-9313-df3f8c9c5174.png" alt="Tool call routed through a control layer with request, validation, execution, and response steps." class="image--center mx-auto" width="1362" height="870" loading="lazy"></p>
<p><em>Figure 5 — Tool calls with guardrails: request → validate → execute → respond</em></p>
<p>Tools are how agents interact with the real world. They fetch data, generate artifacts, and sometimes perform actions with side effects.</p>
<p>Without clear boundaries, tool usage quickly becomes a source of fragility. Hardcoded API calls leak into prompts, tools evolve independently, and agents gain more authority than intended.</p>
<p>To avoid these problems, tools should be explicitly registered and invoked through a narrow interface. The following example shows a simple tool registry pattern that mirrors how MCP exposes tools to an agent without tightly coupling it to implementations.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Callable, Dict, Any

ToolFn = Callable[[Dict[str, Any]], Dict[str, Any]]

TOOLS: Dict[str, ToolFn] = {}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">register_tool</span>(<span class="hljs-params">name: str</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">decorator</span>(<span class="hljs-params">fn: ToolFn</span>):</span>
        TOOLS[name] = fn
        <span class="hljs-keyword">return</span> fn
    <span class="hljs-keyword">return</span> decorator

<span class="hljs-meta">@register_tool("search_docs")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">search_docs</span>(<span class="hljs-params">args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    query = args[<span class="hljs-string">"query"</span>]
    <span class="hljs-comment"># Replace with your MCP client call (or local tool implementation).</span>
    <span class="hljs-keyword">return</span> {<span class="hljs-string">"results"</span>: [<span class="hljs-string">f"doc://example?q=<span class="hljs-subst">{query}</span>"</span>]}

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any]</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> TOOLS:
        <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Tool not allowed: <span class="hljs-subst">{name}</span>"</span>)
    <span class="hljs-keyword">return</span> TOOLS[name](args)
</code></pre>
<p>The Model Context Protocol (MCP) provides a clean way to formalize this pattern. You can think of MCP the same way operating systems treat system calls.</p>
<p>An application does not directly manipulate hardware. Instead, it requests operations through well-defined system calls. The kernel decides whether the operation is allowed and how it executes.</p>
<p>In the same way, the agent knows <em>what</em> capabilities exist, MCP defines <em>how</em> those capabilities are invoked, and the runtime controls <em>when</em> and <em>whether</em> they execute.</p>
<p>This separation prevents several common problems, including hardcoded API details in prompts, unexpected breakage when tools change, and agents performing unrestricted side effects.</p>
<p>When designing tools, it helps to classify them by risk: read tools for safe queries, generate tools for planning or synthesis, and commit tools for irreversible actions. In a personalized agent, commit tools should be rare and tightly guarded.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770580271505/d5d34514-3b98-4997-85ed-dee55e65d711.png" alt="Observability around tool calls using logs, traces, and timing across decision points" class="image--center mx-auto" width="996" height="606" loading="lazy"></p>
<p><em>Figure 6 — Observability around tool calls: logs, traces, timing, decision points</em></p>
<h2 id="heading-how-to-add-long-term-memory-without-polluting-context"><strong>How to Add Long-Term Memory Without Polluting Context</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770577944241/b2a3de65-c5e2-456e-8a33-e9fd4d2695f0.png" alt="Memory candidates extracted from user input, filtered and validated, then stored asynchronously" class="image--center mx-auto" width="1118" height="478" loading="lazy"></p>
<p><em>Figure 7 — Memory admission pipeline: extract → filter/validate → persist asynchronously</em></p>
<p>Memory is where personalization either succeeds or fails.</p>
<p>You can start by storing everything the user says and feed it back into the prompt. This works briefly, then collapses under its own weight as context grows, costs rise, and behavior becomes unpredictable.</p>
<p>A better approach is to treat memory as structured, curated data so you can control what the agent remembers and why with clear admission rules. Before persisting anything, the system should explicitly decide whether the information is worth remembering. The following function demonstrates a simple memory admission policy.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Simplified Reference Only</span>
<span class="hljs-keyword">from</span> typing <span class="hljs-keyword">import</span> Optional, Dict, Any

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">memory_candidate</span>(<span class="hljs-params">user_text: str</span>) -&gt; Optional[Dict[str, Any]]:</span>
    text = user_text.lower()

    <span class="hljs-comment"># Durable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"for this session"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ignore after"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-comment"># Reusable</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"my preferred language is"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"type"</span>: <span class="hljs-string">"preference"</span>, <span class="hljs-string">"key"</span>: <span class="hljs-string">"language"</span>, <span class="hljs-string">"value"</span>: user_text.split()[<span class="hljs-number">-1</span>]}

    <span class="hljs-comment"># Safe (basic example; add PII checks for your use case)</span>
    <span class="hljs-keyword">if</span> <span class="hljs-string">"password"</span> <span class="hljs-keyword">in</span> text <span class="hljs-keyword">or</span> <span class="hljs-string">"ssn"</span> <span class="hljs-keyword">in</span> text:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>

    <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># default: don’t store</span>
</code></pre>
<p>This policy encodes three questions every memory candidate must answer:</p>
<ul>
<li><p>Is it durable? Will it still matter in the future?</p>
</li>
<li><p>Is it reusable? Will it influence future decisions meaningfully?</p>
</li>
<li><p>Is it safe to persist? Does it avoid sensitive or session-specific data?</p>
</li>
</ul>
<p>Only information that passes all three checks should become long-term memory. In practice, this usually includes stable preferences and long-lived constraints, not temporary instructions or intermediate reasoning.</p>
<h3 id="heading-privacy-consent-and-lifecycle-controls-production-checklist"><strong>Privacy, Consent, and Lifecycle Controls (Production Checklist)</strong></h3>
<p>Even if your admission rules are solid, long-term memory introduces governance requirements:</p>
<ul>
<li><p><strong>User control:</strong> allow users to view, export, and delete stored preferences at any time.</p>
</li>
<li><p><strong>Sensitive data handling:</strong> never store secrets/PII. Run PII detection on every memory candidate (and consider redaction).</p>
</li>
<li><p><strong>Retention + consent:</strong> use explicit consent for persistent memory and apply retention windows (TTL) so memory expires unless it’s still useful.</p>
</li>
<li><p><strong>Security + auditability:</strong> encrypt at rest, restrict access by service identity, and keep an audit log of memory writes/updates.</p>
</li>
</ul>
<p>Memory writes should also be asynchronous. The agent should never block while persisting memory, which keeps interactions responsive and avoids coupling reasoning to storage latency.</p>
<h2 id="heading-how-the-end-to-end-agent-flow-works"><strong>How the End-to-End Agent Flow Works</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770578847727/f3cbc4b9-5bc9-4026-ae69-6fd7bc1625fc.png" alt="End-to-end flow showing user input, agent reasoning, tool invocation, and memory updates with feedback loops" class="image--center mx-auto" width="1134" height="308" loading="lazy"></p>
<p><em>Figure 8 — End-to-end request lifecycle: user input → plan → tools → memory updates</em></p>
<p>At this point, you can trace exactly how memory and tools interact during a single request. With the individual components in place, it’s helpful to see how they work together during a single request. The following example walks through the full lifecycle of a personalized interaction, from user input to response.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">handle_request</span>(<span class="hljs-params">user_id: str, user_text: str</span>) -&gt; str:</span>
    memory = memory_store.get(user_id)  <span class="hljs-comment"># e.g., {"prefers_short_answers": True}</span>
    plan = build_plan(user_text, memory)

    tool_outputs = []
    <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> plan.steps:
        out = invoke_tool(step.tool, step.args)
        tool_outputs.append({step.tool: out})

    response = render_response(goal=plan.goal, tool_outputs=tool_outputs, memory=memory)

    cand = memory_candidate(user_text)
    <span class="hljs-keyword">if</span> cand:
        <span class="hljs-comment"># Never block the user on storage.</span>
        memory_store.write_async(user_id, cand)
    <span class="hljs-keyword">return</span> response
</code></pre>
<p>At a high level, the flow looks like this:</p>
<ol>
<li><p>The user sends a message.</p>
</li>
<li><p>Relevant long-term memory is retrieved.</p>
</li>
<li><p>The agent reasons about the request and produces a plan.</p>
</li>
<li><p>ADK invokes tools through MCP as needed.</p>
</li>
<li><p>Results flow back to the agent.</p>
</li>
<li><p>The agent decides whether new information should be persisted.</p>
</li>
<li><p>Memory is written asynchronously.</p>
</li>
<li><p>The final response is returned to the user.</p>
</li>
</ol>
<p>Notice what does <strong>not</strong> happen: the model does not directly write memory, tools do not execute without coordination, and context does not grow without bounds. This structure keeps personalization controlled and predictable.</p>
<h2 id="heading-common-pitfalls-youll-hit-and-how-to-avoid-them"><strong>Common Pitfalls You’ll Hit (and How to Avoid Them)</strong></h2>
<p>Even with a solid architecture, there are a few failure modes that show up repeatedly in real systems. Many of them stem from allowing agents to perform irreversible actions without explicit checks.</p>
<p>The following example shows a simple guardrail for commit-style tools that require approval before execution.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Reference example (pseudocode for illustration)</span>

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">invoke_commit_tool</span>(<span class="hljs-params">name: str, args: Dict[str, Any], approved: bool</span>) -&gt; Dict[str, Any]:</span>
    <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> approved:
        <span class="hljs-comment"># Require explicit confirmation or policy approval before side effects.</span>
        <span class="hljs-keyword">return</span> {<span class="hljs-string">"status"</span>: <span class="hljs-string">"blocked"</span>, <span class="hljs-string">"reason"</span>: <span class="hljs-string">"commit tools require approval"</span>}

    <span class="hljs-comment"># For example: create_ticket, send_email, submit_order, update_record</span>
    <span class="hljs-keyword">return</span> invoke_tool(name, args)
</code></pre>
<p>This pattern forces a clear decision point before side effects occur. It also creates an audit trail that explains <em>why</em> an action was allowed or blocked.</p>
<p>Other common pitfalls include over-personalization, leaky memory that persists session-specific data, uncontrolled tool growth, and debugging blind spots caused by unclear boundaries. If you see these symptoms, it usually means responsibilities are not clearly separated.</p>
<h2 id="heading-what-you-learned-and-where-to-go-next"><strong>What You Learned and Where to Go Next</strong></h2>
<p>Personalized AI agents are powerful, but they require discipline. The key insight is that personalization is a <strong>systems problem</strong>, not a prompt problem.</p>
<p>By separating reasoning from execution, structuring memory carefully, and using protocols like MCP to enforce boundaries, you can build agents that scale beyond demos and remain maintainable in production.</p>
<p>As you extend this system, resist the urge to add “just one more prompt tweak.” Instead, ask whether the change belongs in memory, tools, or orchestration.  </p>
<p>That mindset will save you time as your agent grows in complexity.  </p>
<p>If you’d like to continue the conversation, you can find me on <a target="_blank" href="https://www.linkedin.com/in/natarajsundar/">LinkedIn</a>.</p>
<p>*All diagrams in this article were created by the author for educational purposes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ System Design Patterns in Android Bluetooth [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ If you’ve ever opened the Android Bluetooth source code, you might know this feeling. You go in with the calm confidence of a developer who just wants to understand how things work. You open BluetoothAdapter.java and think, “Ah, this looks clean.” Th... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/system-design-patterns-in-android-bluetooth-full-handbook/</link>
                <guid isPermaLink="false">6915f7d8453f11c904fade0c</guid>
                
                    <category>
                        <![CDATA[ aosp ]]>
                    </category>
                
                    <category>
                        <![CDATA[ bluetooth ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ design patterns ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Nikheel Vishwas Savant ]]>
                </dc:creator>
                <pubDate>Thu, 13 Nov 2025 15:23:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1763047349934/78e1861c-62d3-44c8-adc3-971d6b63a7cc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>If you’ve ever opened the Android Bluetooth source code, you might know this feeling.</p>
<p>You go in with the calm confidence of a developer who just wants to understand how things work. You open <code>BluetoothAdapter.java</code> and think, “Ah, this looks clean.” Then you click through a few methods. Suddenly, you’re in <code>AdapterService.java</code>, then <code>StateMachine.java</code>, and before you realize it, you’re staring at a JNI bridge leading straight into native C++ code that talks to daemons with names like <code>bluetoothd</code>.</p>
<p>Somewhere between the Binder calls, message queues, and “Unexpected state” logs, your curiosity quietly turns into existential dread.</p>
<p>That, my friend, is the Android Bluetooth experience.</p>
<p>But here’s the twist: it’s not chaos. It’s choreography. Every message, callback, and native call exists for a reason. Android Bluetooth has been built, rebuilt, and evolved over more than a decade to support everything from old-school car kits to cutting-edge LE Audio.</p>
<p>Underneath that ever-expanding complexity lies a remarkably disciplined foundation built on <strong>system design patterns</strong>. These patterns are the reason Bluetooth can still work across thousands of devices, dozens of chip vendors, and millions of random user interactions that happen every second.</p>
<p>What’s fascinating is how the Bluetooth stack mirrors Android’s entire design philosophy: isolate complexity, define clear roles, and let components communicate through predictable contracts.</p>
<p>The app layer talks to managers. The managers talk to services. The services talk to native daemons. And the daemons finally talk to the hardware. Each layer speaks its own language but follows a shared rhythm –like musicians who have never met but somehow stay in tune.</p>
<p><img src="https://www.androidauthority.com/wp-content/uploads/2018/03/Bluetooth-Icon-Settings-Menu.jpg" alt="What is Bluetooth and how does it work? - Android Authority" width="1920" height="1080" loading="lazy"></p>
<p>Without these patterns, the system would collapse under its own ambition. Imagine writing logic for pairing, bonding, discovery, connection, streaming, and low-energy data transfer without structure. Every change would be a minefield.</p>
<p>Design patterns bring sanity to this chaos.</p>
<ul>
<li><p>The <strong>Manager-Service split</strong> ensures clear boundaries.</p>
</li>
<li><p>The <strong>State Machine</strong> keeps connection lifecycles predictable.</p>
</li>
<li><p>The <strong>Handler-Looper mechanism</strong> turns concurrency into an orderly queue.</p>
</li>
<li><p>The <strong>Facade</strong> hides native messiness behind friendly APIs.</p>
</li>
<li><p>And the <strong>Observer</strong> pattern lets everyone stay updated without tripping over each other.</p>
</li>
</ul>
<p>This article is about peeling back those layers and seeing the design ideas that quietly keep Android Bluetooth alive. We won’t just list patterns like a textbook. Instead, we’ll explore how each one appears in real AOSP code, why it exists, and how you can apply the same ideas to your own projects.</p>
<p>If you’ve ever wondered how something as temperamental as Bluetooth manages to stay mostly reliable, this is your backstage pass.</p>
<p>So grab your debugger, open a terminal window, and get ready to look at Bluetooth not as a mysterious black box, but as one of Android’s most elegant examples of long-term system design done right.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-the-manager-service-pattern-divide-and-delegate">The Manager–Service Pattern: Divide and Delegate</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-facade-pattern-making-complexity-look-simple">The Facade Pattern: Making Complexity Look Simple</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-state-machine-pattern-keeping-bluetooth-sane">The State Machine Pattern: Keeping Bluetooth Sane</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-handler-looper-pattern-message-driven-concurrency">The Handler–Looper Pattern: Message-Driven Concurrency</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-observer-pattern-when-bluetooth-talks-back">The Observer Pattern: When Bluetooth Talks Back</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-builder-pattern-making-gatt-bearable">The Builder Pattern: Making GATT Bearable</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-strategy-pattern-adapting-to-different-devices">The Strategy Pattern: Adapting to Different Devices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-template-method-pattern-common-flows-custom-details">The Template Method Pattern: Common Flows, Custom Details</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-service-locator-pattern-finding-the-right-profile-at-runtime">The Service Locator Pattern: Finding the Right Profile at Runtime</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-layered-architecture-pattern-from-app-to-radio-without-losing-the-plot">The Layered Architecture Pattern: From App to Radio Without Losing the Plot</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-putting-it-all-together-designing-bluetooth-style-systems">Putting It All Together: Designing Bluetooth-Style Systems</a></p>
</li>
</ol>
<h2 id="heading-the-managerservice-pattern-divide-and-delegate">The Manager–Service Pattern: Divide and Delegate</h2>
<p>When you start exploring Android’s Bluetooth codebase, one of the first things you’ll notice is how often you come across the words “Manager” and “Service.” There is <code>BluetoothManagerService</code>, <code>AdapterService</code>, <code>GattService</code>, <code>A2dpService</code>, and many more.</p>
<p>At first, it seems repetitive and unnecessarily complicated. Why do we need so many layers just to connect to a pair of earbuds? Wouldn’t one class that says “connect” be enough? The short answer is no. The longer answer involves one of Android’s most reliable architectural habits: the separation of responsibility.</p>
<p>Think of a restaurant. The customers talk to the waiter. The waiter talks to the kitchen. The kitchen talks to suppliers. Everyone has a job. The waiter doesn’t need to know how to cook, and the chef doesn’t need to explain menu prices to customers. That separation is what keeps the whole operation smooth and manageable.</p>
<p>Android’s Bluetooth system works in exactly the same way. The <strong>Manager</strong> is like the waiter, the public face that interacts with apps, while the <strong>Service</strong> is like the kitchen, where the actual work happens out of sight.</p>
<p>When you write an app that uses Bluetooth, you might call something like <code>BluetoothAdapter.enable()</code> or <code>BluetoothDevice.connectGatt()</code>. These methods live inside Manager classes in the Android framework. They are deliberately simple, because their only job is to talk to the Bluetooth Service behind the scenes. That Service runs in another process entirely, one that has the necessary system permissions and the ability to interact with the native Bluetooth stack and hardware.</p>
<p>A small example from the Android source code shows this relationship very clearly:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BluetoothManagerService</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">IBluetoothManager</span>.<span class="hljs-title">Stub</span> </span>{
    <span class="hljs-keyword">private</span> AdapterService mAdapterService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">enable</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">if</span> (mAdapterService != <span class="hljs-keyword">null</span>) {
            <span class="hljs-keyword">return</span> mAdapterService.enable();
        }
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
    }
}
</code></pre>
<p>At first glance, this looks trivial, but it demonstrates one of the most important ideas in the system. The <code>BluetoothManagerService</code> does not handle radio operations itself. Instead, it delegates to another internal class called <code>AdapterService</code>, which communicates with lower layers. That service will eventually pass instructions down to native C++ code, which then communicates with the Bluetooth controller chip through the Host Controller Interface.</p>
<p>This relay-style design has several advantages. The first is reliability. If the lower-level service crashes, the Manager layer can detect it and restart it, keeping the system stable. Because the Manager and the Service live in separate processes, your app will not crash when the service does. You might see Bluetooth temporarily toggle off and on again, but that recovery is intentional and automatic.</p>
<p>The second advantage is security. Every Bluetooth action goes through permission checks in the Manager layer before it reaches the Service. If an app without proper privileges tries to perform a restricted operation, the Manager stops it immediately. This prevents unsafe or malicious behavior and ensures that only trusted system components can access the hardware.</p>
<p>The third is flexibility. The Service layer can evolve without affecting the public API. That means Google and device manufacturers can modify or replace internal Bluetooth logic say, to support a new chipset or feature, without breaking existing apps. The Manager acts as a contract that remains stable even if the internal wiring changes.</p>
<p>If you trace what happens when you tap the Bluetooth toggle on your phone, you can see this pattern in action. Your tap calls <code>BluetoothAdapter.enable()</code> in the app layer. That call travels to <code>BluetoothManagerService</code> in the system server process. The manager checks permissions, then calls <code>AdapterService.enable()</code>. Inside the service, a JNI bridge triggers a native C++ function called <code>enableNative()</code>, which finally sends a command to the hardware abstraction layer. From there, it reaches the Bluetooth chip itself. Each layer knows its exact role.</p>
<p>This organization also makes debugging easier. If something goes wrong, you can tell whether it’s the Manager that didn’t send a message, the Service that failed to respond, or the native stack that stopped working. Each part logs its own activity in logcat, so you can follow the chain of events without guessing where the problem began.</p>
<p>At its core, the Manager–Service pattern is Android’s way of keeping large systems under control. It divides authority, enforces security, and lets the entire Bluetooth subsystem recover gracefully from errors. It may look complicated at first, but it is this design that makes Bluetooth remarkably resilient. Every time your phone connects to your car or your earbuds, it happens through this carefully choreographed handoff between the Manager and the Service. It’s a quiet partnership that keeps billions of connections running smoothly every single day.</p>
<h2 id="heading-the-facade-pattern-making-complexity-look-simple">The Facade Pattern: Making Complexity Look Simple</h2>
<p>If the Manager–Service pattern is about dividing responsibility, the Facade pattern is about hiding chaos behind elegance. In many ways, this is the reason most Android developers can use Bluetooth without needing to understand what happens inside the stack.</p>
<p>The Facade pattern provides a friendly public face that masks a labyrinth of underlying operations, creating an illusion of simplicity while managing a tremendous amount of behind-the-scenes work.</p>
<p>To understand this, think about the front desk of a large hotel. When you check in, you talk to one receptionist. That person gives you your key, answers questions, and takes requests. You never meet the maintenance crew fixing the air conditioning or the kitchen staff preparing food or the team handling room cleaning schedules. Yet all those systems quietly operate through that one friendly front desk.</p>
<p>That front desk is the Facade. It provides a simple interface to a complex system, ensuring guests never have to deal with the hotel’s internal machinery.</p>
<p>Android’s Bluetooth framework works in the same way. Developers interact with high-level classes such as <code>BluetoothAdapter</code>, <code>BluetoothDevice</code>, and <code>BluetoothGatt</code>. These classes are the front desks of the Bluetooth system. They provide clean, easy-to-use APIs like <code>enable()</code>, <code>getBondedDevices()</code>, and <code>connectGatt()</code>.</p>
<p>When a developer calls one of these methods, it looks straightforward. But beneath the surface, that call passes through multiple layers of services, IPC mechanisms, and native components before reaching the Bluetooth controller hardware.</p>
<p>Here is a simplified example to illustrate how this works in practice:</p>
<pre><code class="lang-java">BluetoothGatt gatt = device.connectGatt(context, <span class="hljs-keyword">false</span>, callback);
</code></pre>
<p>This single line looks simple. But in reality, it triggers an entire orchestra of operations. The call goes through the <code>BluetoothDevice</code> class, which forwards the request to <code>BluetoothGatt</code>. The <code>BluetoothGatt</code> instance then communicates with the system’s Bluetooth service through Binder IPC. That service eventually invokes native code that sets up an L2CAP channel, negotiates attributes, configures encryption, and starts the Generic Attribute Profile (GATT) procedure. None of that complexity is visible to the developer who wrote the original line.</p>
<p>This is what makes the Facade pattern so powerful. It provides abstraction without removing capability. The Android team knows that very few app developers want to worry about connection intervals, PHY configurations, or attribute protocol responses. They just want to connect to a device and get data. By exposing a Facade, Android lets developers stay productive while the internal layers handle the technical details.</p>
<p>If you look at the Android source tree, you can see this pattern clearly in how Bluetooth is organized. The classes in the <code>android.bluetooth</code> package are intentionally designed to be simple and self-contained. They never reveal how the system service works.</p>
<p>For example, <code>BluetoothAdapter</code> doesn’t know how to send HCI commands, and <code>BluetoothGatt</code> doesn’t know how to open a socket. Instead, they act as representatives, forwarding user requests to the Bluetooth Manager or the corresponding Service, which then interacts with the native stack.</p>
<p>This pattern is what makes the Bluetooth API approachable to beginners. Imagine if Android exposed every detail of the underlying protocols to developers. You would have to manually construct attribute requests, negotiate connection intervals, and handle packet fragmentation. The result would be technically accurate but completely unusable for most app developers. The Facade prevents that by serving as a translation layer between human expectations and machine complexity.</p>
<p>There is also a deeper design reason behind this approach. A Facade protects stability. Because developers only see the outermost layer, Android engineers can modify the internals without breaking existing apps. This allows the system to evolve freely, improving performance and adding new features while keeping the public API consistent.</p>
<p>The Bluetooth internals have changed countless times since the early days of Android, but <code>BluetoothAdapter.startDiscovery()</code> still works the same way it did a decade ago. That consistency is a direct benefit of the Facade pattern.</p>
<p>In a sense, the Facade pattern is about empathy. It respects the developer’s time by not forcing them to learn every Bluetooth nuance. It makes working with a complicated protocol feel human. Whether you are scanning for nearby devices, connecting to a smartwatch, or transferring data, you only need to call a few readable methods and handle a handful of callbacks. Behind those calls, a world of threads, sockets, and packet exchanges whirs silently to life, all hidden behind a calm, minimal interface.</p>
<p>So the next time you call <code>BluetoothAdapter.enable()</code> and your phone’s Bluetooth magically comes to life, remember that you are not flipping a simple switch. You are sending a message through a carefully designed Facade that talks to multiple services, native layers, and hardware interfaces. It is like pressing a single button on a spaceship console while a thousand mechanical parts start moving in perfect synchronization. You don’t see the complexity, and that is precisely the point.</p>
<h2 id="heading-the-state-machine-pattern-keeping-bluetooth-sane">The State Machine Pattern: Keeping Bluetooth Sane</h2>
<p>If you have ever debugged Bluetooth connections, you have probably experienced moments of pure confusion. One minute the device says “Connecting,” then suddenly it jumps to “Connected,” then “Disconnected,” then “Connecting” again, and before you know it, you have no idea what the current state actually is.</p>
<p>Bluetooth is, by nature, an unpredictable environment. Devices move in and out of range, radio interference causes delays, and remote devices can behave differently depending on their chipsets. To make sense of all this unpredictability, Android relies on one of the most battle-tested concepts in computer science: the <strong>State Machine</strong> pattern.</p>
<p>A state machine is like a rulebook that defines how a system behaves depending on its current situation. Instead of reacting randomly to every event, the system maintains a clear notion of “state.”</p>
<p>For Bluetooth, these states might include <em>Disconnected</em>, <em>Connecting</em>, <em>Connected</em>, or <em>Disconnecting</em>. Each state knows exactly what actions are allowed and what transitions are possible.</p>
<p>For example, you can only go from <em>Disconnected</em> to <em>Connecting</em> when a connection attempt starts, and you can only go from <em>Connecting</em> to <em>Connected</em> if the handshake succeeds. If something happens that does not make sense for the current state, the system simply ignores it. This structure prevents chaos.</p>
<p>In Android’s Bluetooth implementation, almost every major profile uses a state machine. You can find them in classes like <code>A2dpStateMachine.java</code> and <code>HeadsetStateMachine.java</code>. Each one extends a generic <code>StateMachine</code> framework that Android provides. The structure is surprisingly elegant. You define individual classes for each state, implement their behaviors, and let the system handle the transitions. Conceptually, it looks like this:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">A2dpStateMachine</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StateMachine</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> State mDisconnected = <span class="hljs-keyword">new</span> Disconnected();
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> State mConnecting = <span class="hljs-keyword">new</span> Connecting();
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> State mConnected = <span class="hljs-keyword">new</span> Connected();

    A2dpStateMachine() {
        addState(mDisconnected);
        addState(mConnecting);
        addState(mConnected);
        setInitialState(mDisconnected);
    }
}
</code></pre>
<p>Although the code may look technical, the idea is simple. Each “State” represents a specific mode of operation, and each one defines how to react to incoming events.</p>
<p>The system starts in <em>Disconnected</em>. When a “connect” command arrives, it moves to <em>Connecting</em>. When the connection completes, it moves to <em>Connected</em>. If the user turns off Bluetooth or the remote device disappears, it transitions back to <em>Disconnected</em>. Every action follows a logical, well-defined path.</p>
<p>This pattern is what keeps Bluetooth stable despite the messy nature of wireless communication. Without it, you would constantly end up with half-open connections, dangling callbacks, and undefined behaviors. Imagine a phone that still thinks it’s connected to your headphones long after you have turned them off. The state machine eliminates that by keeping a single source of truth for connection status.</p>
<p>Beyond correctness, the state machine pattern also improves readability and maintenance. Each state is self-contained, so developers can easily locate the logic that handles a particular situation. If you need to change how Bluetooth behaves when connecting, you only modify the <em>Connecting</em> class, not the entire codebase. This modularity makes the Bluetooth stack easier to evolve as new profiles and features appear.</p>
<p>There is also a subtle psychological benefit to using state machines. When debugging, engineers can trace log messages that indicate transitions, such as “A2dpStateMachine: Transitioning from CONNECTING to CONNECTED.” These logs act like a map of the system’s thought process. Instead of guessing what happened, you can follow a clear narrative of cause and effect. That is invaluable in a system as complex as Bluetooth, where timing issues can hide bugs that are otherwise impossible to reproduce.</p>
<p>State machines also ensure graceful recovery. Suppose a connection fails halfway through. Without structured states, the system might leave resources allocated or callbacks registered. But with a state machine, the <em>Connecting</em> state knows how to clean up before returning to <em>Disconnected</em>. This reduces leaks, power drain, and inconsistent user experiences.</p>
<p>Even at higher levels of Android, you can see the influence of this pattern. For example, when you toggle Bluetooth on or off, the adapter itself transitions through a sequence of states internally: <em>Turning On</em>, <em>On</em>, <em>Turning Off</em>, <em>Off</em>. This ensures that all dependent services, such as GATT and A2DP, are brought up or down in the right order. The pattern guarantees that nothing jumps ahead or lags behind during these transitions.</p>
<p>In everyday terms, the state machine pattern is like traffic lights for Bluetooth. It prevents every component from driving through the intersection at the same time. Each action has a green, yellow, or red light depending on the current situation. This orderliness is what keeps Bluetooth from descending into radio chaos every time multiple devices try to connect or disconnect at once.</p>
<p>So, the next time your phone automatically reconnects to your headphones after a short disconnection, remember that it is not luck. It is a carefully choreographed set of state transitions keeping track of where everything stands. Behind every smooth Bluetooth experience lies a quiet but dependable state machine making sure each event happens exactly when it should and never when it shouldn’t.</p>
<h2 id="heading-the-handlerlooper-pattern-message-driven-concurrency">The Handler–Looper Pattern: Message-Driven Concurrency</h2>
<p>If Bluetooth had a personality, it would be that friend who cannot sit still. It’s constantly juggling tasks: scanning for devices, maintaining connections, handling GATT operations, streaming audio, and sending data to the controller, all at once. Underneath that hustle is one of Android’s most reliable design foundations: the <strong>Handler–Looper</strong> pattern. This pattern is what keeps Bluetooth responsive, synchronized, and stable even when a dozen things happen at the same time.</p>
<p>To understand why it exists, imagine running a busy coffee shop with only one employee who tries to handle every customer request immediately. One person takes an order, makes the drink, cleans the counter, and washes the cups all in real time. Within minutes, chaos erupts. Customers start yelling, the counter gets sticky, and no one knows who’s being served.</p>
<p>Now, imagine a more organized system: every order goes into a queue, and the barista processes them one by one. That’s essentially how the Handler–Looper system works.</p>
<p>In Android, almost everything that involves background work happens through <strong>message queues</strong>. The <strong>Looper</strong> represents a thread that waits for messages, and the <strong>Handler</strong> is the entity that posts those messages into the queue.</p>
<p>Instead of letting different threads modify shared Bluetooth state directly, which could easily lead to race conditions, Android forces all Bluetooth operations to happen on specific threads managed by loopers. Messages arrive, get handled in order, and the system never loses track of what happened first or last.</p>
<p>Inside the Bluetooth system, this pattern appears everywhere. Each service, such as <code>AdapterService</code>, <code>GattService</code>, or <code>A2dpService</code>, has its own Handler running on a dedicated thread. When a Bluetooth event occurs, like “Device Connected” or “Start Discovery,” the event is wrapped in a <code>Message</code> object and sent to the appropriate Handler. That Handler then decides what to do next. The pattern turns what could have been a tangle of multithreaded chaos into a clear, sequential pipeline.</p>
<p>Here’s a simplified example inspired by Android’s real Bluetooth code:</p>
<pre><code class="lang-java"><span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AdapterServiceHandler</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Handler</span> </span>{
    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">handleMessage</span><span class="hljs-params">(Message msg)</span> </span>{
        <span class="hljs-keyword">switch</span> (msg.what) {
            <span class="hljs-keyword">case</span> MSG_START_DISCOVERY:
                startDiscoveryNative();
                <span class="hljs-keyword">break</span>;
            <span class="hljs-keyword">case</span> MSG_STOP_DISCOVERY:
                stopDiscoveryNative();
                <span class="hljs-keyword">break</span>;
        }
    }
}
</code></pre>
<p>This code might look plain, but it’s quietly doing something brilliant. Instead of running <code>startDiscoveryNative()</code> directly, the system posts a message saying, “Hey, when you get a chance, start discovery.” The Looper thread eventually picks up that message and executes it in the correct order. No two threads ever collide, and the main thread stays free to handle user interactions.</p>
<p>The beauty of this approach lies in its predictability. Bluetooth events often happen in unpredictable sequences: a connection attempt might fail while a scan is still in progress, or a new device might appear while another is being paired. Without strict message ordering, these overlaps could lead to deadlocks or inconsistent states. By channeling every operation through a single message queue, Android ensures that Bluetooth behaves deterministically, no matter how chaotic the radio environment becomes.</p>
<p>It also helps with <strong>thread safety</strong>. Instead of sprinkling locks everywhere in the code, Android simply guarantees that all critical Bluetooth work happens on the same thread. This means developers can focus on logic instead of worrying about synchronization bugs. It’s one of those design choices that looks simple but saves thousands of hours of debugging across devices and vendors.</p>
<p>There’s another hidden benefit too: <strong>graceful recovery</strong>. If something goes wrong inside a message handler, say a native call fails or a timeout occurs, the system can isolate that failure to a single message. The rest of the queue continues processing normally. This containment prevents one bad operation from crashing the entire Bluetooth stack.</p>
<p>When you watch logcat during a Bluetooth session, you can often see the Handler–Looper pattern in action. You’ll find lines like “MSG_START_DISCOVERY received” followed by “Starting discovery” and “MSG_STOP_DISCOVERY received.” Those logs are more than just printouts – they are breadcrumbs showing the system’s thought process as it moves through the queue.</p>
<p>In simpler terms, the Handler–Looper pattern is how Android Bluetooth keeps its cool. It takes a storm of asynchronous events, pairing requests, advertisements, data packets, disconnections, and lines them up in a single, calm queue. It ensures that everything happens in order, every time.</p>
<p>So, the next time your phone seamlessly switches from one Bluetooth speaker to another while still streaming music and scanning for your watch in the background, remember what’s quietly at work beneath it all. There’s a dedicated thread looping patiently, reading messages, and keeping order in a world of wireless chaos. It’s the unsung hero of concurrency, one message at a time.</p>
<h2 id="heading-the-observer-pattern-when-bluetooth-talks-back">The Observer Pattern: When Bluetooth Talks Back</h2>
<p>Bluetooth is a chatterbox. It never works alone, and is always reacting to something. A device connects, another disconnects, a new advertisement appears, a bond is created, or a characteristic changes its value. The system needs to keep dozens of components informed about these changes in real time.</p>
<p>This is where the <strong>Observer pattern</strong> comes in. This pattern is all about communication, letting different parts of the system stay updated without constantly asking what’s going on.</p>
<p>The basic idea is simple. You have one source of truth that broadcasts updates, and you have multiple listeners that care about those updates. Whenever the source changes, it notifies everyone who subscribed. It’s like a news channel that sends breaking alerts to subscribers instead of waiting for each viewer to call in and ask, “Anything new today?”</p>
<p>In Android Bluetooth, this is how almost all notifications and callbacks are delivered. When your phone connects to a Bluetooth device, the Bluetooth system service sends out an event. The app doesn’t have to keep checking the connection status every second. Instead, it simply registers a listener that reacts whenever the connection state changes. That listener could be a <code>BroadcastReceiver</code> in the app or a callback interface provided by the framework.</p>
<p>For example, when a device connects, Android sends out a broadcast intent like this:</p>
<pre><code class="lang-java">sendBroadcast(<span class="hljs-keyword">new</span> Intent(BluetoothDevice.ACTION_ACL_CONNECTED));
</code></pre>
<p>Apps that have registered for this intent receive it automatically. They can then update their user interface, show a notification, or start another operation based on the new state. The same mechanism works for disconnections, bonding events, and discovery results. It’s an elegant way of keeping apps informed without them wasting energy by constantly polling the system.</p>
<p>At the GATT level, the Observer pattern takes a slightly different form. When you connect to a Bluetooth Low Energy device and subscribe to a characteristic, you provide a callback called <code>BluetoothGattCallback</code>. This callback has methods such as <code>onConnectionStateChange()</code> and <code>onCharacteristicChanged()</code>. Whenever the device sends new data, the system automatically invokes the appropriate callback on your behalf. You don’t need to ask for updates repeatedly – you simply react when they arrive.</p>
<p>The real beauty of this pattern is how decoupled it makes the system. The Bluetooth framework can notify multiple apps and services simultaneously without knowing anything about how they use the information. It just broadcasts an event and moves on. Each listener independently decides what to do with it.</p>
<p>This design is crucial for a multitasking operating system like Android, where Bluetooth events may be relevant to different components at the same time. For example, the system settings might need to update the connection icon, the media framework might need to route audio, and an app might need to sync data — all triggered by the same connection event.</p>
<p>The Observer pattern also helps with efficiency. Because updates are sent only when something changes, there is no unnecessary processing or battery drain from constant status checks. This design allows the Bluetooth stack to stay responsive while minimizing overhead, which is especially important for mobile devices that need to preserve both power and performance.</p>
<p>In practical terms, this pattern is what makes Bluetooth feel alive. When you open your Bluetooth settings and instantly see your device name appear or disappear, that’s the result of observers doing their job. They are always listening for broadcasts and updating the interface the moment something changes. Without this mechanism, your Bluetooth menu would lag or require manual refreshing just to stay current.</p>
<p>There is also a subtle reliability benefit. Observers can join or leave at any time without breaking the system. If one app crashes or unregisters its listener, others still receive updates normally. This flexibility ensures that the Bluetooth service remains stable even if individual apps behave unpredictably.</p>
<p>So, the next time your phone pops up a notification that your earbuds have connected or your smartwatch silently syncs in the background, remember that it is not magic. It’s the Observer pattern at work: a polite messaging system that lets Bluetooth quietly talk to everyone who is listening, all without raising its voice.</p>
<h2 id="heading-the-builder-pattern-making-gatt-bearable">The Builder Pattern: Making GATT Bearable</h2>
<p>If you have ever worked with Bluetooth Low Energy, you already know that the GATT layer can be a maze. The Generic Attribute Profile, or GATT, is how devices expose data to one another. It defines services, characteristics, and descriptors that describe everything from a heart rate monitor’s readings to a light bulb’s brightness. On paper, it’s beautifully organized. In practice, setting it up manually can feel like assembling furniture without instructions, using only an Allen key and pure faith.</p>
<p>When Android engineers designed the Bluetooth GATT APIs, they realized that developers would need a way to build these services and characteristics without losing their minds. That is where the <strong>Builder pattern</strong> comes in. This pattern is all about constructing complex objects step by step, instead of trying to do everything in one chaotic go.</p>
<p>Think of it like building a sandwich. You start with a base, then add layers: bread, sauce, lettuce, tomato, cheese, and so on. You can add or skip ingredients as needed, and by the end, you have a complete meal that makes sense.</p>
<p>The Builder pattern works the same way. It lets you create a GATT service one piece at a time, adding characteristics and descriptors in a readable, modular fashion.</p>
<p>In Android, a GATT service is represented by the <code>BluetoothGattService</code> class, and each piece of data it exposes is represented by a <code>BluetoothGattCharacteristic</code>. Instead of requiring you to manually wire all of these together in one long, confusing block, Android allows you to build them step by step, like this:</p>
<pre><code class="lang-java">BluetoothGattService service = <span class="hljs-keyword">new</span> BluetoothGattService(SERVICE_UUID,
        BluetoothGattService.SERVICE_TYPE_PRIMARY);

BluetoothGattCharacteristic characteristic =
        <span class="hljs-keyword">new</span> BluetoothGattCharacteristic(CHAR_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_WRITE,
                BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);

service.addCharacteristic(characteristic);
</code></pre>
<p>Even though this looks simple, it reflects a powerful design philosophy. Each method call adds a new layer of configuration without breaking readability. You can look at the code and instantly understand what kind of service you’re creating, what characteristics it contains, and what permissions each one has. There are no massive constructors, no messy parameter lists, and no confusion about what goes where.</p>
<p>This pattern does more than make code pretty. It also prevents errors. GATT structures are very sensitive to incorrect configurations, for example if a characteristic lacks the right permission or if a descriptor is missing. By breaking the setup into small, incremental steps, the Builder pattern helps developers validate each part as they go. It’s much easier to debug a missing characteristic when each one is clearly defined, rather than buried inside a giant, monolithic block of code.</p>
<p>The same idea applies internally within the Android Bluetooth stack. When the system builds its own GATT tables or processes client requests, it follows the same step-by-step assembly model. Each stage of the process adds more detail to the overall structure. The result is not only easier to read but also more robust in handling changes.</p>
<p>There is also a psychological benefit to this approach. Developers can focus on one small piece at a time instead of feeling overwhelmed by the entire setup. It feels like progress, and it reduces the cognitive load that often comes with working on protocols like GATT, where small mistakes can cause big headaches.</p>
<p>In a broader sense, the Builder pattern in Android Bluetooth is a lesson in humility. It acknowledges that complex systems are built incrementally, not in one heroic line of code. It invites you to slow down, define what you need clearly, and construct it carefully. Whether you are setting up a health monitor or designing a custom BLE sensor, the Builder pattern ensures that your code remains clear and maintainable as your project grows.</p>
<p>So the next time you define a Bluetooth service in your app and everything just works, take a moment to appreciate the quiet genius of the Builder pattern. It’s the reason you can build an entire wireless data model with a few readable lines instead of a spaghetti of function calls. It turns the intimidating world of GATT into something almost enjoyable, a reminder that even in low-level systems programming, design elegance still matters.</p>
<h2 id="heading-the-strategy-pattern-adapting-to-different-devices">The Strategy Pattern: Adapting to Different Devices</h2>
<p>Bluetooth, as anyone who has worked with it knows, is not one single, predictable standard in practice. It’s more like a family reunion where every cousin claims to follow the same rules but each one interprets them differently. One device might handle extended advertising perfectly, another insists on using legacy commands, and yet another behaves strangely when it comes to pairing.</p>
<p>In this unpredictable world, Android cannot rely on one fixed set of behaviors. It needs a system that can adapt depending on what kind of device or chipset it is dealing with. This is where the <strong>Strategy pattern</strong> quietly saves the day.</p>
<p>The Strategy pattern is all about flexibility. It allows a system to choose between multiple approaches at runtime depending on the situation. Instead of writing huge <code>if-else</code> blocks to handle every possible scenario, developers define a common interface that represents a behavior, and then create different implementations of that behavior. The system can then pick the right strategy dynamically.</p>
<p>Imagine you are a chef who must cook for guests with different dietary preferences. You don’t rewrite the entire recipe each time someone says they are vegan or gluten-free. Instead, you have multiple cooking strategies, one for each diet, and you simply pick the right one when the order comes in. Android does the same thing with Bluetooth.</p>
<p>Inside the Bluetooth stack, different devices and chipsets support different capabilities. Some controllers can handle multiple advertising sets, some cannot. Some prefer extended packet formats, while others only understand the older legacy commands. To manage this diversity without making the code unreadable, Android uses interchangeable strategies.</p>
<p>For example, when the system needs to start Bluetooth advertising, it doesn’t hard-code every possible hardware path. Instead, it defines an abstract interface, something like:</p>
<pre><code class="lang-java"><span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">AdvertisingStrategy</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">startAdvertising</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">stopAdvertising</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>Then it provides specific implementations for each scenario, such as a <code>LegacyAdvertisingStrategy</code> and an <code>ExtendedAdvertisingStrategy</code>. Depending on the chipset capabilities, the system decides which strategy to use at runtime:</p>
<pre><code class="lang-java">AdvertisingStrategy strategy = controller.supportsExtendedAdvertising()
        ? <span class="hljs-keyword">new</span> ExtendedAdvertisingStrategy()
        : <span class="hljs-keyword">new</span> LegacyAdvertisingStrategy();
strategy.startAdvertising();
</code></pre>
<p>This design keeps the code clean and extensible. If a new Bluetooth version introduces a new advertising method, developers can simply implement another strategy class without touching the existing ones. The same approach appears in connection handling, power management, and even encryption policies.</p>
<p>The Strategy pattern also allows for graceful fallback. Suppose a modern device supports extended advertising but something goes wrong, maybe the controller firmware has a bug. Instead of crashing, the system can quietly switch back to the legacy strategy. Users never notice the change, and Bluetooth continues working.</p>
<p>Beyond hardware adaptability, this pattern also simplifies testing. Developers can easily substitute one strategy with another in unit tests to simulate different hardware configurations. It encourages modularity, which is crucial for a system that runs across hundreds of Android devices made by dozens of manufacturers.</p>
<p>You can also see the philosophical elegance in how this pattern aligns with Bluetooth itself. The Bluetooth protocol is inherently designed for negotiation. Devices exchange capabilities, choose compatible settings, and then proceed. Android’s software architecture mirrors that philosophy at the code level. By using strategies, it lets the system negotiate internally too, not between devices, but between code paths.</p>
<p>From a practical standpoint, the Strategy pattern gives Android the superpower of evolution. As new Bluetooth versions emerge with new features like LE Audio, Isochronous Channels, or Periodic Advertising, Android can keep up simply by introducing new strategy classes. There is no need to overhaul the entire system or rewrite large chunks of legacy logic.</p>
<p>So when your phone seamlessly connects to both a five-year-old Bluetooth speaker and a brand-new pair of earbuds using LE Audio, it’s not luck. It is design. Underneath the surface, Android is quietly picking the right strategy for each device, making the whole experience look effortless. It’s one of those cases where smart architecture turns what could have been a compatibility nightmare into a smooth, invisible handshake between hardware generations.</p>
<h2 id="heading-the-template-method-pattern-common-flows-custom-details">The Template Method Pattern: Common Flows, Custom Details</h2>
<p>In large systems like Android Bluetooth, not every part of the code can be entirely unique. Some operations follow the same general flow every time, but with small variations in the details. For example, connecting to a device, discovering services, or streaming audio all share similar high-level steps.</p>
<p>The pattern that allows Android to reuse these general flows while still letting each Bluetooth profile define its own personality is the <strong>Template Method</strong> pattern.</p>
<p>The essence of this pattern is simple: define the overall process once, but let subclasses decide how specific parts should behave. It’s like giving every chef in a restaurant the same recipe outline – prepare ingredients, cook, and plate – but letting each of them choose their own spices and techniques for flavor. The structure remains constant, but the details can vary.</p>
<p>Bluetooth needs this because different profiles, such as A2DP for audio or GATT for data exchange, often perform similar actions in slightly different ways. They all start connections, maintain states, and handle disconnections, but the way they handle timing, acknowledgments, or retries can differ. The Template Method pattern keeps these flows consistent while allowing room for customization.</p>
<p>Inside Android’s Bluetooth stack, you can see this pattern in how connection management is implemented. The process of connecting to a Bluetooth device typically follows the same structure: initialize the stack, attempt a connection, verify success, and then notify other components. Each profile, however, defines its own way of handling the lower-level details.</p>
<p>In conceptual form, it looks something like this:</p>
<pre><code class="lang-java"><span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BluetoothProfileConnection</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title">connect</span><span class="hljs-params">()</span> </span>{
        prepareConnection();
        performConnection();
        finalizeConnection();
    }

    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">prepareConnection</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">performConnection</span><span class="hljs-params">()</span></span>;
    <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title">finalizeConnection</span><span class="hljs-params">()</span></span>;
}
</code></pre>
<p>A class such as <code>A2dpService</code> or <code>GattService</code> would then implement the abstract methods in its own way. One might set up audio channels, while another negotiates attribute protocols. The overall template (prepare, perform, finalize) never changes. This is what keeps the Bluetooth system organized even when dozens of profiles coexist and evolve over time.</p>
<p>This pattern is particularly useful in a codebase as large as Android’s because it enforces discipline without killing flexibility. It ensures that every Bluetooth operation follows the same skeleton, which makes debugging and extending the system far easier. When an engineer wants to add a new feature or fix a connection bug, they already know where to look and which parts are shared or unique.</p>
<p>Another advantage of the Template Method pattern is that it reduces duplication. Without it, each profile might write its own version of “connect,” “disconnect,” and “reconnect,” each slightly different but doing almost the same thing. That would make the code hard to maintain and error-prone. With a template, the core logic lives in one place, and only the necessary variations appear in subclasses.</p>
<p>There is also an important design insight here: Bluetooth, like many communication protocols, is inherently procedural. You must do things in the correct order, initialize before connecting, connect before discovering, and discover before reading data. The Template Method pattern encodes this order directly into the architecture. It prevents accidental mistakes, such as skipping a required step or performing actions out of sequence.</p>
<p>From a broader perspective, this pattern teaches an important engineering lesson about balance. Too much abstraction, and systems become rigid and bureaucratic. Too little structure, and they turn into chaos. The Template Method pattern sits comfortably in the middle. It provides consistency while still leaving space for creativity and variation.</p>
<p>So the next time your phone connects to your car, switches to the right Bluetooth profile, and starts playing music without skipping a beat, you’ll know that there is a quiet choreography happening inside. Each profile follows the same dance steps – prepare, perform, and finalize – but each does it in its own rhythm. That harmony between structure and flexibility is what makes Bluetooth both powerful and adaptable.</p>
<h2 id="heading-the-service-locator-pattern-finding-the-right-profile-at-runtime">The Service Locator Pattern: Finding the Right Profile at Runtime</h2>
<p>At this point, we have seen how Android Bluetooth manages complexity through delegation, structure, and controlled flexibility. But there is still a practical question to answer: with so many Bluetooth services and profiles running in the system (like A2DP, GATT, HFP, MAP, HID, and more), how does the framework know which one to talk to at any given moment? When you stream audio, it needs A2DP. When you sync contacts, it needs PBAP. When you connect a keyboard, it needs HID. Android’s answer to this problem is the <strong>Service Locator</strong> pattern.</p>
<p>In the simplest terms, the Service Locator is a central registry that helps different parts of a system find the service or component they need without having to know where it lives. It’s like the information desk at a large airport. You don’t need to memorize the location of every gate or airline office – you just ask the information desk, and they point you to the right place.</p>
<p>Inside the Android Bluetooth system, this pattern appears everywhere, especially within the <code>AdapterService</code> and <code>BluetoothManagerService</code> classes. These services manage a variety of Bluetooth profiles, and each profile is responsible for its own behavior. Instead of hard-coding every possible profile into every part of the stack, Android maintains a registry where each service can be looked up dynamically.</p>
<p>Here is a simplified version of what this looks like conceptually:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AdapterService</span> </span>{
    <span class="hljs-keyword">private</span> Map&lt;Integer, ProfileService&gt; mProfileServices = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">registerProfile</span><span class="hljs-params">(<span class="hljs-keyword">int</span> profileId, ProfileService service)</span> </span>{
        mProfileServices.put(profileId, service);
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> ProfileService <span class="hljs-title">getProfileService</span><span class="hljs-params">(<span class="hljs-keyword">int</span> profileId)</span> </span>{
        <span class="hljs-keyword">return</span> mProfileServices.get(profileId);
    }
}
</code></pre>
<p>When a Bluetooth operation occurs, such as starting audio streaming or initiating a data transfer, the system asks the AdapterService for the correct profile implementation. The Service Locator then returns the matching service instance, such as the A2DP service for audio or the GATT service for BLE data. Each profile operates independently, but the Service Locator acts as the phonebook that ties them all together.</p>
<p>This pattern solves several key problems. First, it removes the need for every part of the system to know about every other part. Without it, each class would have to keep track of dozens of others, creating a tangled web of dependencies. With a Service Locator, everything becomes more modular. Each component can register itself once and be discovered whenever needed.</p>
<p>Second, it makes the system flexible. Android devices can enable or disable certain Bluetooth profiles depending on hardware support or user configuration. For example, a smartwatch might only need GATT, while a car infotainment system needs A2DP, HFP, and MAP. The Service Locator allows Android to load only the relevant profiles at runtime instead of baking them all in permanently.</p>
<p>Third, it helps with scalability. As new Bluetooth profiles are introduced, such as LE Audio or Broadcast Audio, they can be added without rewriting existing code. The Service Locator acts as the central meeting point that stays the same even as new services join the system. It’s like a well-organized switchboard that never needs rewiring, no matter how many new phones, watches, or speakers show up.</p>
<p>From a debugging standpoint, this design also makes life easier. Developers can trace which service is currently active or verify that a profile is registered correctly simply by inspecting the registry. It provides a single source of truth that reflects the system’s state at any moment.</p>
<p>On a philosophical level, the Service Locator pattern represents Android’s pragmatic approach to complexity. Instead of trying to make every module aware of the entire Bluetooth world, it centralizes coordination in a controlled, predictable way. It acknowledges that Bluetooth is not a single, monolithic feature but an ecosystem of cooperating components that need a shared directory to find each other efficiently.</p>
<p>So when your phone automatically switches from streaming audio over A2DP to transferring a file over OBEX or syncing notifications with your smartwatch, it happens seamlessly because the system always knows exactly which profile to use. That knowledge comes from the quiet work of the Service Locator pattern, acting like a backstage coordinator ensuring that the right performer walks on stage at the right time.</p>
<h2 id="heading-the-layered-architecture-pattern-from-app-to-radio-without-losing-the-plot">The Layered Architecture Pattern: From App to Radio Without Losing the Plot</h2>
<p><img src="https://source.android.com/static/docs/core/connect/bluetooth/images/fluoride_architecture.png" alt="Bluetooth | Android Open Source Project" class="image--center mx-auto" width="636" height="434" loading="lazy"></p>
<p>If there is one pattern that truly defines Android’s Bluetooth design philosophy, it is <strong>Layered Architecture</strong>. This is the invisible backbone that keeps the entire system structured, predictable, and scalable. In a world where Bluetooth involves everything from mobile apps to kernel drivers, layering is not just a matter of organization, but one of survival.</p>
<p>At first glance, Bluetooth might seem like a single feature. You turn it on, pair a device, and it works. But in reality, it’s a long, intricate journey that starts at the app layer, where you press “Connect”, and travels all the way down to the radio hardware, which emits electromagnetic signals into the air. Between those two points lies an entire vertical stack of software layers, each playing a distinct role, each isolated from the others by well-defined interfaces.</p>
<p>Think of it as a city with multiple levels. The top layer is where people live and work: that’s your app. Below that are roads and traffic systems, which are your Android framework services. Beneath that, you have subways and utilities, the native daemons written in C and C++ that handle protocol specifics. At the very bottom is the foundation, the hardware abstraction layer and the Bluetooth controller chip itself. Every level has a clear boundary. You can remodel one floor without collapsing the whole building.</p>
<p>Here is how those layers roughly line up in Android’s Bluetooth stack.</p>
<p>At the <strong>top layer</strong>, app developers interact with classes such as <code>BluetoothAdapter</code>, <code>BluetoothDevice</code>, and <code>BluetoothGatt</code>. These are part of the Android framework, written in Java or Kotlin, and serve as the public interface. They provide clean, stable methods like <code>startDiscovery()</code> and <code>connectGatt()</code>, hiding the technical chaos below.</p>
<p>The <strong>next layer down</strong> is the system service layer. This includes classes such as <code>BluetoothManagerService</code> and <code>AdapterService</code>. These are responsible for managing Bluetooth as a system feature, enforcing permissions, and coordinating multiple profiles. They act as the brain of the operation, processing commands, routing messages, and maintaining global state.</p>
<p>Below that is the <strong>JNI and native layer</strong>, written primarily in C and C++. This is where the logic gets closer to the metal. JNI (Java Native Interface) acts as a translator between the Java world and the native code. When a Java method like <code>enable()</code> is called, JNI forwards it to the native daemon that actually speaks Bluetooth protocol commands. This bridge keeps performance high while maintaining safety through strict boundaries.</p>
<p>Finally, we reach the <strong>hardware abstraction layer (HAL)</strong> and the <strong>Bluetooth controller</strong>. The HAL defines how the operating system interacts with the underlying hardware. It sends and receives HCI (Host Controller Interface) packets, the low-level binary messages that control the Bluetooth chip. From there, the controller takes over, turning digital instructions into radio signals that travel invisibly through the air to another device.</p>
<p>The brilliance of this design is in how each layer only needs to know about the one directly below it. The app layer never worries about the hardware, and the hardware never needs to know about the app. This clear separation makes it possible for Android to run across thousands of devices built by different manufacturers using different chipsets. It is a pattern that enforces order through boundaries.</p>
<p>There are practical benefits, too. The layered architecture makes the system modular. For instance, when new Bluetooth features arrive, like LE Audio or Bluetooth 5.4, Android engineers can modify only the relevant layers. The app APIs at the top can remain stable while the lower layers evolve to support the new specifications. This is how Android manages to maintain backward compatibility while still introducing new capabilities with every release.</p>
<p>The layering also helps with debugging and reliability. When something breaks, engineers can trace the issue by moving down through the layers like a detective. If an app crashes, the problem is likely near the top. If packets are missing, the issue may be in the native layer or HAL. Each layer leaves its own signature in the logs, helping developers pinpoint where things went wrong.</p>
<p>This pattern also teaches a timeless software design lesson: complexity becomes manageable only when divided. The layered architecture prevents the Bluetooth stack from turning into a tangled mess of cross-dependencies. It lets Android evolve gracefully rather than collapse under the weight of its own history.</p>
<p>So when you tap “Pair new device” on your phone and watch your earbuds connect, remember that your request travels down a carefully organized highway of software, from the app you see, through the framework, into native code, across the hardware abstraction, and finally out into the air as a radio signal. Every piece knows its role, every layer does its part, and together they make Bluetooth feel effortless. The magic of wireless connection is not just in the radio waves, but in the architecture that makes those waves behave.</p>
<h2 id="heading-putting-it-all-together-designing-bluetooth-style-systems">Putting It All Together: Designing Bluetooth-Style Systems</h2>
<p>By now, it’s easy to see that Android’s Bluetooth stack is not just a pile of random services and classes. It’s a carefully choreographed system built on timeless design principles that keep it reliable, flexible, and surprisingly elegant despite its complexity.</p>
<p>Each pattern – the Manager–Service split, the Facade, the State Machine, the Handler–Looper, the Observer, the Builder, the Strategy, the Template Method, the Service Locator, and the Layered Architecture – exists for a reason. Together, they form the invisible scaffolding that allows Bluetooth to connect billions of devices every day without falling apart.</p>
<p>The magic of these patterns is not that they make Bluetooth simple. Bluetooth will never be simple, as it’s an enormous specification with quirks, edge cases, and competing priorities. What these patterns do instead is make the system <strong>manageable</strong>. They turn unpredictability into structure, they replace chaos with order, and they make it possible for teams of engineers around the world to work on the same stack without tripping over each other.</p>
<p>If you step back, you’ll notice that every pattern in the Bluetooth system reflects a deeper philosophy:</p>
<ul>
<li><p>The Manager–Service pattern teaches the value of separation.</p>
</li>
<li><p>The Facade reminds us that good design hides unnecessary complexity.</p>
</li>
<li><p>The State Machine shows the power of predictability.</p>
</li>
<li><p>The Handler–Looper demonstrates the beauty of serialized concurrency.</p>
</li>
<li><p>The Observer proves that communication doesn’t require coupling.</p>
</li>
<li><p>The Builder celebrates incremental construction.</p>
</li>
<li><p>The Strategy encourages adaptability.</p>
</li>
<li><p>The Template Method enforces discipline without rigidity.</p>
</li>
<li><p>The Service Locator maintains organization in a crowded ecosystem.</p>
</li>
<li><p>And the Layered Architecture ties it all together, ensuring that every piece fits logically into the whole.</p>
</li>
</ul>
<p>These same ideas extend far beyond Bluetooth. You can apply them to almost any software system, a web service, a game engine, or even a simple mobile app. The principles remain the same: divide responsibilities, enforce clear boundaries, keep your interfaces stable, and design for change rather than permanence.</p>
<p>Systems that last are not the ones that are perfect on day one. They are the ones that can grow without collapsing under their own weight.</p>
<p>Android Bluetooth has been evolving for more than a decade. It has absorbed new technologies like LE Audio, Fast Pair, and broadcast audio. It has adapted to new hardware, new chipsets, and new use cases. Yet, at its core, the same patterns continue to guide it. That consistency is the reason Bluetooth on Android, despite its quirks, works as well as it does. It’s not just a story of wireless communication, it’s a story of good architecture.</p>
<p>So the next time you tap “Connect” on your phone and your earbuds instantly respond, pause for a moment. Beneath that single tap lies an orchestra of design patterns working in perfect harmony: managers delegating to services, handlers processing messages, observers reacting to broadcasts, and strategies choosing the right behavior for your hardware. It’s a quiet miracle of software design, a reminder that even the most invisible features on your device are built with care, patience, and an eye for long-term evolution.</p>
<p>And if you ever find yourself building a complex system that seems impossible to manage, take a cue from Android Bluetooth. Start small, define your layers, choose the right patterns, and let structure do the heavy lifting. The real magic in engineering isn’t in writing clever code. It’s in designing systems that stay calm, even when the world around them isn’t.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Key System Design Principles Behind High-Traffic Platforms Like Gaming and Job Discovery ]]>
                </title>
                <description>
                    <![CDATA[ Over the last three months, life has had me juggling a lot – managing my marriage, taking care of family health issues, and overseeing new construction work at home. Somehow, I got through it all. But looking back, I realised something important: I c... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-key-system-design-principles-behind-high-traffic-platforms-like-gaming-and-job-discovery/</link>
                <guid isPermaLink="false">68a5f915c3b51ce78e8f92b3</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Prankur Pandey ]]>
                </dc:creator>
                <pubDate>Wed, 20 Aug 2025 16:34:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755707525928/f4a02c14-fe62-4d6f-9afc-d887d45a98d4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the last three months, life has had me juggling a lot – managing my marriage, taking care of family health issues, and overseeing new construction work at home. Somehow, I got through it all. But looking back, I realised something important: I could’ve handled it much better if I had a <em>system</em> in place.</p>
<p>For me, a <strong>system</strong> means a set of rules, processes, and triggers that guide the entire workflow. This helps you conserve energy and not have to figure things out in the moment. It keeps things productive, efficient, and consistent.</p>
<p>Now that the chaos has settled, I’ve been thinking a lot about systems – not just in life, but in tech. I wish I had applied the same principles of <strong>system design</strong> earlier.</p>
<p>In this article, we’re going to explore real-world system design examples from domains like gaming and job platforms. These industries don’t just scale massively – they also demand high availability, low latency, and seamless customer experiences. Understanding how they’re built is a powerful way to level up your thinking as a developer or architect.</p>
<h3 id="heading-what-well-cover">What We’ll Cover</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-introduction-what-is-system-design-and-why-scale-matters">Introduction: What is System Design and Why Scale Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-approaches-to-system-design">Approaches to System Design</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-bottom-up-approach">1. Bottom-Up Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-top-down-approach">2. Top-Down Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-hybrid-design">3. Hybrid Design</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-important-concepts-in-system-design">Important Concepts in System Design</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-full-stack-web-application-components">Full Stack Web Application Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-computers-talk-to-each-other-the-internet">How Computers Talk to Each Other (The Internet)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-problem-of-growth">The Problem of Growth</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-speeding-up-your-website-with-caching-and-cdns">Speeding Up Your Website with Caching and CDNs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-your-application-monolith-vs-microservices">Building Your Application: Monolith vs. Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-apis">The APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-real-time-data">Handling Real-Time Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-databases">Databases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-cap-theorem">Understanding the CAP Theorem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-rate-limiting-and-monitoring">Rate Limiting and Monitoring</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-case-studies-scaling-in-the-real-world">Case Studies: Scaling in the Real World</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-case-study-1-scaling-a-job-search-application">Case Study 1: Scaling a Job Search Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-2-scaling-an-online-gaming-application">Case Study 2: Scaling an Online Gaming Application</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-qampa">Q&amp;A</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-notes">Final Notes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-introduction-what-is-system-design-and-why-scale-matters">Introduction: What is System Design and Why Scale Matters</h2>
<p><strong>System design</strong> is the process of defining the architecture, modules, interfaces, and data of a system.</p>
<p>In other words, system design means explaining the different parts of a system, like its structure, building blocks (modules), and components.</p>
<p>It’s a process used to define, develop, and design a system in a way that meets the specific needs of a business or organisation.</p>
<p>The main goal of system design is to give enough information and details about the system, and to properly implement its parts using models and views. Let’s now talk about the different parts of a system.</p>
<h3 id="heading-elements-of-a-system">Elements of a System:</h3>
<ul>
<li><p><strong>Architecture:</strong> This is a basic structure or model that shows how the system works, looks, and behaves.<br>  We often use <strong>flowcharts</strong> to explain and represent this architecture.</p>
</li>
<li><p><strong>Modules:</strong> These are smaller parts or sections of the system. Each module handles a specific task. When all modules are combined, they make the complete system.</p>
</li>
<li><p><strong>Components:</strong> These provide a specific function or a group of related functions. Components are usually made from one or more modules.</p>
</li>
<li><p><strong>Interfaces:</strong> This is the connection point where different parts (components) of the system exchange information with each other.</p>
</li>
<li><p><strong>Data:</strong> This refers to managing information and how it flows through the system.</p>
</li>
</ul>
<h3 id="heading-why-system-design-matters">Why System Design Matters</h3>
<p>System design is important for a number of practical reasons. First, it can help companies and teams solve complex business problems and make sure they thoroughly analyse all requirements before building. It also reduces the chance that errors will be introduced into processes while making design phases more efficient and structured. Finally, it helps you efficiently gather and present your data in a useful format and improves the overall quality of the system.</p>
<h2 id="heading-approaches-to-system-design">Approaches to System Design</h2>
<p>There are several methods you can use to approach system design. The main ones are:</p>
<h3 id="heading-1-bottom-up-approach">1. Bottom-Up Approach</h3>
<p>In this method, the design starts from the lowest-level components or subsystems. These small parts are gradually combined to form higher-level components. This process continues until the entire system is built as one complete structure.</p>
<p>The more abstraction we use, the higher the level of the design becomes.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p>Components can be reused in other systems.</p>
</li>
<li><p>It’s easier to identify risks early.</p>
</li>
<li><p>It helps in hiding low-level technical details and can be combined with the top-down approach.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>It’s not very focused on the overall structure of the problem.</p>
</li>
<li><p>Building high-quality bottom-up solutions is hard and time-consuming.</p>
</li>
</ul>
<h3 id="heading-2-top-down-approach">2. Top-Down Approach</h3>
<p>Here, the design starts from the entire system, and you break it down into smaller subsystems and components as you go. Each of these subsystems then gets broken down further, step by step, creating a hierarchical structure.</p>
<p>In simple terms, you start with the big picture and keep dividing it until you reach the smallest parts of the system.</p>
<p>To sum up, design starts with defining the whole system, then continues by defining its subsystems and components. When all definitions are ready and fit together, the system is complete.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>The focus is on understanding the requirements first, which leads to a responsive and purpose-driven design.</p>
</li>
<li><p>It’s easier to handle errors in interfaces between components.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>Components can’t be reused easily in other systems.</p>
</li>
<li><p>The resulting architecture is often less flexible or not very clean.</p>
</li>
</ul>
<h3 id="heading-3-hybrid-design">3. Hybrid Design</h3>
<p>The hybrid design approach is a mix of <strong>top-down</strong> and <strong>bottom-up</strong> methods. Instead of committing to just one way, it takes the strengths of both. You start by looking at the overall system (like in top-down) so that you don’t lose sight of the big picture. At the same time, you also focus on building solid, reusable modules or components (like in bottom-up).</p>
<p>In simple terms, you first plan the big picture, then create smaller components that can work independently, and finally combine everything into a cohesive system.</p>
<p>For instance, in our sports team site, we’d use top-down to define the whole fan journey (homepage → match details → live scores). But bottom-up, we’d build modular components like authentication or stats tracking, which can later be reused in new features like ticket booking or merchandise sales.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p>You get the clarity of a top-down plan while still building reusable modules.</p>
</li>
<li><p>It strikes a balance between high-level design and detailed implementation.</p>
</li>
<li><p>Risks are easier to manage since you’re considering both structure and components.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>It can be complex to manage since you’re juggling two approaches.</p>
</li>
<li><p>Requires more coordination between teams working on different levels.</p>
</li>
<li><p>It might take more time compared to using a single approach.</p>
</li>
</ul>
<h2 id="heading-important-concepts-in-system-design">Important Concepts in System Design</h2>
<p>Before exploring core components, I want you to first understand two key concepts:</p>
<ul>
<li><p>Full stack web application components</p>
</li>
<li><p>How computers talk to each other (via the internet)</p>
</li>
</ul>
<h3 id="heading-full-stack-web-application-components">Full Stack Web Application Components</h3>
<p>A full-stack web application is a software application that combines both the frontend (what users see and interact with) and the backend (the server, database, and logic that power the app) into one complete system.</p>
<p>Generally, simple websites don’t require much system design – and in some cases, no system design at all. But when it comes to viral applications or platforms offering complex services, system design becomes essential. Most modern applications are full-stack applications, meaning they involve multiple interconnected layers working together.</p>
<p>Here’s a simplified overview of a typical full-stack application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755603745131/1114a65f-ea7c-4e3f-aca6-4821c8ca683a.png" alt="full stack web overview" width="1920" height="1080" loading="lazy"></p>
<p>Before diving deep into each of these components, let me first give you a quick, high-level overview of what they are and how they fit into the bigger picture (starting from the bottom of the image above).</p>
<ul>
<li><p><strong>Frontend</strong> – The user interface where people interact with your application.</p>
</li>
<li><p><strong>Backend</strong> – The logic and brain of the application that processes requests.</p>
</li>
<li><p><strong>APIs</strong> – The bridge that allows communication between frontend, backend, and external services.</p>
</li>
<li><p><strong>Database</strong> – The storage system where all your structured information lives.</p>
</li>
<li><p><strong>Server</strong> – The infrastructure that hosts, runs, and delivers your application.</p>
</li>
</ul>
<p>Now, we need to understand how computers talk to each other.</p>
<h3 id="heading-how-computers-talk-to-each-other-the-internet">How Computers Talk to Each Other (The Internet)</h3>
<p>When you type a website's URL into the browser – and this site could be a simple portfolio site or a full stack app – how does your computer know where to send the request? It uses the <a target="_blank" href="https://www.freecodecamp.org/news/what-is-dns/"><strong>Domain Name System</strong></a> <strong>(DNS)</strong>. The DNS is like a phonebook for the internet – it translates a human-readable website name, like "example.com," into a unique numeric IP address that computers can understand.</p>
<p>Once your computer has the IP address, it uses <strong>communication protocols</strong> to send and receive data. One of the most important protocols is <a target="_blank" href="https://www.freecodecamp.org/news/tcp-vs-udp/"><strong>TCP</strong></a>. It breaks data into small, numbered packets. If a packet gets lost or arrives out of order, TCP ensures it's resent and reassembled correctly, making it a very reliable way to send data.</p>
<p>On top of TCP, we use higher-level protocols like <a target="_blank" href="https://www.freecodecamp.org/news/what-is-http/"><strong>HTTP</strong></a>. This is an application-level protocol that's easier for developers to use. It's the language your browser speaks to the server.</p>
<p><strong>HTTPS</strong> is the same, but it adds an extra layer of encryption for security.</p>
<p>Now that we understand the basics of the Internet, remember that it serves billions of people worldwide.</p>
<p>Let’s break this down with a real-life example. Imagine you own a restaurant with a seating capacity of 50 people. One day, 10 extra guests arrive – and with a bit of adjustment, you still manage. But suddenly, a thousand more people show up at your door. What would you do then? It’s not just about adding more chairs and tables anymore – you’ll need extra food supplies, more staff, and a bigger setup to handle such massive traffic.</p>
<p>This simple example reflects the real challenge of growth and scalability. And that’s exactly what I’ll be diving into in the next chapter of this tutorial.</p>
<h3 id="heading-the-problem-of-growth">The Problem of Growth</h3>
<p>Imagine you've built a simple website for a local sports team. Initially, it's just you and a few friends using it, so a single server is sufficient. This server holds all the website's logic and connects to a single database where player stats are stored.</p>
<p>As the team becomes more popular, though, more people visit your site, and it suddenly becomes slow. This is a <strong>scaling issue</strong>. Your system can't handle all the new traffic.</p>
<h4 id="heading-scaling-your-system-two-main-ways">Scaling Your System: Two Main Ways</h4>
<p>There are two ways to solve this. The first is <strong>vertical scaling</strong>. This is like giving your one server a bigger engine and more memory. You'd upgrade the CPU (the brain) or add more RAM (temporary memory). You could also use a faster disk storage like an SSD.</p>
<p>The problem is, you can only upgrade so much before you hit the limits of what's available. Plus, if that single server fails, your entire website goes down.</p>
<p>A better approach is <strong>horizontal scaling</strong>. This means adding more servers instead of just upgrading one. Now you have a team of servers, and each can handle a portion of the incoming user requests.</p>
<p>This approach allows for almost unlimited growth. It also creates redundancy and fault tolerance, because if one server breaks, the others can pick up the slack, and your site stays online.</p>
<h4 id="heading-directing-traffic-with-a-load-balancer">Directing Traffic with a Load Balancer</h4>
<p>With multiple servers, you need a way to make sure no single server gets overwhelmed. This is where a <strong>load balancer</strong> comes in. It acts like a traffic cop, sitting in front of your servers and directing each new request to the best-suited server. It uses different algorithms to decide where to send the traffic.</p>
<p>For example, the <strong>Round Robin</strong> method sends requests to servers one by one, in a cycle. Another method is <strong>Least Connection</strong>, which sends the request to the server that has the fewest active connections.</p>
<h3 id="heading-speeding-up-your-website-with-caching-and-cdns">Speeding Up Your Website with Caching and CDNs</h3>
<p>Imagine your website is now used by people all over the world. A user in another country might experience slow loading times because their request has to travel all the way to your servers.</p>
<p>To fix this, you can use a <a target="_blank" href="https://www.freecodecamp.org/news/how-cdns-improve-performance-in-front-end-projects/"><strong>Content Delivery Network (CDN)</strong></a>. A CDN is a network of servers around the world that store copies of your website's static files – like images, videos, and text files. When a user requests one of these files, the CDN serves it from the closest server, making the website load much faster for them.</p>
<p>This process is a form of <strong>caching</strong>. Caching is the general idea of making copies of data and storing them in a faster-to-access location. You can cache data on your server so it doesn't need to fetch the same player stats from the database every time. This reduces the load on your database and speeds up the entire application.</p>
<p>You can read more about the <a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">difference between CDNs and caching here</a>.</p>
<h3 id="heading-building-your-application-monolith-vs-microservices">Building Your Application: Monolith vs. Microservices</h3>
<p>As your website grows, its code can become a tangled mess. You might start with a <strong>monolith</strong>, where all the features (like player stats and live scores, in our example) are built into a single, large program. <a target="_blank" href="https://www.freecodecamp.org/news/microservices-vs-monoliths-explained/">A monolith is easier to start with</a>, but it can be hard to manage and update.</p>
<p>A better approach for a large-scale application is to use a <a target="_blank" href="https://www.freecodecamp.org/news/the-microservices-book-build-and-manage-services-in-the-cloud/"><strong>microservice architecture</strong></a>. This means breaking your application into smaller, independent services, each with a specific job. For example, one service could handle player stats and another could handle live scores. This makes your code more organised and easier to update, because a change in one service won't affect the others.</p>
<p>With microservices, you need an <a target="_blank" href="https://www.freecodecamp.org/news/what-are-api-gateways/"><strong>API Gateway</strong></a>. This acts as a single entry point for all user requests, directing them to the correct microservice behind the scenes. It also handles security and other common tasks.</p>
<h3 id="heading-the-apis">The APIs</h3>
<p>Think of <strong>APIs (Application Programming Interfaces)</strong> as the “middlemen” that let different pieces of software talk to each other.</p>
<p>In simple terms, an API is like a waiter in a restaurant. You (the user) tell the waiter what you want, the waiter takes your order to the kitchen (the system), and then brings the food (data or result) back to you.</p>
<p>Without APIs, your app, website, or software wouldn’t know how to ask another system for information or services.</p>
<p>For example, on our sports team website:</p>
<ul>
<li><p>The front-end (what fans see) uses an API to <strong>fetch player stats</strong> from the database.</p>
</li>
<li><p>When someone buys match tickets, the API talks to the <strong>payment system</strong> to confirm the transaction.</p>
</li>
<li><p>If fans want live score updates, the API makes sure the real-time data flows smoothly from the server to their screen.</p>
</li>
</ul>
<p>So, APIs are important for system design because they shape how efficiently different systems connect, share data, and stay reliable under real-world use.</p>
<p>Your front-end and back-end services can communicate in several ways. The most common is a <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-rest-api/"><strong>REST API</strong></a>. It's a standardised set of rules that uses HTTP to create a consistent way for a client and server to talk to each other. For example, it defines a standard way to signal a successful request ("OK") or a server error ("Internal Server Error").</p>
<h4 id="heading-when-to-use-rest">When to use REST</h4>
<ul>
<li><p>Best when: you need simplicity, broad adoption, and easy integration with browsers, mobile apps, or third-party services.</p>
</li>
<li><p>Example: CRUD apps (blogging platforms, e-commerce sites, user management).</p>
</li>
<li><p>Strength: Human-readable JSON, stateless, widely supported.</p>
</li>
<li><p>Weakness: Over-fetching (getting more data than needed) or under-fetching (not enough data).</p>
</li>
</ul>
<p>Another style is <a target="_blank" href="https://www.freecodecamp.org/news/building-consuming-and-documenting-a-graphql-api/"><strong>GraphQL</strong></a>. Instead of getting all the data a REST API provides, GraphQL lets the client ask for only the specific data it needs, which can make things faster and more efficient.</p>
<h4 id="heading-when-to-use-graphql">When to use GraphQL</h4>
<ul>
<li><p>Best when: clients (like mobile apps) need fine-grained control over exactly what data they fetch.</p>
</li>
<li><p>Example: social media feeds, dashboards with lots of widgets, mobile apps with limited bandwidth.</p>
</li>
<li><p>Strength: Flexible queries, reduces over-fetching, strong typing system.</p>
</li>
<li><p>Weakness: More complex server setup, which can cause performance issues if queries aren’t optimised.</p>
</li>
</ul>
<p>For server-to-server communication, <a target="_blank" href="https://www.freecodecamp.org/news/what-is-grpc-protocol-buffers-stream-architecture/"><strong>gRPC</strong></a> is often used. It's known for being very fast because it uses a more efficient data format called Protocol Buffers instead of JSON.</p>
<h4 id="heading-when-to-use-grpc">When to use gRPC</h4>
<ul>
<li><p>Best when: services talk to each other in microservice architectures, and speed/efficiency is critical.</p>
</li>
<li><p>Example: real-time systems (streaming, payments, IoT, machine learning inference).</p>
</li>
<li><p>Strength: Super fast (binary Protocol Buffers), built-in support for streaming, strong contracts.</p>
</li>
<li><p>Weakness: Not browser-native (needs extra tooling for web), harder debugging compared to REST</p>
</li>
</ul>
<p>So to summarize based on my observations of what I have worked on so far:</p>
<ul>
<li><p>If you’re building something <strong>public-facing and widely consumed</strong> → go for REST.</p>
</li>
<li><p>If your app has <strong>complex, dynamic queries from clients</strong> → go for GraphQL.</p>
</li>
<li><p>If you’re dealing with <strong>high-performance internal service-to-service calls</strong> → go for gRPC.</p>
</li>
</ul>
<p>In system design, choosing the right API style directly affects performance, scalability, and user experience. If you pick REST for its simplicity, GraphQL for its flexibility, or gRPC for its speed, you’re shaping how well your system can grow and adapt as real-world demands change.</p>
<h3 id="heading-handling-real-time-data">Handling Real-Time Data</h3>
<p>Real-time data handling is challenging because it requires maintaining an active connection to continuously transmit and receive data simultaneously. Traditional servers follow a request–response model, where data is only sent when explicitly requested.</p>
<p>That's where <a target="_blank" href="https://www.freecodecamp.org/news/learn-websockets-socket-io/"><strong>WebSockets</strong></a> come in. Unlike HTTP, which is a one-and-done request-and-response model, a WebSocket creates a continuous, two-way connection between the client and server. This allows the server to send updates to the user as soon as they happen, creating a real-time experience.</p>
<p>When microservices need to communicate without being directly connected, they can use <a target="_blank" href="https://www.freecodecamp.org/news/how-message-queues-make-distributed-systems-more-reliable/"><strong>message queues</strong></a>. A service sends a message to the queue, and another service picks it up when it's ready. This helps to decouple the services, so they don't have to worry about the other service being available at that exact moment.</p>
<p>On our sports site, WebSockets allow fans to see live scores instantly without refreshing the page – just like in chat apps, but here it keeps the excitement of the game alive in real time</p>
<h3 id="heading-databases">Databases</h3>
<p>Databases are a critical part of any full-stack application because they serve as the permanent home for user data. Once you’ve decided how to scale your servers and manage communication, you also need to consider the database layer. If everything else scales but the database does not, it can quickly become a bottleneck – leading to crashes, inconsistent records, or even data loss.</p>
<p>Many applications rely on <a target="_blank" href="https://www.freecodecamp.org/news/learn-relational-database-basics-key-concepts-for-beginners/"><strong>relational databases (SQL)</strong></a>, which store data in structured tables with rows and columns and are great for handling structured information. But for applications requiring high flexibility or handling massive unstructured datasets, <a target="_blank" href="https://www.freecodecamp.org/news/learn-nosql-in-3-hours/"><strong>NoSQL databases</strong></a> (like MongoDB or Cassandra) are often chosen. These databases don't follow the strict rules of SQL and are better for handling massive amounts of data.</p>
<p>They follow <a target="_blank" href="https://www.freecodecamp.org/news/acid-databases-explained/">ACID properties</a>:</p>
<ul>
<li><p><strong>Atomicity:</strong> A transaction is all or nothing.</p>
</li>
<li><p><strong>Consistency:</strong> The data always remains in a valid state.</p>
</li>
<li><p><strong>Isolation:</strong> Multiple transactions don't interfere with each other.</p>
</li>
<li><p><strong>Durability:</strong> Once a transaction is complete, the data is permanently saved.</p>
</li>
</ul>
<p>Just like with servers, you might need to scale your database. You can use <strong>sharding</strong>, which divides your data across multiple databases, or <strong>replication</strong>, which creates copies of your database to handle more read requests.</p>
<h3 id="heading-understanding-the-cap-theorem">Understanding the CAP Theorem</h3>
<p>When you're dealing with a distributed system and multiple databases, you inevitably face trade-offs. The <strong>CAP Theorem</strong> states that you can only guarantee two out of the following three properties at the same time:</p>
<ul>
<li><p><strong>Consistency</strong> – Every user sees the same, most up-to-date data.</p>
</li>
<li><p><strong>Availability</strong> – The system is always available to respond to requests.</p>
</li>
<li><p><strong>Partition Tolerance</strong> – The system continues to operate even if a part of the network fails.</p>
</li>
</ul>
<p>Now, from a system design perspective, this theorem forces us to make conscious architectural choices. For example, in financial applications (like banking), consistency often takes priority over availability because even a small inconsistency in balance data can cause chaos.</p>
<p>On the other hand, in social media feeds, availability and partition tolerance are often prioritised – it's okay if you see a slightly outdated post, but the system should never be down.</p>
<p>In the flow we’ve been discussing, whenever we introduce a new component or scale out across multiple regions, we need to reassess which two guarantees matter most for our business case. That decision directly drives what database technology we pick, how we design failover strategies, and what trade-offs we accept in user experience.</p>
<p>In short, the CAP theorem isn’t just a theory – it’s a practical compass. It guides us to balance user expectations, business priorities, and technical feasibility without breaking existing functionality, while still leaving room for future growth.</p>
<h3 id="heading-rate-limiting-and-monitoring">Rate Limiting and Monitoring</h3>
<p>When designing a system, it’s not just about making it <em>work</em> – it’s about making it resilient. Two core guardrails here are <strong>rate limiting</strong> and <strong>monitoring</strong>.</p>
<h4 id="heading-what-is-rate-limiting">What is Rate Limiting?</h4>
<p>Rate limiting is the practice of controlling how many requests a user, client, or service can make to your system within a given timeframe. For example, you might cap an API at 100 calls per user per hour. This prevents abuse, safeguards against denial-of-service attempts, and ensures fair usage across all consumers.</p>
<p>Rate limiting comes into play any time your service is exposed publicly or internally to multiple clients.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/implement-api-rate-limiting-in-strapi/">To incorporate it</a>, you can implement limits at the API gateway, reverse proxy (like NGINX), or within your service logic itself. Many cloud providers (AWS API Gateway, GCP Endpoints) also have built-in support.</p>
<h4 id="heading-what-is-monitoring">What is Monitoring?</h4>
<p><a target="_blank" href="https://www.freecodecamp.org/news/the-front-end-monitoring-handbook/">Monitoring</a> is the practice of collecting metrics, logs, and traces from your system to understand its health in real time. Typical signals include:</p>
<ul>
<li><p><strong>Error rates</strong> (for example, how often requests fail)</p>
</li>
<li><p><strong>Latency</strong> (how long requests take)</p>
</li>
<li><p><strong>Traffic volume</strong> (load across the system)</p>
</li>
<li><p><strong>Resource utilisation</strong> (CPU, memory, disk, and so on)</p>
</li>
</ul>
<p>Monitoring is important from day one – it’s your feedback loop. Without it, you’re essentially flying blind.</p>
<p>To work it into your system, you can use observability stacks like <a target="_blank" href="https://www.freecodecamp.org/news/kubernetes-cluster-observability-with-prometheus-and-grafana-on-aws/">Prometheus + Grafana</a>, or managed solutions like Datadog, New Relic, or CloudWatch. You can also set alerts for threshold breaches (for example, 5% error rate spike).</p>
<p>In practice, rate limiting and monitoring work hand-in-hand. Rate limiting proactively guards against overload, while monitoring gives you visibility into whether the limits are working, whether scaling is needed, or whether a new type of failure is emerging.</p>
<p>For example, if you’ve designed a booking system (like in our earlier flow), rate limiting would ensure a single user can’t spam seat reservations, while monitoring would flag anomalies such as unusual spikes in request volume or sudden latency increases – helping you act before the system collapses.</p>
<h4 id="heading-why-does-this-matter-for-system-design">Why Does This Matter for System Design?</h4>
<p>These topics matter for good system design because they form the foundational building blocks of how modern applications actually operate in the real world. The way systems communicate, the type of APIs we adopt, and how we manage real-time interactions directly influence whether a product feels fast, reliable, and seamless – or slow and frustrating. In short, they determine how well the overall experience holds up when real users put it to the test.</p>
<p>When we develop a deeper understanding of how computers communicate, we begin to see the inner mechanics of client–server architecture – how APIs fetch data from databases through backend system calls. From this baseline, we can pivot into higher-level concerns:</p>
<ul>
<li><p><strong>Scalability and resilience</strong>: Using load balancers to protect against server overload.</p>
</li>
<li><p><strong>Security</strong>: Introducing rate limiting to mitigate potential cyberattacks.</p>
</li>
<li><p><strong>Efficiency</strong>: Choosing the right type of API calls and leveraging caching/CDNs for speed and reduced overhead.</p>
</li>
<li><p><strong>Reliability</strong>: Implementing logging and monitoring to detect issues early and debug faster.</p>
</li>
</ul>
<p>Together, these practices elevate a system from simply <em>working</em> to being robust, performant, and future-ready.</p>
<p>We’ve discussed the basics of all the most important concepts you’ll need to understand before building an end-to-end system. Now it’s time to deep dive into the case studies, where I’ll show you how different types of applications use system design to scale and serve billions of users.</p>
<p>I have picked services that are complex to build and handle multiple different types of components at a time, like gaming, education, and job search platforms.</p>
<p>Now let’s decode each of them together, and I’ll explain how I would scale the application if I were the developer building it.</p>
<h2 id="heading-case-studies-scaling-in-the-real-world">Case Studies: Scaling in the Real World</h2>
<p>System design is best understood when you see it in action. To show how principles like scaling, caching, load balancing, and real-time data management come together, let’s walk through two very different types of applications:</p>
<ul>
<li><p>A <strong>job search platform</strong> (focused on structured data and reliability).</p>
</li>
<li><p>An <strong>online gaming platform</strong> (focused on real-time speed and responsiveness).</p>
</li>
</ul>
<p>Looking at both will show you that, while the tools and concepts may be similar, the way we apply them depends on the type of system we’re building.</p>
<p>Both are high-traffic platforms, but with totally different needs. The job portal is about accuracy, reliability, and data-driven workflows, while the gaming platform is about instant responsiveness, fairness, and global reach.</p>
<p>In a job portal, a 1-second delay just means waiting. In a gaming app, a 1-second delay could mean losing the match. Both are failures – but for completely different reasons, and with different consequences.</p>
<p>Together, they show how the same building blocks of system design (scaling, caching, APIs, monitoring) are applied differently depending on context.</p>
<h3 id="heading-case-study-1-scaling-a-job-search-application">Case Study 1: Scaling a Job Search Application</h3>
<p>A job search platform is one of the most used applications nowadays, as there are always people looking for a job. And there are many different job portals out there that handle the complete process, from finding jobs to user onboarding.</p>
<p>We’ll look at an example site called <a target="_blank" href="https://upstaff.com/">Upstaff</a>. It’s a platform that focuses on hiring AI engineers as its core service (although it services other job profiles as well). At its core, it handles structured information – things like user profiles, job postings, and applications.</p>
<p>Day one, you have a few hundred users. On day one hundred, you may have tens of thousands. And in a year? Possibly millions. That growth means you have to think about scale, speed, and data integrity from the start.</p>
<h4 id="heading-the-core-components">🔹 The Core Components</h4>
<ul>
<li><p><strong>User Management:</strong> registration, login, and role-based access (job seeker vs employer).</p>
</li>
<li><p><strong>User Profiles</strong>: résumés, skills, preferences, stored in structured databases.</p>
</li>
<li><p><strong>Job Posting and Listings</strong>: employers create jobs, seekers browse/search/filter.</p>
</li>
<li><p><strong>Application Tracking</strong>: Every job seeker’s application status needs to be accurate and up to date.</p>
</li>
<li><p><strong>Recommendation Engine</strong>: jobs matched to users based on history and profile.</p>
</li>
<li><p><strong>Notifications</strong>: alerts for new job matches, recruiter replies, deadlines.</p>
</li>
</ul>
<p>Every one of these features depends on the system’s ability to handle large amounts of structured data – and handle it reliably.</p>
<h4 id="heading-step-1-starting-small">Step 1: Starting Small</h4>
<p>At the beginning, everything can run on one server with a single database. This setup is enough for a few thousand users.</p>
<h4 id="heading-step-2-growth-and-traffic-spikes">Step 2: Growth and Traffic Spikes</h4>
<p>As more users join, the single server starts to slow down. To fix this, we add a load balancer and scale horizontally – adding multiple servers that share the traffic.</p>
<h4 id="heading-step-3-database-challenges">Step 3: Database Challenges</h4>
<p>Soon, the database becomes the bottleneck. Searching across thousands of jobs slows things down. To fix this, we:</p>
<ul>
<li><p>Use sharding (split the database by user IDs or job IDs).</p>
</li>
<li><p>Add a cache (like Redis) to store frequent queries such as “Software Engineer in New York.”</p>
</li>
<li><p>Use a CDN to deliver logos, profile pictures, and other static files faster.</p>
</li>
</ul>
<h4 id="heading-step-4-heavy-features">Step 4: Heavy Features</h4>
<p>New features like a résumé parser or recommendation engine require extra computing power. Instead of overloading the main app, we move these into separate microservices.</p>
<h4 id="heading-step-5-security-and-reliability">Step 5: Security and Reliability</h4>
<p>Finally, as traffic grows, we add:</p>
<ul>
<li><p><strong>Rate limiting</strong> to stop any one user from spamming APIs.</p>
</li>
<li><p><strong>Monitoring</strong> to track errors, latency, and user activity in real time.</p>
</li>
<li><p><strong>API Gateway</strong> to ensure all requests are secure and validated. Here is an overview of the entire system scaling in an image :</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754281195211/570680c1-2813-43b0-87a1-48a817ab6c9a.png" alt="system design job portal flowchart" width="1920" height="1080" loading="lazy"></p>
<p>This example shows how careful planning makes growth smooth. By scaling horizontally, caching smartly, and splitting heavy features into microservices, a job portal like Upstaff can handle millions of users without breaking.</p>
<h3 id="heading-case-study-2-scaling-an-online-gaming-application">Case Study 2: Scaling an Online Gaming Application</h3>
<p>Now let’s flip the script. In a gaming platform like <a target="_blank" href="https://xn--ntcasinoutanlicens-ltb.com">this site</a>, speed and responsiveness matter more than anything. A 1-second delay in a job search is annoying. But in gaming, a 1-second delay can make players quit forever. Unlike job portals, the biggest challenge here is real-time responsiveness. A tiny delay can ruin the user experience.</p>
<h4 id="heading-the-core-components-1">🔹 The Core Components</h4>
<ul>
<li><p><strong>User Management Service</strong>: accounts, profiles, and login.</p>
</li>
<li><p><strong>Game Lobby and Matchmaking</strong>: pair players by skill, region, and latency.</p>
</li>
<li><p><strong>Game Server Manager</strong>: spin up and manage live matches.</p>
</li>
<li><p><strong>Real-Time Communication</strong>: powered by WebSockets or UDP for low latency.</p>
</li>
<li><p><strong>Game State Store (Redis)</strong>: fast sync of health, scores, and positions.</p>
</li>
<li><p><strong>Leaderboard &amp; Stats Engine</strong>: global rankings, achievements, and progress.</p>
</li>
<li><p><strong>In-Game Economy</strong>: coins, tokens, inventory.</p>
</li>
<li><p><strong>Payment Gateway</strong>: subscriptions and purchases.</p>
</li>
<li><p><strong>Anti-Cheat Security Layer</strong>: fairness across all players.</p>
</li>
<li><p><strong>Monitoring and Logging</strong>: server uptime, latency, and crash reports.</p>
</li>
</ul>
<p>Unlike a job portal, every millisecond counts.</p>
<h4 id="heading-step-1-starting-small-1">Step 1: Starting Small</h4>
<p>At first, one powerful server is enough to run both the game logic and user accounts. With just a few players, things run smoothly.</p>
<h4 id="heading-step-2-more-players-more-problems">Step 2: More Players, More Problems</h4>
<p>As millions of players log in, the single server crashes. To fix this, we:</p>
<ul>
<li><p>Add a Game Server Manager that spins up separate servers for each match.</p>
</li>
<li><p>Introduce a load balancer that assigns players to available servers.</p>
</li>
</ul>
<h4 id="heading-step-3-real-time-data-handling">Step 3: Real-Time Data Handling</h4>
<p>In gaming, speed is everything. Instead of slow HTTP, we switch to WebSockets or UDP for instant communication. To keep everyone’s game view in sync:</p>
<ul>
<li><p>Use in-memory databases like Redis for positions, scores, and health.</p>
</li>
<li><p>Update leaderboards in near real time.</p>
</li>
</ul>
<h4 id="heading-step-4-scaling-features">Step 4: Scaling Features</h4>
<p>Other services run in parallel:</p>
<ul>
<li><p><strong>Matchmaking service</strong> pairs players by skill, location, and latency.</p>
</li>
<li><p><strong>Economy service</strong> manages coins, rewards, and in-game items.</p>
</li>
<li><p><strong>Payment gateway</strong> handles subscriptions and purchases securely.</p>
</li>
<li><p><strong>Notification system</strong> sends updates like “new event starting.”</p>
</li>
</ul>
<h4 id="heading-step-5-global-expansion-and-security">Step 5: Global Expansion and Security</h4>
<p>When the game expands worldwide:</p>
<ul>
<li><p>Use a CDN to deliver maps and skins quickly to all regions.</p>
</li>
<li><p>Add an Anti-Cheat layer to detect and block unfair play.</p>
</li>
<li><p>Build an Admin and Monitoring panel to track system health and user behavior.</p>
</li>
</ul>
<p>In gaming, system design focuses less on structured data and more on low latency, real-time communication, and fairness. Scaling here means keeping gameplay smooth and secure, even when millions of players join at once. Here is the image representation of the complete game platform system design</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754287625776/546f3949-efa1-4a57-9906-ceb3f8a62f63.png" alt="system design game website flowchart" width="1920" height="1080" loading="lazy"></p>
<h3 id="heading-why-both-case-studies-matter">Why Both Case Studies Matter</h3>
<p>You might wonder – why show two different systems instead of just one? The answer is that system design isn’t “one-size-fits-all.”</p>
<ul>
<li><p>The job portal teaches us how to scale structured, data-heavy applications where reliability and accuracy matter most.</p>
</li>
<li><p>The gaming platform shows us how to design for speed, real-time communication, and fairness under extreme load.</p>
</li>
</ul>
<p>Together, these examples prove that the same system design principles of scaling, caching, monitoring, and microservices apply everywhere. What changes is <em>how you use them</em> to solve the unique challenges of your platform.</p>
<h2 id="heading-qampa">Q&amp;A</h2>
<h3 id="heading-how-to-get-into-system-design-if-you-dont-understand-anything-yet"><strong>How to get into system design if you don’t understand anything (yet)?</strong></h3>
<p>I get this question all the time – and the first thing you need to know is that system design isn’t some separate, elite domain. It’s an <em>additional skill</em> that complements your development journey.</p>
<p>If you're a full-stack developer (or aiming to be one), learning system design gives you a huge edge. After all, building an app isn't just about making it work – it’s about making it <em>work well</em> at scale.</p>
<p>So if you’re just starting and don’t even know how to become a full-stack developer yet, start there. Learn to build applications first, and then system design will start making a lot more sense. Read this guide <a target="_blank" href="https://www.freecodecamp.org/news/become-a-full-stack-developer-and-get-a-job/">How to Become a Full-Stack Developer in 2025 (and Get a Job) – A Handbook for Beginners</a> to learn how to become a full-stack developer.</p>
<h3 id="heading-how-do-you-understand-system-design-concepts"><strong>How do you understand system design concepts?</strong></h3>
<p>The short answer: with time and consistent practice.</p>
<p>Think of it like this: if you know how to use a pencil, it’s up to you whether you use it to sketch or to write. The pencil is just a tool. Similarly, in system design, once you understand the core concepts, it’s about knowing <em>when</em> and <em>where</em> to apply them. The rest – frameworks, tools, and technologies – are just means to an end.</p>
<p>It’s not about memorising patterns, it’s about developing the instinct to use the right building blocks at the right time.</p>
<h3 id="heading-what-tools-should-you-know-before-diving-into-system-design">What tools should you know before diving into system design?</h3>
<p>The truth is, the list keeps growing. New tools and platforms are constantly emerging. But in my experience, having a solid foundation in the following areas makes a huge difference:</p>
<ul>
<li><p><strong>Full Stack Development</strong> – so you understand how both frontend and backend systems interact.</p>
</li>
<li><p><strong>Cloud Platforms</strong> (like AWS, GCP, or Azure) – because most modern systems are cloud-native.</p>
</li>
<li><p><strong>CI/CD Pipelines</strong> – for automating testing, integration, and deployment.</p>
</li>
<li><p><strong>Deployment Strategies</strong> – to know how to roll out new changes with minimal risk.</p>
</li>
</ul>
<p>Mastering these gives you the technical muscle to design systems that are scalable, reliable, and production-ready. I am a frontend developer, why should I know the system design</p>
<h3 id="heading-what-resources-should-i-study-to-learn-system-design">What resources should I study to learn system design?</h3>
<p>In my last <a target="_blank" href="https://www.freecodecamp.org/news/become-a-full-stack-developer-and-get-a-job">article</a>, I shared all the resources that helped me learn system design.</p>
<p>System design is crucial for building reliable, high-performance applications. I explored the following resources:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/donnemartin/system-design-primer"><strong>Free GitHub system design repositor</strong></a><strong>y</strong></p>
</li>
<li><p><a target="_blank" href="https://github.com/ByteByteGoHq/system-design-101"><strong>Another free GitHub system design repositor</strong></a><strong>y</strong></p>
</li>
<li><p><a target="_blank" href="https://www.systemdesignhandbook.com/system-design-interview-handbook/">Free System Design Handbook</a></p>
</li>
<li><p><a target="_blank" href="https://grokkingthesystemdesign.com/intro-to-system-design/">Free Hands-on System Design Learning Platform</a></p>
</li>
<li><p><a target="_blank" href="https://www.educative.io/guide/system-design">Free Intro to System Design Blog</a></p>
</li>
</ul>
<p>Case studies and real-world architectures can also help you understand large-scale systems. You can follow any big tech engineering blog (Uber has a great one).</p>
<p>For high-level concepts, I went through the <a target="_blank" href="https://www.educative.io/courses/grokking-the-system-design-interview"><strong>Grokking System Design</strong></a> course. It’s a paid resource, and I used it to deepen my understanding of system design. It’s not mandatory, but it helped me think about architecture at scale.</p>
<p><strong>Note:</strong> there are other sites and courses out there of course, but I only share what I have personally experienced and used, and I focus on FREE material first.</p>
<h3 id="heading-where-to-practice-system-design">Where to practice system design</h3>
<p>This is where real learning begins. Start by picking any existing application from the internet, just like I did. Google something specific, like “job application portal,” but avoid the results on the first page. Those apps are usually well-optimised and already follow best practices in system design.</p>
<p>Instead, dig deeper and explore results from the second or third page. Look for an app that seems to be in its early stages.</p>
<p>Once you find one, try to understand how the entire application works. Break it down into its core components and then imagine what would happen if that app started receiving 1 million users a day. You’ll naturally begin to see what system design elements are needed to handle that kind of load.</p>
<h2 id="heading-final-notes">Final Notes</h2>
<p>Learning system design becomes much easier when you’ve already built something. Let’s say you’ve created an app and now you're thinking about how to scale it – that’s where real learning begins. The moment you start writing down your requirements (like how your app should behave when it starts getting more traffic), you naturally begin to develop system-level thinking. It’s this process of planning and anticipating real-world usage that turns theory into a practical skill.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Full Stack + System Design = The Ultimate Developer Stack 🔥</p>
<p>By mastering these skills, you can turn any idea into a real-world product, secure high-paying jobs, and even start your tech venture.</p>
<p>Now it's your turn – what are you building next? Let me know!</p>
<p>That’s all from my side. If you found this article helpful, feel free to share it and connect with me. I’m always open to new opportunities:</p>
<ul>
<li><p>Follow me on X: <a target="_blank" href="https://x.com/prankurpandeyy">Prankur's Twitter</a></p>
</li>
<li><p>Connect with me on LinkedIn: <a target="_blank" href="https://linkedin.com/in/prankurpandeyy">Prankur's LinkedIn</a></p>
</li>
<li><p>Follow me on GitHub: <a target="_blank" href="https://github.com/prankurpandeyy">Prankur’s Github</a></p>
</li>
<li><p>View my Portfolio: <a target="_blank" href="https://prankurpandeyy.netlify.app/">Prankur's Portfolio</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The Micro-Frontend Architecture Handbook ]]>
                </title>
                <description>
                    <![CDATA[ Over the years, in my role as a lead full-stack developer, solutions architect, and mentor, I’ve been immersed in the world of micro frontend architecture, working across different large-scale frontend projects where multiple teams, stacks, and deplo... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/complete-micro-frontends-guide/</link>
                <guid isPermaLink="false">6842c120a3469a9ca728862b</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Frontend Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Andrew Maksimchenko ]]>
                </dc:creator>
                <pubDate>Fri, 06 Jun 2025 10:21:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1748915817752/b35a8786-9aa7-46cd-a1d8-f82069470496.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the years, in my role as a lead full-stack developer, solutions architect, and mentor, I’ve been immersed in the world of micro frontend architecture, working across different large-scale frontend projects where multiple teams, stacks, and deployment pipelines had to coexist somehow.</p>
<p>As projects grew in complexity and teams worked in parallel across different stacks, it became clear that monolithic approaches couldn’t keep up. I needed practical tools that allowed easy cross-app interaction, independent deployability, better team autonomy, framework-agnosticism, and more. Some solutions worked elegantly in theory but struggled in real-world conditions. Others made things messier and more painful than helpful.</p>
<p>After diving deep into different paradigms—from iframes to Web Components, single-spa, Module Federation, Piral, Luigi, and hybrid setups—I even distilled my proven experience into a full-fledged online course on Udemy.</p>
<p>And today, in this comprehensive hands-on tutorial, I want to share my expertise and tell you more about micro-frontend architecture—method by method—with code, tradeoffs, visuals, and real-world insights.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-are-micro-frontends-for">What are Micro Frontends For?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-1-iframes-amp-cross-window-messaging">Method #1: Iframes &amp; Cross-Window Messaging</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-2-web-components-custom-elements-shadow-dom">Method #2: Web Components (Custom Elements + Shadow DOM)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-3-single-spa-the-meta-framework-approach">Method #3: Single-SPA — The Meta-Framework Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-method-4-module-federation-sharing-code-at-runtime">Method #4: Module Federation - Sharing Code at Runtime</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-tools-amp-ecosystem-additions">Other Tools &amp; Ecosystem Additions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-thoughts">Final Thoughts</a></p>
</li>
</ul>
<h2 id="heading-what-are-micro-frontends-for">What are Micro Frontends For?</h2>
<p>In traditional frontend development, we often build single, monolithic apps—one codebase, one repo, one deployment pipeline, one team. It works great for small to medium projects, sometimes even for larger ones.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770222181/fb73c7ce-366f-4897-9ab7-b208c6e37cfa.png" alt="Monolith App Diagram - Three Features in React" class="image--center mx-auto" width="2218" height="1416" loading="lazy"></p>
<p>But challenges arise when:</p>
<ul>
<li><p>Your frontend codebase expands beyond 50+ components.</p>
</li>
<li><p>Multiple development teams need autonomy over different parts and tech stacks.</p>
</li>
<li><p>Different sections require varying deployment frequencies (weekly or monthly).</p>
</li>
<li><p>You need to integrate diverse frameworks, like combining React features with an Angular-based CMS.</p>
</li>
</ul>
<p>This is where micro frontends step in.</p>
<p>Micro frontends extend the principles of microservices to the frontend world. Instead of one big frontend app, you build independent frontend modules, each owned by a team, using its own tech stack, deployed separately, and integrated at runtime.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770253697/c78a8d84-a6a9-42af-90fd-423983c7ec77.png" alt="Micro-Frontends App Diagram - Three Apps in React, Angular, Vue" class="image--center mx-auto" width="2214" height="1424" loading="lazy"></p>
<p>Think of it like Lego blocks:</p>
<ul>
<li><p>Each block is similar to a self-contained micro frontend.</p>
</li>
<li><p>They plug into a shared layout or shell.</p>
</li>
<li><p>Each can evolve, update, or be replaced without affecting the others.</p>
</li>
</ul>
<p>For example, imagine that you’re building a modern e-commerce site, and here’s what your business side expects from you:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>Section</code></td><td><code>Team</code></td><td><code>Stack</code></td><td><code>Deployment</code></td></tr>
</thead>
<tbody>
<tr>
<td>Product Listing</td><td>Search Team</td><td>React</td><td>Weekly</td></tr>
<tr>
<td>Product Details</td><td>Catalog Team</td><td>Angular</td><td>Monthly</td></tr>
<tr>
<td>Cart &amp; Checkout</td><td>Checkout Team</td><td>Vue</td><td>Biweekly</td></tr>
<tr>
<td>CMS Pages</td><td>Marketing Team</td><td>Vanilla JS</td><td>Daily</td></tr>
</tbody>
</table>
</div><p>Each team wants autonomy, and with micro frontends, each of these sections becomes a separate app, loaded dynamically into a shell at runtime.</p>
<h3 id="heading-why-its-getting-popular">Why It’s Getting Popular?</h3>
<p>Here are a few things everyone considers:</p>
<ol>
<li><p><strong>Independent deployments</strong> – A little or no effort to coordinate every release.</p>
</li>
<li><p><strong>Team autonomy</strong> – Teams choose their own stack and tools on the project.</p>
</li>
<li><p><strong>Incremental upgrades</strong> – Migrate legacy apps piece by piece incrementally without the need to rewrite the whole app at once.</p>
</li>
<li><p><strong>Technical agnosticism</strong> – Vue, React, Angular? Doesn’t matter. They can all work together seamlessly at the same time in a single app.</p>
</li>
<li><p><strong>Better scalability</strong> – Parallelize work across teams to enable efficiency of delivery and scale at ease.</p>
</li>
</ol>
<p>Now let’s discover how we can bring this idea to life in our projects.</p>
<p>Nowadays, there are different ways to achieve that, but not all solutions are equal. The implementation method you choose will drastically affect:</p>
<ul>
<li><p>Developer experience</p>
</li>
<li><p>Bundle sizes and performance</p>
</li>
<li><p>SEO and accessibility</p>
</li>
<li><p>Runtime stability</p>
</li>
<li><p>Interoperability across stacks</p>
</li>
</ul>
<p>So let’s begin by exploring the oldest, but still surprisingly viable method.</p>
<h2 id="heading-method-1-iframes-amp-cross-window-messaging"><strong>Method #1: Iframes &amp; Cross-Window Messaging</strong></h2>
<p>You may ask, “Aren’t iframes bad?” They’re often misunderstood. While yes, iframes can feel clunky and isolated, they’re also the most secure and decoupled way to host micro frontends—especially when you don’t trust the team on the other side.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748770863603/9daefd01-22ac-413f-bf54-c339bb6e4e9e.png" alt="Micro-Frontend Method 1 - Iframes" class="image--center mx-auto" width="1772" height="964" loading="lazy"></p>
<h3 id="heading-what-is-an-iframe"><strong>What Is an IFRAME?</strong></h3>
<p>An <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/iframe"><strong>iframe</strong></a> (inline frame) is an HTML element that allows you to embed another HTML page within your current webpage. The whole communication between apps is strictly based on events and delivered by means of the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><strong>Post Message API.</strong></a></p>
<p>If you need to send data to another app, you simply call the <code>postMessage()</code> method on that element. On the other side, to receive a message, you just have to subscribe to the <code>message</code> event. That’s it.</p>
<h3 id="heading-real-world-example">Real-World Example</h3>
<p>Let’s see a simple example of two apps communicating with each other using <code>iframes</code> on two apps:</p>
<ul>
<li><p>The Main Web App</p>
</li>
<li><p>A Search App.</p>
</li>
</ul>
<p>Every iframe must be hosted somewhere to serve static content from it. It can be AWS Amplify, Digital Ocean, Heroku, GitHub Pages, or alike.</p>
<p>To help you out here, <a target="_blank" href="https://pages.github.com">here’s an official GitHub guideline</a> explaining how to host a website on their platform.</p>
<p>Let’s say you deployed a Search App on Github Pages and you were given this URL to host your app: <a target="_blank" href="https://search.example.com"><code>https://example.github.io</code></a>. Now let’s write some content for it.</p>
<p>Assuming that you want to post messages from the Search App to the Main Web App, and to subscribe to the incoming messages from it there. You can do it in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Initializing Search App...'</span>);

<span class="hljs-comment">// Subscribe to messages from outside the iframe (like Main Web App)</span>
<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-keyword">if</span> (event.data?.type === <span class="hljs-string">'init'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Main Web App passed userId:'</span>, event.data.userId);
  }
});

<span class="hljs-comment">// Simulate sending Search results back to Main Web App</span>
<span class="hljs-built_in">window</span>.parent.postMessage({
  <span class="hljs-attr">type</span>: <span class="hljs-string">'searchResult'</span>,
  <span class="hljs-attr">payload</span>: [<span class="hljs-string">'Item A'</span>, <span class="hljs-string">'Item B'</span>]
}, <span class="hljs-string">'*'</span>);
</code></pre>
<p>Here, you initialize the search app and set up two-way communication with a parent application (such as a main web app) using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage"><strong>Post Message API</strong></a>. You listen for incoming messages using the built-in <code>message</code> event. Once received, that message becomes available in the <code>event.data</code> object. Finally, you simulate sending data back to the parent by posting a <code>searchResult</code> message containing a list of items. This setup enables isolated iframe-based apps to communicate safely with the main shell application.</p>
<p>Then, in the DOM of the main web app<strong>,</strong> you need to include the iframe that will render the search app, specifying the URL to the hosted search app in this way:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span>
  <span class="hljs-attr">id</span>=<span class="hljs-string">"search-mfe"</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"https://example.github.io"</span>
  <span class="hljs-attr">style</span>=<span class="hljs-string">"width: 100%; height: 200px; border: none;"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
</code></pre>
<p>Styles were added here to ensure that the <code>iframe</code> displays seamlessly within the layout for a cleaner UI integration.</p>
<p>And now you can pass some content from the main web app down to the search app and get some messages from it. You can accomplish it in the main web app’s JavaScript code in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Initializing Main Web App...'</span>);

<span class="hljs-keyword">const</span> iframe = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'search-mfe'</span>);
iframe.onload = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Send message to child iframe (inputs)</span>
  iframe.contentWindow.postMessage({ <span class="hljs-attr">type</span>: <span class="hljs-string">'init'</span>, <span class="hljs-attr">userId</span>: <span class="hljs-number">42</span> }, <span class="hljs-string">'*'</span>);
};

<span class="hljs-built_in">window</span>.addEventListener(<span class="hljs-string">'message'</span>, <span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> {
  <span class="hljs-comment">// Receive data from the Search App (outputs)</span>
  <span class="hljs-keyword">if</span> (event.data?.type === <span class="hljs-string">'searchResult'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Received result from Search App: '</span>, event.data.payload);
  }
});
</code></pre>
<p>As you see, when the <code>iframe</code> loads, the <code>init</code> event is sent to the search app (the <code>type</code> can be anything you want, just ensure it matches the one that another app expects from you). And then, in the <code>message</code> event handler as before, you can receive the incoming messages from the search app, and do something with them.</p>
<p>Here are a few pros and cons to consider, along with popular use cases:</p>
<h3 id="heading-pros"><strong>✅ Pros:</strong></h3>
<ul>
<li><p><strong>Strong sandboxing</strong>: No shared memory, no shared styles.</p>
</li>
<li><p><strong>Zero dependency clashes</strong>: One iframe is equivalent to one environment.</p>
</li>
<li><p><strong>Perfect for legacy</strong>: Easy to wrap old apps in an iframe.</p>
</li>
<li><p><strong>Practical</strong> for micro-apps in PHP, Java, Razor (ASP.NET)</p>
</li>
</ul>
<h3 id="heading-cons"><strong>❌ Cons:</strong></h3>
<ul>
<li><p>Slow rendering</p>
</li>
<li><p>Difficult shared navigation</p>
</li>
<li><p>Inconsistent/complicated styling</p>
</li>
<li><p>Complex communication</p>
</li>
<li><p>Must be hosted somewhere</p>
</li>
</ul>
<h3 id="heading-popular-use-cases"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<ul>
<li><p>Embedding legacy dashboards (for example, old AngularJS or Java apps)</p>
</li>
<li><p>Secure cross-domain apps (for example, payments, 3rd party analytics)</p>
</li>
<li><p>Highly untrusted integrations</p>
</li>
<li><p>Embedded Ads</p>
</li>
</ul>
<p>But if you want a more fluid UX, shared components, and a smoother dev experience, you’ll want something better. That brings us to Web Components.</p>
<h2 id="heading-method-2-web-components-custom-elements-shadow-dom"><strong>Method #2: Web Components (Custom Elements + Shadow DOM)</strong></h2>
<blockquote>
<p>“What if you could ship a self-contained natively understood widget that works in any framework — React, Vue, Angular, or plain HTML?”</p>
</blockquote>
<p>That’s exactly what <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">Web Components</a> make possible. They’re natively built into the browser as an <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components">API</a>, you don’t need a framework or extra dependency. They allow you to create reusable, scalable, encapsulated UI elements that work just like native HTML tags.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748773939725/8b017162-96a8-449d-b9b8-5fe8ef382e91.png" alt="Micro-Frontend Method 2 - Web Components" class="image--center mx-auto" width="1974" height="1414" loading="lazy"></p>
<p>Moreover, you can easily use them as wrappers around any elements from other UI frameworks (React, Angular, Svelte, etc) and use your framework-based components as regular native DOM elements in any web application.</p>
<p>They are, in many ways, the ideal foundation for micro frontends.</p>
<p>A web component is made of:</p>
<ul>
<li><p><strong>Custom Element</strong> - defines your own HTML tag (&lt;user-profile&gt;) and behavior</p>
</li>
<li><p><strong>Shadow DOM</strong> – provides scoped, encapsulated styles and DOM structure</p>
</li>
<li><p><strong>HTML Template</strong> – brings reusable HTML blocks/fragments</p>
</li>
<li><p><strong>Slots</strong> – acts as placeholder areas for host content (used in content projection)</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748772947093/6090d9bb-2c10-4a92-9ece-c5235b8382a2.png" alt="Micro-Frontend Method 2 - Web Components Key Blocks" class="image--center mx-auto" width="2740" height="1220" loading="lazy"></p>
<p>In web components<strong>,</strong> you have to sync the data (input/output) via:</p>
<ul>
<li><p><strong>Attributes</strong> (inputs):</p>
<ul>
<li><p>In Javascript: <code>element.setAttribute()</code>, <code>element.getAttribute()</code>, and so on.</p>
</li>
<li><p>In HTML: <code>&lt;element attr1=”value1” attr2=”value2”&gt;&lt;/element&gt;</code></p>
</li>
</ul>
</li>
<li><p><strong>Properties</strong> (inputs) – <code>element.someProp = value</code> (only Javascript)</p>
</li>
<li><p><strong>Custom Events</strong> (outputs) - <code>new CustomEvent('name', data)</code></p>
</li>
</ul>
<p>First, let me show you a basic implementation of a web component, and then you’ll learn how to leverage it for micro-frontends.</p>
<p>Assuming that you’re building a reusable product-tile component that must:</p>
<ul>
<li><p>Accept one input parameter – <code>“title”</code></p>
</li>
<li><p>Send an output event <code>"add-to-cart"</code> with this <code>“title”</code> to the outside world, when the component is mounted to the DOM.</p>
</li>
</ul>
<p>Here’s how this web component could look:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-tile.js</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProductTile</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'title'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
      <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>
      <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
      <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
      <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
      shadow.innerHTML = <span class="hljs-string">`&lt;div id="title"&gt;&lt;/div&gt;`</span>;
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called when the custom element ProductTile is added to the DOM</span>
  connectedCallback() {
      <span class="hljs-comment">// When added to the DOM, read and render the title attribute</span>
      <span class="hljs-keyword">const</span> title = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'title'</span>) ?? <span class="hljs-string">'Unnamed Product'</span>;
      <span class="hljs-built_in">this</span>.updateTitle(title);

      <span class="hljs-comment">// Dispatch a custom event with the current title</span>
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(<span class="hljs-string">'add-to-cart'</span>, {
          <span class="hljs-attr">detail</span>: { title },
          <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,
          <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>,
      });

      <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called whenever observed attributes change.</span>
  <span class="hljs-comment">// In our case it's "title" only</span>
  attributeChangedCallback(name, oldValue, newValue) {
      <span class="hljs-keyword">if</span> (name === <span class="hljs-string">'title'</span> &amp;&amp; oldValue !== newValue) {
          <span class="hljs-built_in">this</span>.updateTitle(newValue);
      }
  }

  <span class="hljs-comment">// Internal method to safely update the title content</span>
  updateTitle(title) {
      <span class="hljs-keyword">const</span> titleElem = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#title'</span>);
      titleElem.textContent = title;
  }
}

customElements.define(<span class="hljs-string">'product-tile'</span>, ProductTile);
</code></pre>
<p>Now, let me explain what’s happening here:</p>
<ul>
<li><p>First, you create a custom element class that extends from <code>HTMLElement</code> or its children. This gives you access to web component lifecycle hooks and DOM integration capabilities.</p>
</li>
<li><p>If you want to react to changes in input parameters (attributes), you have to define a static <code>observedAttributes()</code> getter that returns a list of attribute names to watch. In our case, we observe <code>“title”</code>.</p>
</li>
<li><p>Then, in the constructor:</p>
<ul>
<li><p>Call <code>super()</code> to properly inherit from <code>HTMLElement</code>.</p>
</li>
<li><p>Create a shadow DOM using <code>attachShadow({ mode: 'open' })</code>. This encapsulates your component’s internal DOM and styles. You can even use a <code>closed</code> mode here to add a higher level of isolation to the shadow DOM.</p>
</li>
<li><p>Then, populate the shadow DOM with minimal inner HTML—in this case, a <code>&lt;div&gt;</code> element that will later display the product title.</p>
</li>
</ul>
</li>
<li><p>When the component is added to the DOM, the built-in <code>connectedCallback()</code> lifecycle reaction runs:</p>
<ul>
<li><p>It reads the current value of the <code>"title"</code> attribute.</p>
</li>
<li><p>Updates the UI with an initial value in the <code>"title"</code> attribute.</p>
</li>
<li><p>Then it dispatches a custom event named <code>"add-to-cart"</code>, passing the <code>"title"</code> as detail down to it. The events are <code>bubbles: true</code> and <code>composed: true</code>, so that parent elements or host apps outside the shadow DOM can subscribe to it and catch it.</p>
</li>
</ul>
</li>
<li><p>When the title attribute changes at runtime, another built-in lifecycle reaction named <code>attributeChangedCallback()</code> runs automatically:</p>
<ul>
<li><p>It checks the new value and updates the <code>"title"</code> display accordingly.</p>
</li>
<li><p>This enables reactive behavior in the component—similar to input bindings in UI frameworks.</p>
</li>
</ul>
</li>
<li><p>Finally, you register the component globally using <code>customElements.define()</code> method (it’s available in the global <code>window</code> object), giving it:</p>
<ul>
<li><p>A tag name of <code>&lt;product-tile&gt;</code> that can be used anywhere in HTML.</p>
</li>
<li><p>A <code>reference</code> to the custom element you previously created to associate one with another.</p>
</li>
</ul>
</li>
</ul>
<p>Ultimately, here’s how you can use this component in your apps, which will work in vanilla JS, React, Angular, Svelte, Vue, whatever UI framework you choose:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">product-tile</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Coffee Mug"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">product-tile</span>&gt;</span>
</code></pre>
<p>And then you can listen to the <code>"add-to-cart"</code> event from inside <code>ProductTile</code> component like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> elem = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'product-tile'</span>);
elem.addEventListener(<span class="hljs-string">'add-to-cart'</span>, <span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Add to cart!'</span>, e.detail);
});
</code></pre>
<p>As you see, no <code>ReactDOM.render</code>, no <code>NgModule</code>, no extra glue. Everything is entirely native, pure <strong>JavaScript</strong> code that browsers understand.</p>
<p>And now, due to the Shadow DOM and other Web Components’ features, you can easily wrap and embed any web app written in a different framework into the Shadow Tree that will isolate your app entirely and won’t allow its layout or styles to leak out.</p>
<p>Alternatively, if you decide to publish it as a separate npm package (for example, <code>@webcomp/product-tile</code>), you can even dynamically import and mount the Web Component like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span>(<span class="hljs-string">'@webcomp/product-tile'</span>).then(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Now &lt;product-tile&gt; is defined — you can create and use it</span>
  <span class="hljs-keyword">const</span> elem = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'product-tile'</span>);
  elem.setAttribute(<span class="hljs-string">'title'</span>, <span class="hljs-string">'Wireless Mouse'</span>);
  <span class="hljs-built_in">document</span>.body.appendChild(elem);
});
</code></pre>
<p>Or load from CDN or any hosting provider:</p>
<pre><code class="lang-jsx">&lt;script type=<span class="hljs-string">"module"</span> src=<span class="hljs-string">"https://example.github.io/product-tile.js"</span>&gt;&lt;/script&gt;
</code></pre>
<p>It’s simple, clean, and independent.</p>
<p>But you’re not here just for that, right? :) Now, let’s learn the real power of Web Components in a micro-frontends world!</p>
<h3 id="heading-micro-frontends-with-web-components"><strong>Micro-Frontends with Web Components</strong></h3>
<p>Imagine that you’ve built a Video Player in React—or perhaps want to reuse one from another team. Now the question is: How can you make this React-based player usable in any other frontend application, regardless of its underlying framework, using Web Components?</p>
<p>Let’s figure it out!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748785841227/e58d9ffd-3098-4652-ae52-a55ab218c8fd.png" alt="Micro-Frontend Method 2 - Web Components - Real World Example" class="image--center mx-auto" width="2066" height="1320" loading="lazy"></p>
<p>Let’s say, this video player:</p>
<ul>
<li><p>Accepts <code>src</code> and <code>controls</code> as inputs</p>
</li>
<li><p>Emits events: <code>play</code> and <code>pause</code> as outputs</p>
</li>
<li><p>Can be used in any app via <code>&lt;magic-player&gt;</code> in this way:</p>
<pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">magic-player</span>
    <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.example.com/video.mp4"</span>
    <span class="hljs-attr">controls</span>=<span class="hljs-string">"true"</span>
  &gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">magic-player</span>&gt;</span>
</code></pre>
</li>
</ul>
<p>Now let’s get to implementation!</p>
<p><strong>🔹</strong> <strong>Step #1: Include your React player in the project</strong></p>
<p>Here, you can play around with any React component of your choice, to be honest, or you can just use a simple React Video Player like the one below:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// ReactVideoPlayer.jsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ReactVideoPlayer</span>(<span class="hljs-params">{ src, controls, onPlay, onPause }</span>) </span>{
  <span class="hljs-keyword">return</span> (
      <span class="hljs-comment">// HTML5 video element with full width and controls enabled</span>
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">video</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">"100%"</span>
      <span class="hljs-attr">controls</span>=<span class="hljs-string">{controls}</span>  {/* <span class="hljs-attr">Enable</span> / <span class="hljs-attr">Disable</span> <span class="hljs-attr">controls</span> */}
      <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{onPlay}</span>      {/* <span class="hljs-attr">Callback</span> <span class="hljs-attr">for</span> <span class="hljs-attr">play</span> <span class="hljs-attr">event</span> */}
      <span class="hljs-attr">onPause</span>=<span class="hljs-string">{onPause}</span>    {/* <span class="hljs-attr">Callback</span> <span class="hljs-attr">for</span> <span class="hljs-attr">pause</span> <span class="hljs-attr">event</span> */}
    &gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{src}</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"video/mp4"</span> /&gt;</span>
      Your browser does not support the video tag.
    <span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>🔹</strong> <strong>Step #2: Create the Web Component Wrapper</strong></p>
<p>Now, you need to create a Web Component wrapper around this React player app by mounting it into the shadow DOM of a custom element in this way:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.js</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p>Then you need to add inputs and outputs like so:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.js</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'src'</span>, <span class="hljs-string">'controls'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadowRoot = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadowRoot.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }

  <span class="hljs-comment">// Helper-like method to dispatch native-like events (our outputs)</span>
  <span class="hljs-comment">// In our case, it will be triggered for "onPlay" and "onPause" events</span>
  dispatch(eventName, detail = {}) {
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(eventName, {
      detail,            <span class="hljs-comment">// Pass custom data ("onPlay" or "onPause")</span>
      <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// Allow event to bubble up</span>
      <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>     <span class="hljs-comment">// Allow it to cross the Shadow DOM boundary</span>
    });
    <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p>And lastly, add two built-in lifecycle reactions to render a React video player app when the page loads and every time the inputs change:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// magic-player.element.jsx</span>

<span class="hljs-comment">// Define a new custom element class</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MagicPlayerElement</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">HTMLElement</span> </span>{
  <span class="hljs-comment">// Specify which attributes (inputs) to observe for changes</span>
  <span class="hljs-keyword">static</span> <span class="hljs-keyword">get</span> <span class="hljs-title">observedAttributes</span>() { <span class="hljs-keyword">return</span> [<span class="hljs-string">'src'</span>, <span class="hljs-string">'controls'</span>]; }

  <span class="hljs-keyword">constructor</span>() {
    <span class="hljs-built_in">super</span>(); <span class="hljs-comment">// Call base HTMLElement constructor (obligatory)</span>

    <span class="hljs-comment">// Create a Shadow DOM for style and DOM encapsulation</span>
    <span class="hljs-keyword">const</span> shadow = <span class="hljs-built_in">this</span>.attachShadow({ <span class="hljs-attr">mode</span>: <span class="hljs-string">'open'</span> });
    <span class="hljs-comment">// Populate Shadow DOM with a DIV container where React will render the player</span>
    shadow.innerHTML = <span class="hljs-string">`
        &lt;div id="react-video-player"&gt;&lt;/div&gt;
    `</span>;
  }

  <span class="hljs-comment">// Helper-like method to dispatch native-like events (our outputs)</span>
  <span class="hljs-comment">// In our case, it will be triggered for "onPlay" and "onPause" events</span>
  dispatch(eventName, detail = {}) {
      <span class="hljs-keyword">const</span> event = <span class="hljs-keyword">new</span> CustomEvent(eventName, {
      detail,            <span class="hljs-comment">// Pass custom data ("onPlay" or "onPause")</span>
      <span class="hljs-attr">bubbles</span>: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// Allow event to bubble up</span>
      <span class="hljs-attr">composed</span>: <span class="hljs-literal">true</span>     <span class="hljs-comment">// Allow it to cross the Shadow DOM boundary</span>
    });
    <span class="hljs-built_in">this</span>.dispatchEvent(event);
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called when the custom element &lt;magic-player&gt; is added to the DOM</span>
  connectedCallback() {
    <span class="hljs-built_in">this</span>.render();
  }

  <span class="hljs-comment">// Built-in Lifecycle Reaction.</span>
  <span class="hljs-comment">// Called whenever observed attributes change.</span>
  <span class="hljs-comment">// In our case it's "src" and "controls"</span>
  attributeChangedCallback() {
    <span class="hljs-built_in">this</span>.render();
  }

  <span class="hljs-comment">// Render the React player inside the container</span>
  render() {
    <span class="hljs-keyword">const</span> src = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'src'</span>);
    <span class="hljs-keyword">const</span> controls = <span class="hljs-built_in">this</span>.getAttribute(<span class="hljs-string">'controls'</span>) === <span class="hljs-string">'true'</span>;
    <span class="hljs-keyword">const</span> mount = <span class="hljs-built_in">this</span>.shadowRoot.querySelector(<span class="hljs-string">'#react-video-player'</span>);

    ReactDOM.createRoot(mount).render(
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ReactVideoPlayer</span>
        <span class="hljs-attr">src</span>=<span class="hljs-string">{src}</span>
        <span class="hljs-attr">controls</span>=<span class="hljs-string">{controls}</span>
        <span class="hljs-attr">onPlay</span>=<span class="hljs-string">{()</span> =&gt;</span> this.dispatch('play')}
        onPause={() =&gt; this.dispatch('pause')}
      /&gt;</span>
    );
  }
}

customElements.define(<span class="hljs-string">'magic-player'</span>, MagicPlayerElement);
</code></pre>
<p><strong>🔹</strong> <strong>Step #3: Connect your React-Player to any UI framework:</strong></p>
<p>Then, in the main web app (whatever UI framework you’re using there). We put our newly created React video player wrapper in any place in the DOM, passing down initial attributes (inputs) to it:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- Use your new React-based player anywhere! --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">magic-player</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.example.com/movie.mp4"</span>
  <span class="hljs-attr">controls</span>=<span class="hljs-string">"true"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">magic-player</span>&gt;</span>
</code></pre>
<p>And then you can easily subscribe to the custom events (outputs) from inside the React app:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Listen to native-style events from the custom element</span>
<span class="hljs-keyword">const</span> magicPlayer = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">'magic-player'</span>);
magicPlayer.addEventListener(<span class="hljs-string">'play'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Video has started playing!'</span>);
});

magicPlayer.addEventListener(<span class="hljs-string">'pause'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Video has been paused.'</span>);
});
</code></pre>
<p>That’s it! Now, try to accomplish the same with a different <strong>UI framework</strong>!</p>
<h3 id="heading-pros-1"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Framework-agnostic:</strong> Works in React, Angular, Vue, Svelte, or even plain HTML — no rewrites needed</p>
</li>
<li><p><strong>Natively supported by browsers:</strong> No need for external libraries or frameworks — just HTML, JS, and CSS.</p>
</li>
<li><p>No extra configuration or hosting needed as in iframes. But still, components can be published to npm/CDNs and reused across multiple apps.</p>
</li>
<li><p><strong>Intuitive &amp; easy communication:</strong> Expose native DOM attributes as inputs and native custom events as outputs.</p>
</li>
<li><p><strong>SSR-friendly with hydration:</strong> It supports serialization, declarative shadow DOM, and can be server-rendered and hydrated, especially using modern tools.</p>
</li>
<li><p><strong>Supports Accessibility</strong> (ARIA attributes and roles).</p>
</li>
</ul>
<h3 id="heading-cons-1"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Integration Difficulties</strong>: If you want to bridge two apps in different technical stacks, you need to properly manage their communication in a custom element wrapper and its shadow DOM.</p>
</li>
<li><p><strong>Limited Support for old Browsers</strong>: If you need compatibility with legacy browsers like Internet Explorer 10, Web Components need a polyfill. But here’s a popular repository with all polyfills for Web Components: <a target="_blank" href="https://github.com/webcomponents/polyfills">https://github.com/webcomponents/polyfills</a></p>
</li>
<li><p><strong>Global State Isolation</strong>: There’s no built-in way to share state across components. You’ll need to implement your own global bus or event bridge using <code>CustomEvents</code> or alike.</p>
</li>
</ul>
<h3 id="heading-popular-use-cases-1"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<ul>
<li><p>Reusable Design systems &amp; UI libraries</p>
</li>
<li><p>Micro frontends inside framework apps</p>
</li>
<li><p>Legacy integration to modern stack and vice versa</p>
</li>
<li><p>Cross-team component delivery</p>
</li>
<li><p>CDN-based plug-and-play UIs</p>
</li>
</ul>
<p>The Web Components API has many more possibilities and power. So, if you want, you can go deeper and advance your knowledge by passing any available free course on freeCodeCamp or passing the one I’ve built myself around this technique on Udemy.</p>
<p>Now let’s move on!</p>
<h2 id="heading-method-3-single-spa-the-meta-framework-approach"><strong>Method #3: Single</strong>-SPA — The Meta-<strong>Framework Approach</strong></h2>
<blockquote>
<p>“What if instead of embedding micro frontends as Web Components or iframes, we had a system that orchestrated multiple SPAs together in one layout?”</p>
</blockquote>
<p>That’s what <a target="_blank" href="https://single-spa.js.org/">single-spa</a> is all about. It’s not a rendering library, it’s a runtime JavaScript router and orchestrator for micro frontends.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748788736898/90800e32-f8d0-4fc5-aedb-e7ce8d753c4c.png" alt="Micro-Frontend Method 3 - Single SPA" class="image--center mx-auto" width="3024" height="1457" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://single-spa.js.org/">https://single-spa.js.org</a></p>
</blockquote>
<h3 id="heading-what-is-single-spa"><strong>What Is</strong> single-spa<strong>?</strong></h3>
<p>single-spa (Single Page Application) lets you build and run multiple independent SPAs (React, Vue, Angular, and so on) inside one webpage. Each SPA is responsible for part of the UI and is loaded dynamically depending on the current route.</p>
<p>In short, it’s a <strong>framework</strong> that:</p>
<ul>
<li><p>Loads your micro frontends when needed</p>
</li>
<li><p>Mounts/unmounts them cleanly</p>
</li>
<li><p>Coordinates routing and lifecycles</p>
</li>
<li><p>Supports different frameworks in the same app.</p>
</li>
</ul>
<h3 id="heading-real-life-example"><strong>Real-Life Example</strong></h3>
<p>Let’s say you have this route breakdown:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><code>Path</code></td><td><code>Micro Frontend App</code></td><td><code>Stack</code></td><td><code>App Name</code></td></tr>
</thead>
<tbody>
<tr>
<td>/products</td><td>Product Listing App</td><td>React</td><td><code>@shop/products</code></td></tr>
<tr>
<td>/checkout</td><td>Checkout App</td><td>Vue</td><td><code>@shop/checkout</code></td></tr>
<tr>
<td>/account</td><td>Account Dashboard</td><td>Angular</td><td><code>@shop/account</code></td></tr>
</tbody>
</table>
</div><p>Each one is a <strong>fully independent SPA</strong>, and single-spa loads them as needed.</p>
<p><strong>🔹</strong> <strong>Step #1:</strong> single-spa <strong>installation</strong></p>
<p>First, you need to install the single-spa as a dependency for your project:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a new project (if it's not yet)</span>
npm init

<span class="hljs-comment"># Install Single SPA</span>
npm install single-spa systemjs
</code></pre>
<p>Notice that we also installed the <code>systemjs</code> package. This package is responsible for the dynamic runtime module loading that makes Single-SPA work seamlessly. It uses <code>SystemJS</code> as a module loader to allow micro frontends to be:</p>
<ol>
<li><p>Loaded at runtime</p>
</li>
<li><p>Independently deployed</p>
</li>
<li><p>Framework-agnostic</p>
</li>
<li><p>Lazy-loaded only when needed</p>
</li>
</ol>
<p>Now you need to implement each micro-app. For instance, let’s see how the <code>@shop/products</code> app written in React could be managed.</p>
<p><strong>🔹 Step #2: Project Structure</strong></p>
<p>The project structure for each micro app can look like this:</p>
<pre><code class="lang-apache"><span class="hljs-attribute">shop</span>/products/
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">root</span>.component.jsx
│   └── <span class="hljs-attribute">index</span>.single-spa.js
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html
├── <span class="hljs-attribute">package</span>.json
└── <span class="hljs-attribute">webpack</span>.config.js
</code></pre>
<p><strong>🔹 Step #3: Root Micro App Component</strong></p>
<p>The <code>root.component.jsx</code> file represents the root of the React app that will be mounted to the main DOM using single-spa. Here’s a simple example:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/root.component.jsx</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>', <span class="hljs-attr">border:</span> '<span class="hljs-attr">1px</span> <span class="hljs-attr">solid</span> #<span class="hljs-attr">ccc</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>🛍 Product Micro App<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a micro frontend powered by React + Single-SPA!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p><strong>🔹</strong> <strong>Step #4: Set Up Lifecycle Hooks</strong></p>
<p>Also, each Micro App in single-spa requires an entry point with at least three core functions/lifecycle hooks. For that purpose, you will need a separate file, which you can name as <code>index.single-spa.js</code> and it will provide the implementation of those hooks, like:</p>
<ul>
<li><p><code>bootstrap()</code> - Called when the micro app is launched by the main app (Shell) before mounting to the DOM</p>
</li>
<li><p><code>mount()</code> - Called when the app is attached to the host in the DOM</p>
</li>
<li><p><code>unmount()</code> - Called when the app is removed/detached from the DOM</p>
</li>
</ul>
<p>And here’s an example of what they could look like:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/index.single-spa.js</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> Root <span class="hljs-keyword">from</span> <span class="hljs-string">'./root.component.jsx'</span>;

<span class="hljs-comment">// Hold the React root instance for reuse</span>
<span class="hljs-keyword">let</span> root = <span class="hljs-literal">null</span>;

<span class="hljs-comment">// Called once when the micro frontend is first initialized</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">bootstrap</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve();
}

<span class="hljs-comment">// Called every time the route matches and the app should appear</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mount</span>(<span class="hljs-params">props</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> container = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'product-container'</span>) || createContainer();
    root = ReactDOM.createRoot(container);
    root.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Root</span> /&gt;</span></span>);
  });
}

<span class="hljs-comment">// Called when the route no longer matches (cleanup)</span>
<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">unmount</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve().then(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (root) {
      root.unmount();
    }
  });
}

<span class="hljs-comment">// Create a container div if it doesn't exist</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createContainer</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> div = <span class="hljs-built_in">document</span>.createElement(<span class="hljs-string">'div'</span>);
  div.id = <span class="hljs-string">'product-container'</span>;
  <span class="hljs-built_in">document</span>.body.appendChild(div);
  <span class="hljs-keyword">return</span> div;
}
</code></pre>
<p>As you see, you have to resolve a Promise in all lifecycle hooks and ensure the React app is mounted and unmounted properly based on the React best practices.</p>
<p><strong>🔹</strong> <strong>Step #5: Configuring Webpack for SystemJS</strong></p>
<p>Also, each micro-app in single-spa needs a separate configuration. For that, you will include a <code>webpack.config.js</code> file, specifying how to build the app (<code>output</code>), where to host it (<code>publicPath</code>), and so on.</p>
<p>Since single-spa uses the <code>SystemJS</code> package, the <code>libraryTarget</code> will be <code>system</code> for all micro apps.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// webpack.config.js</span>
<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">externals</span>: {
    <span class="hljs-attr">react</span>: <span class="hljs-string">'React'</span>,
    <span class="hljs-string">'react-dom'</span>: <span class="hljs-string">'ReactDOM'</span>,
  },
  <span class="hljs-attr">output</span>: {
    <span class="hljs-attr">filename</span>: <span class="hljs-string">'products.js'</span>,
    <span class="hljs-attr">libraryTarget</span>: <span class="hljs-string">'system'</span>, <span class="hljs-comment">// SystemJS-compatible format</span>
    <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'http://localhost:8500/'</span>, <span class="hljs-comment">// Host location of this micro app</span>
  },
};
</code></pre>
<p>This app will be hosted on the <a target="_blank" href="http://localhost:8500"><code>localhost:8500</code></a>. For production, you will have to use any suitable hosting provider (like the ones described in the iframes section).</p>
<p><strong>🔹</strong> <strong>Step #6: Registering the Micro App in Root-Config</strong></p>
<p>Next, it’s time to register a new micro-app in the Singla-SPA root config. Here’s how you can do it:</p>
<p>Create a <code>root-config.js</code> file in the root of the project and fill it with this content:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// root-config.js (host shell)</span>
<span class="hljs-keyword">import</span> { registerApplication, start } <span class="hljs-keyword">from</span> <span class="hljs-string">'single-spa'</span>;

registerApplication({
  <span class="hljs-attr">name</span>: <span class="hljs-string">'@shop/products'</span>,
  <span class="hljs-attr">app</span>: <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">'@shop/products'</span>),
  <span class="hljs-attr">activeWhen</span>: [<span class="hljs-string">'/products'</span>],
});

start(); <span class="hljs-comment">// Initializes routing and micro app lifecycles</span>
</code></pre>
<p>First, you have to register the application, and then you start it to enable routing and the micro app lifecycle. The registration for other micro apps will look the same.</p>
<p><strong>Note</strong>: <code>System.import()</code> is part of <code>SystemJS</code>, used by default in single-spa for loading remote apps.</p>
<p>Also, single-spa comes with so-called "Parcels" – a lower-level construct in comparison to applications. They’re essentially self-contained pieces of UI that you can dynamically mount anywhere. Think of them like “mini microfrontends” or reusable widgets that don’t control routing:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Example</span>
mountParcel(SomeParcelComponent, { <span class="hljs-attr">domElement</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'micro-app'</span>) });
</code></pre>
<p>You’d use them when:</p>
<ul>
<li><p>You don’t want the parcel to own a route.</p>
</li>
<li><p>You need to inject a micro frontend dynamically inside another one.</p>
</li>
<li><p>You want encapsulated logic (like a widget) embedded within a larger app.</p>
</li>
</ul>
<p>In all other cases, prefer the usage of a <code>registerApplication(...)</code> function.</p>
<p><strong>🔹</strong> <strong>Step #7: Adding Micro App to SystemJS Import Map</strong></p>
<p>The last step is to register the micro app in <code>SystemJS</code>. For that, in your root <code>index.html</code> file, you need to add the following two scripts:</p>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- public/index.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Micro Frontend Shell<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/products"</span>&gt;</span>Products<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> |
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/checkout"</span>&gt;</span>Checkout<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Import maps handled by bundler or injected at runtime --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"systemjs-importmap"</span>&gt;</span><span class="javascript">
    {
      <span class="hljs-string">"imports"</span>: {
        <span class="hljs-string">"@shop/root-config"</span>: <span class="hljs-string">"http://localhost:9000/root-config.js"</span>,
        <span class="hljs-string">"@shop/products"</span>: <span class="hljs-string">"http://localhost:8500/products.js"</span>,
        <span class="hljs-comment">// other micro apps</span>
      }
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- Start the root-config application --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
    System.import(<span class="hljs-string">'@shop/root-config'</span>);
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>First, you have to add a script with an import map declaration. As you see, it represents a JSON where:</p>
<ul>
<li><p>Each key is the micro app name and</p>
</li>
<li><p>Each value is the URL where the main JS file (from the bundle) actually lives</p>
</li>
</ul>
<p>Note that we’ve added the <code>@shop/root-config</code> here to the import map to tell <code>SystemJS</code> where to fetch the main JavaScript file for the main/shell app so it knows how to resolve and execute <code>System.import('@shop/root-config')</code> properly.</p>
<p>Secondly, you include another script to start the main / shell application. It executes the JS file you just mapped in the import map above. Treat it as the real “boot” of your shell app:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  System.import(<span class="hljs-string">'@shop/root-config'</span>);
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>That’s it! Now go ahead and try doing the same with other micro-apps in Vue (Checkout App) and Angular (Account Dashboard).</p>
<p>Here’s a simple diagram illustrating this connection:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748789553598/4729600f-54d7-4d72-97e7-462093cf08b5.png" alt="Micro-Frontend Method 3 - Single SPA - Real World Example" class="image--center mx-auto" width="1232" height="882" loading="lazy"></p>
<p>Now that you’ve registered and integrated your first micro app, you might be wondering if this approach right for you. Let’s quickly look at the benefits and limitations of using single-spa in production.</p>
<h3 id="heading-pros-2"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Built-in Routing &amp; Lifecycles</strong> - No need to reinvent navigation or mounting logic</p>
</li>
<li><p><strong>Cross-framework support</strong> - React, Vue, Angular can all co-exist</p>
</li>
<li><p><strong>Fine-grained loading</strong> - Only load the active app (lazy and efficient)</p>
</li>
<li><p><strong>Flexible project structure</strong> - can be monorepo or polyrepo</p>
</li>
<li><p><strong>Good CLI tooling -</strong> create and link MFEs with create-single-spa &amp; helpers</p>
</li>
</ul>
<h3 id="heading-cons-2"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Complex learning curve</strong> - Lifecycle APIs and <code>SystemJS</code> can be intimidating</p>
</li>
<li><p><strong>Configurations</strong> <strong>can get verbose</strong> – Managing multiple registries, import maps, deployment URLs, and lifecycle wrappers across apps adds setup overhead</p>
</li>
<li><p><strong>Shared state is manual</strong> - You must implement custom global state solutions</p>
</li>
<li><p><strong>Hard to SSR</strong> - Designed for full client-side rendering</p>
</li>
<li><p><strong>More boilerplate</strong> - Each app needs wrappers for lifecycles, routing, and so on.</p>
</li>
<li><p><strong>Global styles leak -</strong> No default encapsulation like Shadow DOM</p>
</li>
</ul>
<p>And a few popular <strong>use cases</strong> for it:</p>
<h3 id="heading-popular-use-cases-2"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<p>You can use single-spa when:</p>
<ul>
<li><p>You want a central router managing all micro frontends</p>
</li>
<li><p>Teams are using different frameworks</p>
</li>
<li><p>You prefer full SPA experiences over isolated widgets</p>
</li>
<li><p>You don’t mind some boilerplate for orchestration</p>
</li>
<li><p>You’re okay with a purely client-side setup</p>
</li>
</ul>
<p>Let’s move on!</p>
<h2 id="heading-method-4-module-federation-sharing-code-at-runtime"><strong>Method #4: Module Federation - Sharing Code at Runtime</strong></h2>
<blockquote>
<p>“What if your micro frontends could load each other’s components, modules, or libraries at runtime — without iframes, without import maps, and without repackaging?”</p>
</blockquote>
<p>That’s exactly what <a target="_blank" href="https://module-federation.io/">Module Federation</a>, introduced in <a target="_blank" href="https://webpack.js.org/blog/2020-10-10-webpack-5-release/">Webpack 5</a>, makes possible. It’s fairly new and it allows multiple, separately built and deployed applications to share modules in real-time, via the browser.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748789750463/ad976d48-f564-4e94-a3ca-c18e9612dc55.png" alt="ad976d48-f564-4e94-a3ca-c18e9612dc55" class="image--center mx-auto" width="2772" height="1020" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://module-federation.io/">https://module-federation.io/</a></p>
</blockquote>
<p>With Module Federation, you can:</p>
<ul>
<li><p>Import components across independent builds</p>
</li>
<li><p>Share React, Vue, or any dependency</p>
</li>
<li><p>Version-control exposed modules</p>
</li>
<li><p>Ship independently, yet consume each other</p>
</li>
</ul>
<p>Module Federation is what makes micro frontends in a single cohesive layout truly feel like one app.</p>
<p>Now let’s see it in action!</p>
<h3 id="heading-real-life-example-1"><strong>Real-Life Example</strong></h3>
<p>Let’s assume that you have to build two self-contained apps:</p>
<ul>
<li><p>Main / Host app (shell) — loads components from others (let’s say it’s in React)</p>
</li>
<li><p>Remote app (product-app) — exposes components written also in React to others</p>
</li>
</ul>
<p>Module Federation allows you to export these components without publishing them to NPM or wrapping them as a Web Component. Instead, the host app will load the component directly at runtime from the compiled JavaScript bundle.</p>
<p>Here’s how the project structure could look:</p>
<p><strong>Product App:</strong></p>
<pre><code class="lang-apache"><span class="hljs-attribute">product</span>-app/                ← Remote Micro Frontend
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html          ← Mount point for optional local test render
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">ProductTile</span>.jsx     ← Component to expose
│   └── <span class="hljs-attribute">index</span>.js            ← Optional: local entry point
├── <span class="hljs-attribute">webpack</span>.config.js       ← Exposes Product App
├── <span class="hljs-attribute">package</span>.json
└── .<span class="hljs-attribute">babelrc</span> / .gitignore / etc
</code></pre>
<p>Note, that <code>webpack.config.js</code> must be at the root level, same as <code>package.json</code>, so <code>Webpack</code> can locate it automatically.</p>
<p><strong>Main / Host App (shell):</strong></p>
<pre><code class="lang-apache"><span class="hljs-attribute">host</span>-app/                     
├── <span class="hljs-attribute">public</span>/
│   └── <span class="hljs-attribute">index</span>.html        ← Mount point
├── <span class="hljs-attribute">src</span>/
│   ├── <span class="hljs-attribute">App</span>.jsx           ← Mounts ProductTile from remote
│   └── <span class="hljs-attribute">bootstrap</span>.js      ← App entry point
├── <span class="hljs-attribute">webpack</span>.config.js     ← Loads remotes via Module Federation
└── <span class="hljs-attribute">package</span>.json
</code></pre>
<p>You can keep them both in a monorepo or host them in entirely different repos.</p>
<p>🔹 <strong>Step #0: Initiate projects (Host + Product Apps)</strong></p>
<p>If you know how to do it, you can set up two separate React applications yourself for the Host App and one for the Remote (Product App), or initialize them in this way:</p>
<pre><code class="lang-bash">npm init
npm install react react-dom
</code></pre>
<p><strong>🔹</strong> <strong>Step #1: Install Webpack 5 + dependencies (Host + Product Apps)</strong></p>
<p>Before you do anything federation-related, both the host and remote apps must be set up with Webpack 5 and its plugins. Go ahead and run this in both projects:</p>
<pre><code class="lang-bash">npm install webpack webpack-cli webpack-dev-server html-webpack-plugin --save-dev
</code></pre>
<p>A few notes about these packages:</p>
<ul>
<li><p><code>webpack + webpack-cli</code> — Core bundler and CLI</p>
</li>
<li><p><code>webpack-dev-server</code> — Local server for hot reload + module exposure</p>
</li>
<li><p><code>html-webpack-plugin</code> — Automatically injects your bundles into HTML</p>
</li>
<li><p>Optional but common: You can add <code>Babel</code>, <code>React preset</code>, <code>loaders</code>, and so on, for <code>JSX</code>/<code>TSX</code> support later.</p>
</li>
</ul>
<p>This setup gives you a foundation. From here, you can add module federation to connect apps together.</p>
<p><strong>🔹</strong> <strong>Step #2: Create the Remote App (Product App)</strong></p>
<p>Let’s start with the remote app, the one exposing a React component to be consumed by others.</p>
<p>Here’s a simple <code>ProductTile</code> React component (of course, you can implement yours):</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/src/ProductTile.jsx</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ProductTile</span>(<span class="hljs-params">{ title }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">border:</span> '<span class="hljs-attr">1px</span> <span class="hljs-attr">solid</span> #<span class="hljs-attr">aaa</span>', <span class="hljs-attr">padding:</span> '<span class="hljs-attr">1rem</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>🛍 {title}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>A <code>ProductTile</code> component supplies a prop – <code>“title”</code> – and renders it.</p>
<p>Now let’s expose this component to other apps, not just render it locally.</p>
<p><strong>🔹</strong> <strong>Step #3: Configure Webpack in the Remote App (Product App)</strong></p>
<p>This will be done utilizing module federation, which you must enable in <code>webpack.config.js</code> file. Here’s how it can be done. At the very top of the file, you will need to import these packages:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/webpack.config.js</span>

<span class="hljs-keyword">const</span> HtmlWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'html-webpack-plugin'</span>);
<span class="hljs-keyword">const</span> ModuleFederationPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>).container.ModuleFederationPlugin;
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
</code></pre>
<ul>
<li><p><code>HtmlWebpackPlugin</code> – Handles HTML generation and script injection.</p>
</li>
<li><p><code>ModuleFederationPlugin</code> – The core Webpack plugin that lets you expose and consume modules at runtime</p>
</li>
</ul>
<p>Then, define the actual config in <code>module.exports</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// product-app/webpack.config.js</span>

<span class="hljs-keyword">const</span> HtmlWebpackPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'html-webpack-plugin'</span>);
<span class="hljs-keyword">const</span> ModuleFederationPlugin = <span class="hljs-built_in">require</span>(<span class="hljs-string">'webpack'</span>).container.ModuleFederationPlugin;
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">entry</span>: <span class="hljs-string">'./src/index.js'</span>,                         <span class="hljs-comment">// Entry file to the product app</span>
  <span class="hljs-attr">mode</span>: <span class="hljs-string">'development'</span>,                             <span class="hljs-comment">// Must be production if you go live</span>
  <span class="hljs-attr">devServer</span>: {
    <span class="hljs-attr">port</span>: <span class="hljs-number">3001</span>                                     <span class="hljs-comment">// Product app runs on this port</span>
  },
  <span class="hljs-attr">output</span>: {
    <span class="hljs-attr">publicPath</span>: <span class="hljs-string">'auto'</span>,                            <span class="hljs-comment">// Required for dynamic federation</span>
  },
  <span class="hljs-attr">plugins</span>: [
    <span class="hljs-keyword">new</span> ModuleFederationPlugin({
      <span class="hljs-attr">name</span>: <span class="hljs-string">'productApp'</span>,                         <span class="hljs-comment">// Internal name of the remote app</span>
      <span class="hljs-attr">filename</span>: <span class="hljs-string">'remoteEntry.js'</span>,                 <span class="hljs-comment">// Entry file others will load</span>
      <span class="hljs-attr">exposes</span>: {
        <span class="hljs-string">'./ProductTile'</span>: <span class="hljs-string">'./src/ProductTile.jsx'</span>, <span class="hljs-comment">// Expose this module</span>
      },
      <span class="hljs-attr">shared</span>: {                                   <span class="hljs-comment">// Shared packages if needed</span>
        <span class="hljs-attr">react</span>: { <span class="hljs-attr">singleton</span>: <span class="hljs-literal">true</span> },
        <span class="hljs-string">'react-dom'</span>: { <span class="hljs-attr">singleton</span>: <span class="hljs-literal">true</span> },
      },
    }),
    <span class="hljs-keyword">new</span> HtmlWebpackPlugin({
      <span class="hljs-attr">template</span>: <span class="hljs-string">'./public/index.html'</span>,
    }),
  ],
};
</code></pre>
<p>Now it’s time to use the product app in the main/host app:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// host-app/src/App.jsx</span>

<span class="hljs-keyword">import</span> React, { Suspense } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-comment">// Dynamically import ProductTile from the remote</span>
<span class="hljs-keyword">const</span> RemoteProductTile = React.lazy(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'productApp/ProductTile'</span>));

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">padding:</span> '<span class="hljs-attr">2rem</span>' }}&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>📦 Host App<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">Suspense</span> <span class="hljs-attr">fallback</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">div</span>&gt;</span>Loading product tile...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>}&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">RemoteProductTile</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Bluetooth Speaker"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">Suspense</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>In React, you can use the <code>React.lazy()</code> function to dynamically import the federated module. It returns a promise that React renders as soon as it’s ready.</p>
<p>That’s it. There’s nothing related to the module federation in the <code>bootstrap.js</code> and <code>index.html</code> files, but regular setup, so you can put whatever you want there:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// host-app/src/bootstrap.js</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;

<span class="hljs-keyword">const</span> root = createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>));
root.render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span></span>);
</code></pre>
<pre><code class="lang-xml"><span class="hljs-comment">&lt;!-- host-app/public/index.html --&gt;</span>

<span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Host App<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"root"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>And lastly, you can launch the host app:</p>
<pre><code class="lang-bash">npx webpack serve
</code></pre>
<p>That’s it!</p>
<p>Here are a few advantages and limitations of Module Federation, along with popular use cases.</p>
<h3 id="heading-pros-3"><strong>✅ Pros</strong></h3>
<ul>
<li><p><strong>Runtime Integration</strong> – Import remote components after both apps are built</p>
</li>
<li><p><strong>Independent Deployment</strong> – Teams can ship apps on separate pipelines</p>
</li>
<li><p><strong>Code Sharing</strong> – Share common libraries (React, lodash) to reduce duplication</p>
</li>
<li><p><strong>No iframes or wrappers</strong> – Native component integration, not isolated like Web Components</p>
</li>
<li><p><strong>No import maps needed</strong> – Webpack handles all the resolution logic</p>
</li>
<li><p><strong>Works across frameworks –</strong> Can be used in React, Angular, Vue, even Web Components</p>
</li>
</ul>
<h3 id="heading-cons-3"><strong>❌ Cons</strong></h3>
<ul>
<li><p><strong>Tied to Webpack</strong> – <strong>Federation</strong> is Webpack-specific (Vite/Rollup alternatives exist but are not native)</p>
</li>
<li><p><strong>Initial setup is complicated</strong> – Requires per-app Webpack configuration and shared dependency coordination</p>
</li>
<li><p><strong>Runtime failures are possible –</strong> If the remote is down, the host may break unless you handle fallbacks</p>
</li>
<li><p><strong>Version mismatch risks</strong> – Shared libs (like React) must be tightly versioned and aligned</p>
</li>
<li><p><strong>No automatic SSR</strong> – Requires custom hydration logic for federated components</p>
</li>
</ul>
<h3 id="heading-popular-use-cases-3"><strong>👨🏻‍💻 Popular Use Cases</strong></h3>
<p>Use <strong>Module Federation</strong> when:</p>
<ul>
<li><p>You want to build a platform composed of independently deployed apps</p>
</li>
<li><p>You need runtime module loading (not just widgets)</p>
</li>
<li><p>You want to share design systems or UI libraries across apps</p>
</li>
<li><p>Your team is federating complex app sections, not just components</p>
</li>
<li><p>You want to avoid loading dependencies multiple times across apps</p>
</li>
</ul>
<h2 id="heading-other-tools-amp-ecosystem-additions"><strong>Other Tools &amp; Ecosystem Additions</strong></h2>
<p>While iframes, Web Components, single-spa, and Module Federation are the major players in the micro-frontend arena, there’s a growing ecosystem of alternative tools and strategies. They don’t always serve as full micro-frontend methods, but still solve important pieces of the puzzle. Let’s walk through some of the less prominent, yet practical solutions that are worth your attention.</p>
<h3 id="heading-import-maps-native-es-modules"><strong>Import Maps + Native ES Modules</strong></h3>
<p><a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap">Import Maps</a> allow you to define where modules are loaded from, directly in the browser. Combined with native ES module support, they enable zero-build micro frontend setups.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"importmap"</span>&gt;</span><span class="javascript">
{
  <span class="hljs-string">"imports"</span>: {
    <span class="hljs-string">"ui-library/"</span>: <span class="hljs-string">"https://cdn.example.com/ui/v1.2.3/"</span>,
    <span class="hljs-string">"square"</span>: <span class="hljs-string">"./modules/shapes/square.js"</span>
  }
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>You might’ve noticed that it looks similar to what single-spa + <code>SystemJS</code> does.</p>
<p><strong>Use it when</strong>:</p>
<ul>
<li><p>You want to dynamically load shared libraries (like design systems)</p>
</li>
<li><p>You’re building federated apps without bundlers</p>
</li>
<li><p>You’re targeting modern browsers only</p>
</li>
</ul>
<h3 id="heading-piral-micro-frontends-as-pluggable-portals"><strong>Piral: Micro Frontends as Pluggable Portals</strong></h3>
<p><a target="_blank" href="https://piral.io/">Piral</a> is a specialized framework for building portal-based micro frontends. It provides a structured environment where micro apps (called pilets) can be plugged into a central shell (the Piral instance).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748797958786/125cdd57-0d2d-4d23-a320-028b081ee989.png" alt="125cdd57-0d2d-4d23-a320-028b081ee989" class="image--center mx-auto" width="3008" height="1068" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://piral.io/">https://piral.io/</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Routing</p>
</li>
<li><p>Layout orchestration</p>
</li>
<li><p>Shared state</p>
</li>
<li><p>Module loading</p>
</li>
<li><p>Authentication hooks</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li><p>Enterprise-scale portals</p>
</li>
<li><p>Apps with lots of features teams</p>
</li>
<li><p>Admin dashboards or CMS-heavy UIs</p>
</li>
</ul>
<h3 id="heading-luigi-micro-frontends-sap-style-shells"><strong>Luigi: Micro Frontends + SAP-style Shells</strong></h3>
<p><a target="_blank" href="https://luigi-project.io/">Luigi</a> is a microfrontend framework built by SAP to enable consistent layout shells with side navigation, top bars, permissions, and more.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798177808/16380085-a4fc-4cc9-9fe2-b44821f9feef.png" alt="16380085-a4fc-4cc9-9fe2-b44821f9feef" class="image--center mx-auto" width="3010" height="1472" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://luigi-project.io/">https://luigi-project.io/</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Config-driven app registration</p>
</li>
<li><p>Automatic route activation</p>
</li>
<li><p>Role-based access control (RBAC)</p>
</li>
<li><p>Seamless iframe integration with a shell</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li><p>Intranet tools</p>
</li>
<li><p>Cloud admin panels</p>
</li>
<li><p>Productized dashboards</p>
</li>
</ul>
<h3 id="heading-open-components"><strong>Open Components</strong></h3>
<p><a target="_blank" href="https://github.com/opencomponents/oc">OpenComponents</a> is a framework-agnostic way to build self-contained microservices with UI logic, registered to a central registry.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798238923/6406ef71-4dde-47bc-8d2b-9476593afdd5.png" alt="6406ef71-4dde-47bc-8d2b-9476593afdd5" class="image--center mx-auto" width="3022" height="1204" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://github.com/opencomponents/oc">https://github.com/opencomponents/oc</a></p>
</blockquote>
<p><strong>This framework comes with built-in:</strong></p>
<ul>
<li><p>Server-rendered or client-rendered</p>
</li>
<li><p>REST-like model for UI consumption</p>
</li>
<li><p>Great CDN + registry story</p>
</li>
</ul>
<p><strong>Great for:</strong></p>
<ul>
<li>Used when your company treats UI as deployable microservices, just like APIs.</li>
</ul>
<h3 id="heading-bit-meet-a-composable-architecture">Bit: Meet a composable architecture</h3>
<p><a target="_blank" href="https://bit.dev/">Bit</a> isn’t a micro frontend framework per se, but a component-driven development and distribution platform. It organizes source code into composable components, empowering to build reliable, scalable applications in the era of AI.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1748798402542/9fdf7de4-cc1d-41b5-9709-be824c8ffe41.png" alt="9fdf7de4-cc1d-41b5-9709-be824c8ffe41" class="image--center mx-auto" width="3024" height="1126" loading="lazy"></p>
<blockquote>
<p><em>Source:</em> <a target="_blank" href="https://bit.dev/">https://bit.dev</a></p>
</blockquote>
<p>Use it alongside Web Components or Module Federation to supercharge reuse. If you want to practice, they have an <a target="_blank" href="https://bit.dev/blog/mastering-micro-frontends-with-module-federation-and-bit-ljn4ruah/">Official Guide</a> on how to master Micro-Frontends with Module Federation.</p>
<p>It’s a great addition when:</p>
<ul>
<li><p>You want to publish reusable components across teams</p>
</li>
<li><p>You need to manage versions, ownership, and discovery</p>
</li>
<li><p>You’re aiming for component-first delivery, not app-first</p>
</li>
</ul>
<h2 id="heading-final-thoughts"><strong>Final Thoughts</strong></h2>
<p>Micro frontends offer immense power, but that power comes with architectural responsibility.</p>
<p>Each method we explored solves a different kind of problem:</p>
<ul>
<li><p>IFrames are secure, but come with complex communication and high isolation.</p>
</li>
<li><p>Web Components are native, framework-agnostic, dependency-free, and perfect for reusable UI Kits</p>
</li>
<li><p>single-spa shines when you need orchestration and multiple SPAs under one shell.</p>
</li>
<li><p>Module Federation is the go-to for runtime code sharing and independent deployment.</p>
</li>
<li><p>And tools like Import Maps, Piral, Luigi, and others fill in the gaps, each in their own way.</p>
</li>
</ul>
<p>There’s no one-size-fits-all solution here, but with the right match for your team structure and product strategy, you can build apps that scale across teams, tech stacks, and time.</p>
<hr>
<p>If you liked this guide, feel free to repost and share it with your friends, colleagues, and social network.</p>
<p>If you want to take your micro-frontend skills to a new level, especially around Web Components, I invite you to check out my best-selling Udemy course called <a target="_blank" href="https://www.udemy.com/course/web-components-api/?couponCode=HERO_START">“Web Components: The Ultimate Guide from Zero to Hero“</a>.</p>
<p>And of course, if you have questions, feedback, or need help with your micro frontend setup, feel free to reach out to me on my social media such as <a target="_blank" href="https://www.linkedin.com/in/andrewmaksimchenko/">LinkedIn</a> / <a target="_blank" href="https://x.com/avmax19">X</a> / <a target="_blank" href="https://t.me/codelikeandrew">Telegram</a>. I’m always happy to chat, connect, and help other devs build amazing things! 💚</p>
<p>Let’s build the IT future we could be proud of! 💪🏼 Thanks for reading — and happy decoupling! 🚀</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn Software Design Basics: Key Phases and Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ Coding has become one of the most common tasks in modern society. With computers now central to almost every field, more people are designing algorithms and writing code to solve various problems. From healthcare to finance, robust software systems p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-software-design-basics/</link>
                <guid isPermaLink="false">67cb6446f54b40e1e9144db0</guid>
                
                    <category>
                        <![CDATA[ software design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TDD (Test-driven development) ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Coding Best Practices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Software Engineering ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Programming Blogs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Developer ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Soham Banerjee ]]>
                </dc:creator>
                <pubDate>Fri, 07 Mar 2025 21:25:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741188275855/9858518f-38c0-4e3b-8be1-7c56b68c77a7.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Coding has become one of the most common tasks in modern society. With computers now central to almost every field, more people are designing algorithms and writing code to solve various problems.</p>
<p>From healthcare to finance, robust software systems power our daily operations, making good software design essential to avoid inefficiencies and bottlenecks. This involves not just writing code but also designing systems that are easy to scale, maintain, and debug, while allowing others to contribute effectively.</p>
<p>Inefficient or ineffective software design can lead to significant issues, like scope creep, miscommunication within teams, project delays, resource misallocation, and complex systems that are difficult to maintain or understand. Without a strong design, teams often accumulate technical debt, which hinders long-term progress and increases maintenance costs.</p>
<p>This article will introduce you to key software design elements that will help you and your team address these challenges and guide you in building efficient, scalable systems. By understanding and applying these elements correctly, you can set up a project for both short-term and long-term success.</p>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>I’ll explain these concepts through examples, but a basic understanding of programming in any language is required for this article (knowledge of Python will be especially beneficial).</p>
<h2 id="heading-scope"><strong>Scope</strong></h2>
<p>The article will introduce key software design elements and explain them using an example. While I won’t provide a full software design for the example problem, I will include enough details to effectively illustrate each design element.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-overview-of-key-software-design-elements">Overview of Key Software Design Elements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-walkthrough-of-the-software-design-process">A Walkthrough of the Software Design Process</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-problem-statement">Problem Statement</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-cases">Use Cases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-requirements">Requirements</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-high-level-system-architecture">High Level System Architecture</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-detailed-software-design-and-component-breakdown">Detailed Software Design and Component Breakdown</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion-the-value-of-thoughtful-software-design">Conclusion: The Value of Thoughtful Software Design</a></p>
</li>
</ul>
<h2 id="heading-overview-of-key-software-design-elements"><strong>Overview of Key Software Design Elements</strong></h2>
<p>To fully understand the benefits of the software design process, you’ll need to understand some key elements and their scope.</p>
<p>Once you have a good grasp of these, the next step is to define them for the specific problem at hand. Accurately defining these elements reduces risks and simplifies the implementation phase.</p>
<p>Doing this groundwork before implementation helps prevent late discoveries, minimizes the need for rewriting, and makes sure that the design can handle constraints and corner cases.</p>
<p>Now let’s briefly go over the key elements of the software design process:</p>
<ol>
<li><p><strong>Creating a problem statement</strong>: This step involves creating a clear and concise description of the problem that needs to be solved, along with its scope. The scope is essential because it focuses on the exact problem to be addressed and includes assumptions that must be considered during design.</p>
</li>
<li><p><strong>Identifying use cases</strong>: This step outlines all possible user interactions with the software to achieve the desired outcome. It is a critical input to the architecture, as it helps create a design that addresses both general and edge-case use cases.</p>
</li>
<li><p><strong>Stating requirements</strong>: This step defines the expectations of the software, such as its limitations, behaviors, and capabilities for different use cases.</p>
</li>
<li><p><strong>Designing the architecture</strong>: This step provides a high-level structure of the software design, focusing on how to meet the requirements. The architecture typically includes components, how they interact, and how data flows through the system.</p>
</li>
<li><p><strong>Drafting a detailed design</strong>: This step refines the high-level architecture into detailed, component-specific designs, ready for implementation.</p>
</li>
</ol>
<p>In addition to these core elements, there are two important factors you need to consider throughout the design phase.</p>
<p>First, you’ll need to identify and state any assumptions you have. Assumptions can be present at any stage in the design process. Making correct assumptions increases the likelihood of success, improves focus, and reduces complexity in the design.</p>
<p>Second, you’ll need to create good documentation. Documentation is one of the most important elements in the software design process. It’s essential to document each stage as you go along. Documentation serves as the only formal record of the software design and is invaluable for presentations to management, for onboarding new team members, and for anyone returning to the project after a break. It saves valuable time and ensures continuity, as we often overestimate our own memory.</p>
<p>The figure below provides a visual summary of the key software design elements discussed in this section.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738540359869/2ee49614-84b1-439a-ae7e-af637c0f34dd.png?auto=compress,format&amp;format=webp" alt="Figure 1: Key software design elements" width="2244" height="402" loading="lazy"></p>
<p>Next, we’ll apply these key software design elements to a practical example, demonstrating how each element contributes to building a robust and scalable system.</p>
<h2 id="heading-a-walkthrough-of-the-software-design-process"><strong>A Walkthrough of the Software Design Process</strong></h2>
<p>In any well-structured software project, clearly defining the problem is the first crucial step before diving into design and implementation. A well-defined problem ensures that the software meets user needs, remains maintainable, and scales effectively over time.</p>
<p>For this walkthrough, we will focus on designing a financial expense categorization system that processes and analyzes transaction data. This system is a part of a larger financial management solution and needs to be easy to debug, maintain, and scale.</p>
<h3 id="heading-problem-statement"><strong>Problem Statement</strong></h3>
<p>The problem statement provides a high-level goal for the software that we’ll design.</p>
<p>For this example, here’s our statement: Design a software solution that categorizes monthly expenses and generates a report from a list of transactions.</p>
<h4 id="heading-define-the-scope"><strong>Define the scope</strong></h4>
<p>Defining the scope clarifies the smaller tasks that must be accomplished to meet the high-level goal. It outlines the focus of the software design and includes some assumptions.</p>
<p>Includes:</p>
<ol>
<li><p>Implementing a parser to process a list of transactions provided as input.</p>
</li>
<li><p>Filtering transactions for a given month.</p>
</li>
<li><p>Analyzing, categorizing, and generating a report for each expense category.</p>
</li>
</ol>
<p>Excludes:</p>
<p>Performance and memory optimization (excluded due to the limited scope of this article). While performance and memory optimizations are not the primary focus here, it’s important to keep future scalability in mind. Small design choices made now, such as selecting data structures, can help avoid significant refactoring later when the system grows.</p>
<p>Assumptions:</p>
<ol>
<li><p>The list of transactions will be provided as a CSV file in the following format:<br> Columns: "Date, Description, Amount, Type, Category Label".</p>
</li>
<li><p>Expense categories will be provided as input through a JSON file.</p>
</li>
<li><p>The software will run in a shell environment, and inputs will be taken as command-line arguments.</p>
</li>
</ol>
<p>Now that the scope is clear, let’s examine how users will interact with the system through various use cases.</p>
<h3 id="heading-use-cases"><strong>Use Cases</strong></h3>
<p>Use cases define how users will interact with the system to accomplish specific goals. Identifying accurate and valid use cases is critical to creating comprehensive requirements. Failing to capture enough use cases can lead to a design that is incomplete and lacks robustness. This may result in the need for redesigns, which increases time and resource consumption.</p>
<p>On the other hand, identifying too many use cases without considering their feasibility can lead to overly complex designs that are difficult to maintain and implement in the short term.</p>
<p>For our specific problem, the user will need to provide the following inputs while running the software in a shell:</p>
<ol>
<li><p>A CSV file containing a list of transactions.</p>
</li>
<li><p>A month number.</p>
</li>
<li><p>A JSON file containing expense categories.</p>
</li>
</ol>
<p>We need to consider all possible ways the user can interact with the script to achieve the desired outcome. For each of the three inputs, there are two possibilities: valid input or invalid input. This gives us 8 potential use cases (2 possibilities per input: valid and invalid). It's important to define what constitutes valid and invalid inputs for this problem:</p>
<ul>
<li><p>CSV File: Valid if it is in the format described in Assumption 1 (columns: "Date, Description, Amount, Type, Category Label").</p>
</li>
<li><p>Month Number: Valid if the value is between 1 and 12.</p>
</li>
<li><p>JSON File: Valid if it contains expense categories in the correct JSON format.</p>
</li>
</ul>
<p>An input is invalid if it doesn't meet these definitions or if the input is absent.</p>
<p>It’s also crucial to consider the correlation between inputs when evaluating the feasibility of certain use cases, as they may interact with each other in unforeseen ways. Based on these use cases, we can now define the specific requirements that the system must meet.</p>
<h3 id="heading-requirements"><strong>Requirements</strong></h3>
<p>Now, let’s define the expected behaviors, limitations, and capabilities for each use case. Requirements serve as the foundation for architecture, specifications, and implementation. Based on our problem statement, the software will need to accomplish the following tasks:</p>
<ol>
<li><p>The script shall take three inputs: a CSV file of transactions, a month number, and a JSON file of expense categories.</p>
</li>
<li><p>The script shall verify all inputs.</p>
</li>
<li><p>The script shall throw an error and exit if the CSV file cannot be opened or if it does not match the format in Assumption 1.</p>
</li>
<li><p>The script shall throw an error and exit if the JSON file cannot be opened.</p>
</li>
<li><p>The script shall throw an error if the month number is not between 1 and 12.</p>
</li>
<li><p>The script shall parse each transaction and load it into a data structure.</p>
</li>
<li><p>The script shall filter transactions by the specified month.</p>
</li>
<li><p>The script shall load the expense categories from the JSON file into a data structure.</p>
</li>
<li><p>The script shall categorize transactions based on the category label provided in the CSV file.</p>
</li>
<li><p>The script shall throw an exception if a category label in the CSV file is not present in the expense categories.</p>
</li>
<li><p>The script shall use a categorizing function to assign transactions to categories from the JSON file.</p>
</li>
<li><p>A class shall encapsulate categorized transactions, providing APIs to modify or access them.</p>
</li>
<li><p>The script shall support statistics calculation and report generation for categorized transactions.</p>
</li>
</ol>
<p>With the requirements in place, we can now design a high-level architecture to meet those needs.</p>
<h3 id="heading-high-level-system-architecture"><strong>High Level System Architecture</strong></h3>
<p>In this stage, we will design the system at a high level, much like creating a master plan. Architecture involves organizing the software's functions into distinct components, illustrating how they interact, and mapping the flow of control and data through the system. While designing the architecture in this tutorial, we’ll incorporate good design principles.</p>
<p>For this example, the high-level requirements include:</p>
<ol>
<li><p>Loading inputs and verifying them.</p>
</li>
<li><p>Applying time-based filtering.</p>
</li>
<li><p>Categorizing transactions based on category labels and descriptions.</p>
</li>
<li><p>Managing categorized transactions in a finance registry.</p>
</li>
<li><p>Generating reports from the categorized data.</p>
</li>
</ol>
<p>One important component of software architecture is telemetry. Telemetry gathers data on the software's behavior, which is invaluable for debugging and performance assessment in real-world environments.</p>
<p>For smaller systems, simpler logging mechanisms may be sufficient to track basic errors and monitor performance. The decision to implement telemetry should depend on the complexity of the system and operational requirements.</p>
<p>Since telemetry provides such a helpful feedback loop for improving the design in future iterations, we’ll add it to the list of components here.</p>
<p>We’ll build our system architecture around a Test-Driven Development (TDD) approach. We’ll design each component with testing in mind to ensure it meets our requirements.</p>
<p>Just keep in mind that while TDD is a strong practice for ensuring code quality, it may not be the best fit for all projects. In scenarios where you need rapid prototyping or exploratory development, testing might be prioritized after initial iterations. Balancing between TDD and other methodologies depends on the project context and team preferences.</p>
<p>Our architecture will follow a modular structure, meaning the system will be divided into self-contained components. Each component will be responsible for specific functionality, making the system easier to test, maintain, and scale.</p>
<p>To achieve this, the architecture will emphasize loose coupling between components. Each component will interact with others through well-defined interfaces or APIs, ensuring minimal dependencies. We’ll abstract and encapsulate internal implementation details, exposing only the necessary information for interaction. Also, each component will handle its own errors and exceptions to ensure robustness and fault isolation.</p>
<p>But it is also important to consider a centralized error-handling strategy in some cases. Centralizing error handling can reduce redundancy, improve consistency, and make maintenance easier. The choice between local and centralized error handling should depend on the system's complexity and how components interact. This will contribute to the overall scalability and maintainability of the system.</p>
<p>Below is a summary of each component's functionality in this architecture:</p>
<ul>
<li><p>Load and verify input: This component will take the CSV file, JSON file, and month number as input, verify their validity, and load the data into structures.</p>
</li>
<li><p>Time-based filter: This component will filter transactions based on the input month and store the filtered transactions in a data structure.</p>
</li>
<li><p>Label-based categorization: This component will categorize transactions based on the category label in the CSV file.</p>
</li>
<li><p>Description-based categorization: This component will categorize transactions using an algorithm based on the transaction description.</p>
</li>
<li><p>Finance registry: This component will store all categorized transactions for further processing. It isolates the post-processing of categorized transactions from the categorization process and provides methods for updating or retrieving datasets.</p>
</li>
<li><p>Report generation: This component will generate expense reports from the categorized transaction data.</p>
</li>
<li><p>Telemetry: This component will monitor the performance of other components. It will track the flow of transactions, ensuring that all transactions are categorized either by label or description. Additional parameters can be added as needed to monitor specific functionalities.</p>
</li>
</ul>
<p>The diagram below demonstrates the flow of data through these components:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738540585066/6236b867-8c57-4a04-b5ea-4f9dd7f1fef3.png?auto=compress,format&amp;format=webp" alt="Figure 2: Flow of data through various components defined in the architecture" width="2360" height="794" loading="lazy"></p>
<h3 id="heading-detailed-software-design-and-component-breakdown"><strong>Detailed Software Design and Component Breakdown</strong></h3>
<p>While we won't cover the full system design, this section will highlight key components and their specifications. For this example, I will assume the role of both the designer and implementer of the software.</p>
<p>Software design and specifications depend on several factors, including the designer's knowledge, skill set, available time, and resources. We’ll define some of the design details for the system, starting with the choice of the implementation language.</p>
<p>Choosing the right language is based on several important factors:</p>
<ol>
<li><p>The language must meet the software requirements.</p>
</li>
<li><p>It should be stable, and have strong support from an active developer community.</p>
</li>
<li><p>Additional considerations include performance (speed and memory), scalability (ability to grow with future requirements), and platform support (ability to run on all major operating systems).</p>
</li>
</ol>
<p>If you’re the one implementing this design, you’ll need to be familiar with and confident using that programming language. For this project, I chose Python because it meets all the project requirements, has a robust developer community for support, it’s stable, and I’m confident in using it to complete the implementation successfully.</p>
<h4 id="heading-data-structures"><strong>Data Structures</strong></h4>
<p>Now, let’s look at the fundamental data structures that we’ll use in the design. We need to load the contents of the CSV file into a data structure for further analysis and processing. In Python, the Pandas DataFrame from the Pandas library is ideal for analyzing and processing tables, so we will use it to store the transactions.</p>
<p>For generating report, we will encapsulate categorized transactions along with relevant statistics, such as the total number of transactions, mean amount, and maximum amount, within a dedicated dataset class. This approach ensures a clear separation of concerns, where the dataset class manages data processing, while the reporting component focuses on presentation.</p>
<p>By structuring the system this way, we enhance reusability, maintainability, and scalability, making it easier to extend and modify in the future.</p>
<p>This dataset class will include:</p>
<ul>
<li><p>Member variables: category name, category description, a Pandas DataFrame for transactions, total number of transactions, mean amount, and max amount of transactions.</p>
</li>
<li><p>Member functions: set/get DataFrame, save dataset to CSV (useful for debugging).</p>
</li>
</ul>
<p>Here’s an example of a Dataset class in Python for structured data management and processing:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd  <span class="hljs-comment"># Import Pandas for data handling</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Dataset</span>:</span>
    <span class="hljs-string">"""
    A class representing a structured dataset with a name, predefined keys, 
    and a Pandas DataFrame.
    """</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, name, keys</span>):</span>
        <span class="hljs-string">"""
        Initializes the Dataset object.

        Parameters:
        name (str): The name of the dataset.
        keys (list): A list of expected column names for the dataset.

        Attributes:
        self.name (str): Stores the dataset name as a string.
        self.keys (list): Stores the expected column names for data organization.
        self.mean_amt (float): Tracks the mean (average) transaction amount.
        self.max_amt (float): Tracks the maximum transaction amount.
        self.count (int): Stores the total number of transactions in the dataset.
        self.dataframe (pd.DataFrame): A Pandas DataFrame initialized with the specified column names.
        """</span>
        self.name = str(name)  <span class="hljs-comment"># Convert and store dataset name as a string</span>
        self.keys = keys  <span class="hljs-comment"># Store expected column names for consistency</span>
        self.mean_amt = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize mean transaction amount to zero</span>
        self.max_amt = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize max transaction amount to zero</span>
        self.count = <span class="hljs-number">0</span>  <span class="hljs-comment"># Initialize transaction count to zero</span>
        self.dataframe = pd.DataFrame(columns=keys)  <span class="hljs-comment"># Initialize empty DataFrame with predefined columns</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getName</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the name of the dataset.

        Returns:
        str: The name of the dataset.
        """</span>
        <span class="hljs-keyword">return</span> self.name  <span class="hljs-comment"># Fixed: Removed incorrect parentheses</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getValue</span>(<span class="hljs-params">self, key</span>):</span>
        <span class="hljs-string">"""
        Retrieves a specific column from the DataFrame.

        Parameters:
        key (str): The column name to retrieve.

        Returns:
        pandas.Series or None: The column data if the key exists, otherwise None.
        """</span>
        <span class="hljs-keyword">if</span> key <span class="hljs-keyword">in</span> self.dataframe.columns:
            <span class="hljs-keyword">return</span> self.dataframe[key]
        <span class="hljs-keyword">else</span>:
            print(<span class="hljs-string">f"Warning: Key '<span class="hljs-subst">{key}</span>' not found in DataFrame."</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">None</span>  <span class="hljs-comment"># Prevents KeyError</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getKeys</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the list of expected keys (column names) of the dataset.

        Returns:
        list: The keys defining the dataset.
        """</span>
        <span class="hljs-keyword">return</span> self.keys

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setDataFrame</span>(<span class="hljs-params">self, dataframe</span>):</span>
        <span class="hljs-string">"""
        Sets the dataset's DataFrame while ensuring it contains only expected keys.

        Parameters:
        dataframe (pandas.DataFrame): The DataFrame to assign to the dataset.
        """</span>
        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> isinstance(dataframe, pd.DataFrame):
            <span class="hljs-keyword">raise</span> TypeError(<span class="hljs-string">"Provided data is not a valid pandas DataFrame."</span>)

        <span class="hljs-comment"># Ensure only the expected columns are included</span>
        self.dataframe = dataframe[self.keys].copy() <span class="hljs-keyword">if</span> set(self.keys).issubset(dataframe.columns) <span class="hljs-keyword">else</span> dataframe.copy()

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getDataFrame</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Returns the DataFrame associated with the dataset.

        Returns:
        pandas.DataFrame: The dataset's DataFrame.
        """</span>
        <span class="hljs-keyword">return</span> self.dataframe

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">save_to_csv</span>(<span class="hljs-params">self, file_name</span>):</span>
        <span class="hljs-string">"""
        Saves the dataset's DataFrame to a CSV file.

        Parameters:
        file_name (str): The name of the CSV file to save.
        """</span>
        self.dataframe.to_csv(file_name, mode=<span class="hljs-string">'w'</span>, index=<span class="hljs-literal">False</span>)  <span class="hljs-comment"># Save the DataFrame to CSV</span>
</code></pre>
<p>In the previous section, we outlined the high-level system architecture, detailing the core components and their interactions. Now, let’s dive into the detailed design of some of the individual components, specifying how we’ll implement each one and how it’ll function within the system. We’ll also break down the components to explain how they work together to process the input and generate the report.</p>
<p>Below, you can see the flow diagram for the software, illustrating the interaction between the core components and the flow of data through the system.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739209441033/60142953-c1f4-4146-b64e-c042039e1ef6.png?auto=compress,format&amp;format=webp" alt="Figure 3 Software Flow Diagram" width="1940" height="1128" loading="lazy"></p>
<h4 id="heading-category-label-based-filtering-component"><strong>Category Label-Based Filtering Component</strong></h4>
<p>The Category Label-Based Filtering Component classifies transactions by matching their "Category Label" with predefined expense categories from a JSON file. Transactions with valid category labels are stored in the finance registry, while unmatched ones remain for further processing.</p>
<ul>
<li><p>Input: DataFrame of time-filtered transactions, expense categories from JSON.</p>
</li>
<li><p>Libraries used: Pandas DataFrame.</p>
</li>
<li><p>Software design: Filters transactions based on the "Category Label" column and assigns them to corresponding categories. Transactions that cannot be categorized remain for further processing.</p>
</li>
<li><p>Output: DataFrame of remaining transactions with empty values in the "Category Label" field.</p>
</li>
<li><p>Component tests: Validate handling of valid, invalid, and missing category labels.</p>
</li>
</ul>
<h4 id="heading-finance-registry-component"><strong>Finance Registry Component</strong></h4>
<p>The Finance Registry Component manages categorized transactions by storing them as datasets for each expense category. It maintains a structured collection of DataFrames, each containing transactions and summary statistics such as total count, max amount, and mean amount.</p>
<ul>
<li><p>Input: Expense categories from JSON.</p>
</li>
<li><p>Libraries used: Pandas DataFrame.</p>
</li>
<li><p>Software design: Implements a class that organizes datasets for all expense categories, providing methods to set and retrieve DataFrames.</p>
</li>
<li><p>Component tests: Validate dataset creation, ensuring correct storage and retrieval of categorized transactions.</p>
</li>
</ul>
<p>Here’s a simple and efficient Finance Registry implementation in Python for managing categorized financial datasets:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> Dataset <span class="hljs-keyword">import</span> Dataset
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd  <span class="hljs-comment"># Ensure Pandas is imported if used elsewhere</span>

<span class="hljs-comment"># Define column structure for datasets</span>
KEYS = (<span class="hljs-string">"Date"</span>, <span class="hljs-string">"Description"</span>, <span class="hljs-string">"Amount"</span>, <span class="hljs-string">"Transaction Type"</span>, <span class="hljs-string">"Category"</span>, <span class="hljs-string">"Account Name"</span>, <span class="hljs-string">"Labels"</span>, <span class="hljs-string">"Notes"</span>)

<span class="hljs-comment"># Define dataset names for different financial categories</span>
EXAMPLE_DATASET_NAMES = (<span class="hljs-string">"Investment"</span>, <span class="hljs-string">"Expense"</span>, <span class="hljs-string">"Savings"</span>)

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FinanceRegistry</span>:</span>
    <span class="hljs-string">"""
    A class to manage categorized financial datasets, including investment, expense, and savings datasets.
    This registry allows structured access to transaction data and maintains aggregated financial metrics.
    """</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""
        Initializes the FinanceRegistry object.

        Attributes:
        self.example_dataset (dict): A dictionary storing Dataset objects for financial datasets.
        """</span>
        self.example_dataset = {name: Dataset(name, KEYS) <span class="hljs-keyword">for</span> name <span class="hljs-keyword">in</span> EXAMPLE_DATASET_NAMES}  <span class="hljs-comment"># Create datasets for categories</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">setExampleDatasetToRegistry</span>(<span class="hljs-params">self, name, dataframe</span>):</span>
        <span class="hljs-string">"""
        Merges a new dataframe into the existing dataset for a given financial category.

        Parameters:
        name (str): The category name (e.g., "Investment", "Expense", or "Savings").
        dataframe (pd.DataFrame): The new data to be added.

        If the dataset already contains data, it concatenates the new dataframe to the existing one.

        Raises:
        ValueError: If the provided name is not a valid dataset category.
        """</span>
        <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.example_dataset:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid dataset name: '<span class="hljs-subst">{name}</span>'. Expected one of <span class="hljs-subst">{EXAMPLE_DATASET_NAMES}</span>"</span>)

        df = self.example_dataset[name].getDataFrame()  <span class="hljs-comment"># Get existing dataset</span>

        <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> dataframe.empty:  <span class="hljs-comment"># Ensure the new dataframe is not empty</span>
            dataframe = pd.concat([df, dataframe], axis=<span class="hljs-number">0</span>, ignore_index=<span class="hljs-literal">True</span>)  <span class="hljs-comment"># Append new data</span>

        self.example_dataset[name].setDataFrame(dataframe)  <span class="hljs-comment"># Update dataset in registry</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">getExampleDatasetFromRegistry</span>(<span class="hljs-params">self, name</span>):</span>
        <span class="hljs-string">"""
        Retrieves the dataset for a given financial category.

        Parameters:
        name (str): The category name (e.g., "Investment", "Expense", or "Savings").

        Returns:
        Dataset: The dataset corresponding to the given name.

        Raises:
        ValueError: If the provided name is not a valid dataset category.
        """</span>
        <span class="hljs-keyword">if</span> name <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> self.example_dataset:
            <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">f"Invalid dataset name: '<span class="hljs-subst">{name}</span>'. Expected one of <span class="hljs-subst">{EXAMPLE_DATASET_NAMES}</span>"</span>)

        <span class="hljs-keyword">return</span> self.example_dataset[name]
</code></pre>
<p>The diagram below illustrates how the Finance Registry organizes these datasets for further processing in the Report Generation component.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739209411075/7a772e4f-9687-4c96-8995-10a70e27a36d.png?auto=compress,format&amp;format=webp" alt="Figure 4 Finance Registry datasets for expense category" width="1850" height="864" loading="lazy"></p>
<h4 id="heading-report-generation-component"><strong>Report Generation Component</strong></h4>
<p>The Report Generation Component processes categorized transaction datasets from the finance registry and generates summary statistics. It calculates key financial metrics such as maximum amount, mean amount, and total transaction count. It also provides functionality to display categorized transactions in a structured format within the shell.</p>
<ul>
<li><p>Input: Datasets of categorized transactions from the finance registry.</p>
</li>
<li><p>Libraries used: Numpy for calculations, Tabulate for formatted shell output (if needed).</p>
</li>
<li><p>Software design: Implements a class with methods to compute financial statistics and display transaction summaries per expense category.</p>
</li>
<li><p>Component tests: Validate correct calculation of mean, max, and total transactions, and ensure accurate display of categorized datasets in the shell.</p>
</li>
</ul>
<p>Here’s a function to compute transaction statistics, including mean, max, and count, from a dataset in the report generation component:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> Dataset <span class="hljs-keyword">import</span> Dataset
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculateStats</span>(<span class="hljs-params">dataset</span>):</span>
    <span class="hljs-string">"""
    Computes statistical metrics for a given dataset.

    Parameters:
    dataset: The dataset containing transaction data.

    Updates:
    - dataset.mean: Mean transaction amount.
    - dataset.max: Maximum transaction amount.
    - dataset.count: Number of transactions.
    """</span>

    <span class="hljs-comment"># Return early if the dataset has no transactions</span>
    <span class="hljs-keyword">if</span> dataset.dataframe.empty:
        <span class="hljs-keyword">return</span>

    <span class="hljs-comment"># Extract transaction amounts as a list</span>
    tx_amount_list = dataset.dataframe[<span class="hljs-string">'Amount'</span>].astype(float).round(<span class="hljs-number">2</span>).tolist()

    <span class="hljs-comment"># Adjust transaction amounts based on "Transaction Type"</span>
    <span class="hljs-keyword">for</span> i, tx_type <span class="hljs-keyword">in</span> enumerate(dataset.dataframe[<span class="hljs-string">'Transaction Type'</span>]):
        <span class="hljs-keyword">if</span> tx_type == <span class="hljs-string">'debit'</span>:
            tx_amount_list[i] *= <span class="hljs-number">-1</span>  <span class="hljs-comment"># Convert debit transactions to negative values</span>

    <span class="hljs-comment"># Compute statistical metrics</span>
    dataset.mean = round(np.mean(tx_amount_list), <span class="hljs-number">2</span>)
    dataset.max = max(tx_amount_list)
    dataset.count = len(tx_amount_list)
</code></pre>
<p>This concludes the design section, where we explored key software design elements with a practical example. The next step, implementation, is beyond the scope of this article. But it's crucial to recognize that new challenges often emerge during development, requiring updates to requirements, architecture, and specifications.</p>
<p>The purpose of this article is not to provide a full implementation, but to teach you some basic software design principles through an example. The focus is on understanding how to structure software, define clear requirements, and create scalable architectures, all before writing code.</p>
<p>By following a structured design process, you can shift complex problem-solving from implementation to the architecture phase, where you can explore solutions more effectively using flowcharts, block diagrams, and documentation. This makes the development process more organized, efficient, and maintainable, a crucial skill for real-world software engineering.</p>
<p>If you're learning to code, remember that good design is just as important as writing code itself!</p>
<h2 id="heading-conclusion-the-value-of-thoughtful-software-design"><strong>Conclusion: The Value of Thoughtful Software Design</strong></h2>
<p>With well-defined problem statements, scope, requirements, specifications, and design, even complex problems can be solved and maintained in a sustainable way.</p>
<p>The steps we went through in this article can help you break down any problem, regardless of its complexity, into smaller, actionable tasks that you and your team can efficiently tackle.</p>
<p>Without proper planning, projects are often plagued by scope creep, wasted time and resources, miscommunication between teams, overly complicated designs, technical debt, and frequent redesigns.<br>Good design is often simple design, but achieving simplicity is difficult without thorough planning.</p>
<p>Approaching each problem with the mindset of defining a Problem Statement, Scope, Use Cases, Requirements, Architecture, and Specifications helps cultivate a strong software design mindset. This mindset is crucial for developing software that is scalable, maintainable, and high quality.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Rocket Control System: Basic Control Theory with Python ]]>
                </title>
                <description>
                    <![CDATA[ Building any control systems, including a rocket control system, involves combining control theory with programming. Control theory is the study of how to make systems behave in a desired way using inputs. Planes, cars, trains, circuits, rockets and ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/basic-control-theory-with-python/</link>
                <guid isPermaLink="false">66ba531cf77647345442b9cf</guid>
                
                    <category>
                        <![CDATA[ control theory ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Control Systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tiago Capelo Monteiro ]]>
                </dc:creator>
                <pubDate>Tue, 06 Aug 2024 14:26:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/08/pexels-pixabay-2159.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building any control systems, including a rocket control system, involves combining control theory with programming.</p>
<p>Control theory is the study of how to make systems behave in a desired way using inputs.</p>
<p>Planes, cars, trains, circuits, rockets and many more systems need to have a brain or an architecture inside them.</p>
<p>Control theory is the study of the control architectures of these complex systems.</p>
<p>In this article, we will explore how to apply control theory to create a rocket control system using Python.</p>
<p>This is a simple guide to how the architecture of complex systems is created. In this case, it's based on a rocket.</p>
<p>In this article, you will learn about:</p>
<ul>
<li><a class="post-section-overview" href="#heading-rocket-systems-and-cake-baking-a-fun-comparison">Rocket Systems and Cake Baking: A Fun Comparison</a></li>
<li><a class="post-section-overview" href="#heading-rocket-control-made-simple-understanding-pid-controllers">Rocket Control Made Simple: Understanding PID Controllers</a></li>
<li><a class="post-section-overview" href="#heading-code-example-designing-a-simple-pid-controller">Code example: Designing a simple PID controller</a></li>
<li><a class="post-section-overview" href="#heading-conclusion-non-linear-control-systems">Conclusion: Non-linear control systems</a></li>
</ul>
<p><strong>Note:</strong> We'll assume the rocket is time-invariant, meaning its behavior doesn't change over time. Addressing time-varying dynamics would complicate this tutorial more than I'd like. </p>
<h2 id="Cake">Rocket Systems and Cake Baking: A Fun Comparison</h2>

<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/cake.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Photo by <a target="_blank" href="https://www.pexels.com/photo/white-icing-cover-cake-1702373/">Brent Keane on Pexels</a></em></p>
<h3 id="heading-what-is-a-rocket-control-system">What is a Rocket Control System?</h3>
<p>Imagine you are backing a cake. Your recipe provides the steps and ingredients needed to bake the cake.</p>
<p>In this analogy:</p>
<ul>
<li>The cake is the rocket</li>
<li>The recipe is the rocket flight plan</li>
<li>The baker's actions are the rocket control system</li>
</ul>
<p>Just as you change the oven temperature or mixing time to get the best cake, a control system changes rocket's parameters to ensure it stays on its course and remains stable.</p>
<h3 id="heading-why-are-control-systems-important-in-programming">Why are control systems important in programming?</h3>
<p>By understanding control systems, you'll become better at algorithmic design and systems thinking.</p>
<p>It also enables you to figure out how to adjust processes in feedback loops. This is very important in many areas of programming.</p>
<p>You'll mainly use control theory and control systems when creating software for:</p>
<ul>
<li><strong>Robotics and Automation</strong>: Control systems enable precise movement and adaptability in robots using feedback loops based on sensor input.</li>
<li><strong>Signal Processing and Communication</strong>: They optimize data transmission, error correction, and filtering for reliable communication.</li>
<li><strong>Embedded Systems and IoT</strong>: Control systems manage device interactions with environments, processing sensor inputs and adjusting outputs efficiently.</li>
</ul>
<h3 id="heading-how-to-create-a-rocket-control-system">How to Create a Rocket Control System</h3>
<p>In terms of our cake baking analogy:</p>
<ol>
<li><strong>Choose the Cake and Recipe</strong>: Select a simple control strategy, like choosing a basic cake recipe. A common choice is a PID controller because it's simple and effective.</li>
<li><strong>Understanding the Ingredients</strong>: Derive a mathematical model of the characteristics and trajectory of the rocket. Like studying the recipe and ingredients. This way, we get a clear understanding of the system.</li>
<li><strong>Gathering Initial Ingredients</strong>: Set initial parameters, similar to gathering your basic ingredients. </li>
<li><strong>Mixing and Baking</strong>: Adjust and test the system, much like mixing ingredients and baking. This involves using various graphs to check stability and performance.</li>
<li><strong>Adding Final Touches</strong>: Fine-tune the parameters, just like adding final decorations to your cake, to optimize the control system for efficiency.</li>
<li><strong>Following the Recipe</strong>: Convert your design into a practical form, like carefully following a cake recipe.</li>
</ol>
<h2 id="Rocket">Rocket Control Made Simple: Understanding PID Controllers</h2>

<h3 id="heading-a-simple-control-system-the-pid-controller">A simple control system: The PID controller</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/M6_ControlSystemsdiagram.png" alt="Image" width="600" height="400" loading="lazy">
<em>Example of control system diagram (<a target="_blank" href="https://edtech.engineering.utoronto.ca/object/control-systems-diagrams">source</a>)</em></p>
<p>Every control system has a controller that runs it. One of the most used controllers is the PID controller.</p>
<p>In the code example here, we will use the PID controller. This is because it's simple and effective for simple control systems.</p>
<p>In a rocket control system, the rocket's PID controller constantly adjusts the rocket's path (processing block) by comparing its current position to where it should be (feedback block).</p>
<p>This way, the rocket stays on course and reaches its final destination.</p>
<p>The PID controller has three key parts that work in the processing and feedback part of the system: proportional gain (Kp), integral gain (Ki), and derivative gain (Kd).</p>
<ul>
<li><strong>The proportional gain (Kp):</strong> Reacts immediately to any error, making the system respond quickly but sometimes causing it to overshoot the target.</li>
<li><strong>The integral gain (Ki):</strong> Fixes past errors by adding them up over time, getting rid of any leftover errors, but it can make the system unstable if set too high.</li>
<li><strong>The derivative gain (Kd):</strong> Predicts future errors to help prevent overshooting and smooth out the system's response.</li>
</ul>
<p>This is why it's called a PID (Proportional-Integral-Derivative) controller.</p>
<p>These three parts work together to create a control signal that changes the rocket's setting. This ensures that it's stable, accurate and effective.</p>
<p>With the PID controller, we can control how the inputs like thrust and altitude change the position and speed to ensure the rocket is stable and on its intended path.</p>
<h3 id="heading-analyzing-stability">Analyzing Stability</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/stability.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Photo by <a target="_blank" href="https://www.pexels.com/photo/closeup-photography-of-stacked-stones-1051449/">Shiva Smyth on Pexels</a></em></p>
<p>To design a PID controller means to design a stable control system.</p>
<p>The process of designing a stable control system is called stability analysis.</p>
<p>There are many methods, but in the code example we will use:</p>
<ul>
<li><strong>Root locus:</strong> Shows system stability and response</li>
<li><strong>Bode plot:</strong> Displays system <em>gain</em> and <em>phase margins</em></li>
<li><strong>Nyquist plot:</strong> Illustrates stability and potential oscillations</li>
</ul>
<p>In this case, the gain and phase margins simply mean that the control system can tolerate changes.</p>
<p>The gain margin tells us how much the system gain can increase without losing stability. Gain means how much to amplify the input signal to make the output signal.</p>
<p>The phase margin tells us how much delay is tolerable without losing stability. Delay in control theory means how much time it takes for the output to respond to the input.</p>
<p>This tells us how to change the Kp, Ki, and Kd so that the PID controller can control the rocket in an effective manner.</p>
<h3 id="heading-the-need-for-transfer-functions-controlling-the-rocket-and-determining-component-values">The Need for Transfer Functions: Controlling the Rocket and Determining Component Values</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/Transfer-function-v2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To implement any control system, we need two transfer functions: one theoretical and one physical.</p>
<p>Transfer functions tell us how inputs convert to outputs in a mathematical way.</p>
<p>The theoretical function is, in this case, the PID controller.</p>
<p>The physical system transfer function represents real-world dynamics and behavior of the physical components in the system.</p>
<p>By combining both, we can understand the behavior of materials and component values such as:</p>
<ul>
<li>Capacitor values for energy storage</li>
<li>Sensor calibration values for accurate data measurement and feedback</li>
<li>Spring constants for shock absorption systems</li>
<li>Pressure ratings for fuel and oxidizer tanks</li>
</ul>
<p>This way, the PID controller is not only the brain of the rocket but also can tell us the values of the components needed so that the rocket can fly its path.</p>
<h3 id="heading-how-do-engineers-find-the-physical-transfer-function-equation">How do engineers find the physical transfer function equation?</h3>
<p>First, we need to understand what the rocket is for.</p>
<p>Will it send a LEO (Low Earth Orbit) or MEO (medium Earth orbit) satellite to space or a rocket to the moon?</p>
<p>After knowing its use case, we can, with math and physics, find the physical equation of the transfer function.</p>
<p>There is actually an entire field of engineering called <strong>system identification</strong> dedicated to this.</p>
<p>Now let's see how to find, for any control system, its physical transfer function.</p>
<h2 id="Code">Code example: Designing a simple PID controller</h2>

<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/rocket.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Photo by <a target="_blank" href="https://www.pexels.com/photo/space-rocket-launching-73871/">Pixabay</a></em></p>
<p>Now with this code example, we will create a simple control system for a rocket.</p>
<p>Before we dive into the code, let's talk about decibels.</p>
<p>Decibels use a logarithmic scale to measure sound. In control theory, they measure gain in a way that's easier to visualize on graphs.</p>
<p>This way, we can see many more large and small values in a manageable range.</p>
<p>In other words, by seeing the gain in a logarithmic scale, we are seeing how much the input is amplified to be the output in a manageable range of values.</p>
<p>I'll also explain how root locus, Bode plot, and Nyquist plots assist engineers in stability analysis.</p>
<p>Let's see the code – and then we'll analyze it block by block:</p>
<pre><code># Step <span class="hljs-number">1</span>: Import libraries
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> control <span class="hljs-keyword">as</span> ctrl

# Step <span class="hljs-number">2</span>: Define a <span class="hljs-keyword">new</span> rocket transfer <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">with</span> <span class="hljs-title">poles</span> <span class="hljs-title">closer</span> <span class="hljs-title">to</span> <span class="hljs-title">the</span> <span class="hljs-title">imaginary</span> <span class="hljs-title">axis</span>
<span class="hljs-title">num</span> = [10] 
<span class="hljs-title">den</span> = [2, 2, 1] 
<span class="hljs-title">G</span> = <span class="hljs-title">ctrl</span>.<span class="hljs-title">TransferFunction</span>(<span class="hljs-params">num, den</span>)

# <span class="hljs-title">Step</span> 3: <span class="hljs-title">Design</span> <span class="hljs-title">a</span> <span class="hljs-title">PID</span> <span class="hljs-title">controller</span> <span class="hljs-title">with</span> <span class="hljs-title">new</span> <span class="hljs-title">parameters</span>
<span class="hljs-title">Kp</span> = 5
<span class="hljs-title">Ki</span> = 2
<span class="hljs-title">Kd</span> = 1
<span class="hljs-title">C</span> = <span class="hljs-title">ctrl</span>.<span class="hljs-title">TransferFunction</span>(<span class="hljs-params">[Kd, Kp, Ki], [<span class="hljs-number">1</span>, <span class="hljs-number">0</span>]</span>)

# <span class="hljs-title">Step</span> 4: <span class="hljs-title">Applying</span> <span class="hljs-title">the</span> <span class="hljs-title">PID</span> <span class="hljs-title">controller</span> <span class="hljs-title">to</span> <span class="hljs-title">the</span> <span class="hljs-title">rocket</span> <span class="hljs-title">transfer</span> <span class="hljs-title">function</span>
<span class="hljs-title">CL</span> = <span class="hljs-title">ctrl</span>.<span class="hljs-title">feedback</span>(<span class="hljs-params">C * G, <span class="hljs-number">1</span></span>)

# <span class="hljs-title">Step</span> 5: <span class="hljs-title">Plot</span> <span class="hljs-title">Root</span> <span class="hljs-title">Locus</span> <span class="hljs-title">for</span> <span class="hljs-title">Closed</span>-<span class="hljs-title">Loop</span> <span class="hljs-title">System</span>
<span class="hljs-title">plt</span>.<span class="hljs-title">figure</span>(<span class="hljs-params">figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>)</span>)
<span class="hljs-title">ctrl</span>.<span class="hljs-title">root_locus</span>(<span class="hljs-params">C * G, grid=True</span>)
<span class="hljs-title">plt</span>.<span class="hljs-title">title</span>(<span class="hljs-params"><span class="hljs-string">"Root Locus Plot (Closed-Loop)"</span></span>)

# <span class="hljs-title">Step</span> 6: <span class="hljs-title">Plot</span> <span class="hljs-title">Bode</span> <span class="hljs-title">Plot</span> <span class="hljs-title">for</span> <span class="hljs-title">Closed</span>-<span class="hljs-title">Loop</span> <span class="hljs-title">System</span>
<span class="hljs-title">plt</span>.<span class="hljs-title">figure</span>(<span class="hljs-params">figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>)</span>)
<span class="hljs-title">ctrl</span>.<span class="hljs-title">bode_plot</span>(<span class="hljs-params">CL, dB=True, Hz=False, deg=True</span>)
<span class="hljs-title">plt</span>.<span class="hljs-title">suptitle</span>(<span class="hljs-params"><span class="hljs-string">"Bode Plot (Closed-Loop)"</span>, fontsize=<span class="hljs-number">16</span></span>)

# <span class="hljs-title">Step</span> 7: <span class="hljs-title">Plot</span> <span class="hljs-title">Nyquist</span> <span class="hljs-title">Plot</span> <span class="hljs-title">for</span> <span class="hljs-title">Closed</span>-<span class="hljs-title">Loop</span> <span class="hljs-title">System</span>
<span class="hljs-title">plt</span>.<span class="hljs-title">figure</span>(<span class="hljs-params">figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>)</span>)
<span class="hljs-title">ctrl</span>.<span class="hljs-title">nyquist_plot</span>(<span class="hljs-params">CL</span>)
<span class="hljs-title">plt</span>.<span class="hljs-title">title</span>(<span class="hljs-params"><span class="hljs-string">"Nyquist Plot (Closed-Loop)"</span></span>)

<span class="hljs-title">plt</span>.<span class="hljs-title">show</span>(<span class="hljs-params"></span>)</span>
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Full Code</em></p>
<h3 id="heading-step-1-import-libraries">Step 1: Import libraries</h3>
<pre><code><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> control <span class="hljs-keyword">as</span> ctrl
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Importing libraries</em></p>
<p>Here we import two libraries:</p>
<ul>
<li><a target="_blank" href="https://matplotlib.org/">matplotlib</a>: A plotting library for creating various types of visualizations</li>
<li><a target="_blank" href="https://python-control.readthedocs.io/en/0.10.0/">Control</a>: A library for analyzing and designing control systems</li>
</ul>
<h3 id="heading-step-2-define-the-transfer-function-of-the-rocket-system">Step 2: Define the Transfer Function of the Rocket System</h3>
<pre><code>num = [<span class="hljs-number">10</span>] 
den = [<span class="hljs-number">2</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>] 
G = ctrl.TransferFunction(num, den)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Define the Transfer Function of the Rocket System</em></p>
<p>In this code we define the transfer function of the physical system</p>
<ul>
<li><strong><code>num=[10]</code></strong>: Sets the system gain to 10.</li>
<li><strong><code>den=[2,2,1]</code></strong>: Defines the denominator.</li>
<li><strong><code>G = ctrl.transferFunction(num, cen)</code></strong>: Constructs the transfer function.</li>
</ul>
<p>This is the transfer function we are going to control with PID:</p>
<p>&lt;!DOCTYPE html&gt;</p>


    
    
    Black-Scholes Equation


    <div class="card">
        <div class="card-body">
            <p>
                $$\frac{\partial V}{\partial t} + \frac{1}{2}\sigma^2 S^2 \frac{\partial^2 V}{\partial S^2} = rV - rS \frac{\partial V}{\partial S}$$
            </p>
            <h5 id="heading-rocket-transfer-function">Rocket transfer function
          </h5>
        </div>
    </div>




<p>In this code example, the transfer function rocket equation is very simple. But in real life, rocket transfer functions are not time-invariant linear systems. Usually, they are very complex non-linear systems.</p>
<h3 id="heading-step-3-design-a-pid-controller-with-new-parameters">Step 3: Design a PID controller with new parameters</h3>
<pre><code>Kp = <span class="hljs-number">5</span>
Ki = <span class="hljs-number">2</span>
Kd = <span class="hljs-number">1</span>
C = ctrl.TransferFunction([Kd, Kp, Ki], [<span class="hljs-number">1</span>, <span class="hljs-number">0</span>])
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Design a PID controller with new parameters</em></p>
<p>This code sets up a PID controller with specific gains and creates a transfer function:</p>
<ul>
<li><strong><code>Kp = 5</code></strong>: Sets the proportional gain to 5.</li>
<li><strong><code>Ki = 2</code></strong>: Sets the integral gain to 2.</li>
<li><strong><code>Kd = 1</code></strong>: Sets the derivative gain to 1.</li>
<li><strong><code>C = ctrl.TransferFunction([Kd, Kp, Ki], [1, 0])</code></strong>: Creates a transfer function of the PID controller</li>
</ul>
<h3 id="heading-step-4-applying-the-pid-controller-to-the-rocket-transfer-function">Step 4: Applying the PID controller to the rocket transfer function</h3>
<pre><code>CL = ctrl.feedback(C * G, <span class="hljs-number">1</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/5.png" alt="Image" width="600" height="400" loading="lazy">
<em>Applying the PID controller to the rocket transfer function</em></p>
<ul>
<li><strong><code>C * G</code></strong>: Multiplies the PID controller <code>C</code> with the system <code>G</code> (the rocket) to form the open-loop transfer function, which models the system's behavior without feedback and relies on predefined settings.</li>
<li><strong><code>ctrl.feedback(C * G, 1)</code></strong>: Computes the closed-loop transfer function by applying feedback and representing the system's behavior with feedback. This allows it to adjust inputs and automatically correct errors.</li>
<li><strong><code>CL</code></strong>: Stores the resulting closed-loop system, integrating the controller with the rocket to maintain desired performance through feedback, and is used for further analysis or simulation.</li>
</ul>
<h3 id="heading-step-5-root-locus-for-gain-analysis">Step 5: Root Locus for gain analysis</h3>
<p>In this code:</p>
<pre><code>plt.figure(figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>))
ctrl.root_locus(C * G, grid=True)
plt.title(<span class="hljs-string">"Root Locus Plot (Closed-Loop)"</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/6.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create the Root Locus Graph</em></p>
<p>We generate this plot:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/root-locus.png" alt="Image" width="600" height="400" loading="lazy">
<em>Simple Root Locus Graph</em></p>
<p>This is a root locus graph. It was invented to help engineers study the stability of control systems.</p>
<p>The crosses on the graph, called poles, are very important.</p>
<p>If they are on the left side of the graph, the system is stable. If they are on the right side, the system is unstable.</p>
<p>The more to the left they are, the quicker the system will return to normal after a disturbance, and thus, the more stable it will be.</p>
<p>But moving more to the left can cause too many oscillations, depending on their specific locations.</p>
<p>The key point is:</p>
<ul>
<li>By changing the <strong><code>Kp</code></strong>, <code>Ki</code>, and <strong><code>Kd</code></strong>, this moves the poles to be as far left as possible without causing oscillations.</li>
</ul>
<p>However, the root locus graph is not enough to ensure stability. We need to use the Bode and Nyquist plots as well. Only with them can we get the best PID controller values for the rocket control system.</p>
<h3 id="heading-step-6-bode-plot-for-stability-analysis">Step 6: Bode Plot for Stability Analysis</h3>
<p>In this code:</p>
<pre><code>plt.figure(figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>))
ctrl.bode_plot(CL, dB=True, Hz=False, deg=True)
plt.suptitle(<span class="hljs-string">"Bode Plot (Closed-Loop)"</span>, fontsize=<span class="hljs-number">16</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/7.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create the Bode Plot Graph</em></p>
<p>We generate this plot:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/bode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Simple Bode Plot</em></p>
<p>The Bode plot was invented to help engineers understand how a system responds to changes and how stable it will be under different conditions.</p>
<p>The Bode plot also shows the system's stability and safety margins.</p>
<p>Let's understand how it works:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/detail-bode.png" alt="Image" width="600" height="400" loading="lazy">
<em>Bode Plot in detail</em></p>
<p>The graph on top is called the Magnitude Plot and the one below it is called the Phase Plot.</p>
<p>The magnitude plot measures the gain of a system across different frequencies. Higher gain means quicker and stronger reactions, which is good for precise control.</p>
<p>The phase plot measures the phase shift introduced by the system across different frequencies. The phase shift is seen when the gain is 0.</p>
<p>In this case, we can see with the green line when the gain is zero and what phase shift is associated with that in the red line. It is approximately 63 degrees.</p>
<p>An ideal range is a phase shift of 30 to 60 degrees, which balances stability and response speed.</p>
<p>Over 60 degrees, the system is very stable, but might slow down the system response to changes.</p>
<p>So after analyzing the plot, we can conclude this PID controller is stable.</p>
<h3 id="heading-step-7-nyquist-plot-for-stability-analysis">Step 7: Nyquist Plot for Stability Analysis</h3>
<p>In this code:</p>
<pre><code>plt.figure(figsize=(<span class="hljs-number">10</span>, <span class="hljs-number">6</span>))
ctrl.nyquist_plot(CL)
plt.title(<span class="hljs-string">"Nyquist Plot (Closed-Loop)"</span>)
</code></pre><p><img src="https://www.freecodecamp.org/news/content/images/2024/08/8.png" alt="Image" width="600" height="400" loading="lazy">
<em>Create the Nyquist Plot Graph</em></p>
<p>We generate this plot:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/nyquist.png" alt="Image" width="600" height="400" loading="lazy">
<em>Nyquist Plot Graph</em></p>
<p>The Nyquist Plot is a tool to help engineers quickly check if a control system is stable or not.</p>
<p>It is very simple:</p>
<ul>
<li>If there is no circle around the red cross at point (-1 0), the system is stable.</li>
<li>If there are circles around the red cross, namely clockwise circles, at point (-1 0), the system is unstable.</li>
</ul>
<p>Since there aren't circles around the red cross, this control system is stable.</p>
<h3 id="heading-last-step-after-designing-the-rocket-control-system">Last step after designing the rocket control system</h3>
<p>After completing the design of this PID control system, we can use tools like <a target="_blank" href="https://www.mathworks.com/products/simulink.html">Simulink</a> to find the necessary values for many components.</p>
<p>In other words, after getting the best PID controller variables, it's time to find the physical component values of the rocket.</p>
<p>Some of these values are:</p>
<ul>
<li>Resistor values for controlling current flow</li>
<li>Capacitor values for energy storage</li>
<li>Inductor values for managing electromagnetic interference</li>
<li>Sensor calibration values for accurate data measurement and feedback</li>
<li>Strength and durability of materials for the rocket's body and fins</li>
<li>Torque and speed requirements for servo motors</li>
<li>Spring constants for shock absorption systems</li>
<li>Pressure ratings for fuel and oxidizer tanks</li>
</ul>
<p>Thanks to Simulink, we can get all these values needed to design a rocket according to its mission.</p>
<p>With a stable control system, based on a PID controller to control the physical transfer function of a rocket, we can get all the values needed for each component.</p>
<h2 id="Conclusion">Conclusion: Non-linear control systems</h2>

<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/moon.jpg" alt="Image" width="600" height="400" loading="lazy">
<em>Photo by Peter de Vink: https://www.pexels.com/photo/photo-of-full-moon-975012/</em></p>
<p>There are many methods available to us to optimize a Linear Time-Invariant (LTI) control system:</p>
<ol>
<li><strong>Root Locus Method</strong>: Adjust system poles to reduce oscillations.</li>
<li><strong>Bode Plot Analysis</strong>: Maintain phase margin and stability.</li>
<li><strong>Nyquist Plot</strong>: Confirm overall system stability.</li>
</ol>
<p>With these tools, it's possible to create a control system.</p>
<p>However, in this process, it is good practice to use methods like the Ziegler-Nichols method to more quickly find the best PID controller variables.</p>
<p>In our exploration, we worked with a very simple rocket system.</p>
<p>In real life, only non-linear tools are used because all rocket systems are non-linear systems.</p>
<p>One example is adaptive control, where the control system adjusts itself in real-time to handle changing conditions</p>
<p>Another one is Lyapunov's method. In this case, it is used for stability analysis instead of these three plots.</p>
<p>Still, the process of making these control systems is always the same. This article explained how this process works and how it is applied in a time-invariant system.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/tiagomonteiro0715/freecodecamp-my-articles-source-code">https://github.com/tiagomonteiro0715/freecodecamp-my-articles-source-code</a></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn System Design Principles and Prepare for an Job Interview ]]>
                </title>
                <description>
                    <![CDATA[ Mastering system design is important for anyone who wants to build scalable and reliable applications. System design includes a range of topics from basic computer architecture to complex networking concepts, each playing an important role in creatin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-system-design-principles/</link>
                <guid isPermaLink="false">66a264c5db0851e6c3ce5e67</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 25 Jul 2024 14:44:21 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1721918626724/720abc1b-4573-4264-b2c0-ef79b1ca3d8e.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Mastering system design is important for anyone who wants to build scalable and reliable applications. System design includes a range of topics from basic computer architecture to complex networking concepts, each playing an important role in creating efficient, robust systems.</p>
<p>We just published a course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that will teach you all about system design. Created by Hayk Simonyan, this comprehensive tutorial teaches the core aspects of system design, providing clear explanations, real-world examples, and practical strategies. This course covers essential topics like scalability, reliability, data handling, and high-level architecture, making it an invaluable resource for mastering system design.</p>
<h3 id="heading-course-breakdown">Course Breakdown</h3>
<h4 id="heading-introduction">Introduction</h4>
<p>The course kicks off with an introduction to system design, setting the stage for the detailed topics that follow. This section provides an overview of why system design is important and what you can expect to learn.</p>
<h4 id="heading-computer-architecture">Computer Architecture</h4>
<p>In this section, you will explore the fundamentals of computer architecture, including disk storage, RAM, cache, and CPU. Understanding these components is essential for designing systems that are both efficient and scalable.</p>
<h4 id="heading-production-app-architecture">Production App Architecture</h4>
<p>Here, the course teaches the architecture of production applications, covering Continuous Integration/Continuous Deployment (CI/CD), load balancers, and logging &amp; monitoring. These concepts are important for maintaining and scaling applications in a real-world environment.</p>
<h4 id="heading-design-requirements">Design Requirements</h4>
<p>This section focuses on the critical design requirements of modern systems. Topics include the CAP Theorem, throughput, latency, and Service Level Objectives (SLOs) and Service Level Agreements (SLAs). These principles help ensure that systems meet their performance and reliability goals.</p>
<h4 id="heading-networking">Networking</h4>
<p>A deep dive into networking covers TCP, UDP, DNS, IP addresses, and IP headers. Networking is the backbone of any distributed system, and understanding these protocols is important for designing robust architectures.</p>
<h4 id="heading-application-layer-protocols">Application Layer Protocols</h4>
<p>The course also covers various application layer protocols such as HTTP, WebSockets, WebRTC, and MQTT. These protocols are important for building interactive and real-time applications.</p>
<h4 id="heading-api-design">API Design</h4>
<p>Effective API design is crucial for creating scalable and maintainable systems. This section provides guidelines and best practices for designing APIs that are easy to use and efficient.</p>
<h4 id="heading-caching-and-cdns">Caching and CDNs</h4>
<p>Learn about caching mechanisms and Content Delivery Networks (CDNs) to optimize performance and reduce latency. These techniques are essential for handling high traffic loads and ensuring fast response times.</p>
<h4 id="heading-proxy-servers">Proxy Servers</h4>
<p>The course explains the roles of forward and reverse proxy servers in system design. Proxies can enhance security, load balancing, and caching, making them a vital part of modern architectures.</p>
<h4 id="heading-load-balancers">Load Balancers</h4>
<p>Explore different types of load balancers and their importance in distributing traffic across multiple servers. Load balancers help maintain system reliability and availability.</p>
<h4 id="heading-databases">Databases</h4>
<p>Finally, the course covers database design, including sharding, replication, ACID properties, and vertical and horizontal scaling. These concepts are important for managing large datasets and ensuring data integrity and availability.</p>
<p>Hayk Simonyan's course is packed with detailed explanations and practical examples to help you master system design. Watch the full course on <a target="_blank" href="https://youtu.be/F2FmTdLtb_4">the freeCodeCamp.org YouTube channel</a> (1-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/F2FmTdLtb_4" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn High-Level System Design by Building a YouTube Clone ]]>
                </title>
                <description>
                    <![CDATA[ High-Level System Design involves creating a blueprint for complex systems, focusing on architecture, component interactions, and scalability. It addresses how different parts of a system communicate, manage data, and handle user requests efficiently... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-high-level-system-design-by-building-a-youtube-clone/</link>
                <guid isPermaLink="false">66687c53b9efa9ba0ca0ebb3</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Tue, 11 Jun 2024 16:33:23 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1718206158910/699b887d-0621-4647-a184-306ad88c55f1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>High-Level System Design involves creating a blueprint for complex systems, focusing on architecture, component interactions, and scalability. It addresses how different parts of a system communicate, manage data, and handle user requests efficiently.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel about high-level system design. This course offers a unique hands-on approach to understanding high-level system design (HLD) concepts by building a fully functional YouTube-like platform. Keerti Purswani developed this courese.</p>
<h2 id="heading-what-is-high-level-system-design">What is High-Level System Design?</h2>
<p>High-Level System Design involves creating a blueprint for complex systems, focusing on architecture, component interactions, and scalability. It addresses how different parts of a system communicate, manage data, and handle user requests efficiently.</p>
<h2 id="heading-course-overview">Course Overview</h2>
<p>In this course, you will start with a basic system flow and gradually incorporate three key services: <strong>upload, watch, and transcoder</strong>. Each service is important to building a scalable and robust video platform. Here’s a detailed look at what you will learn:</p>
<ol>
<li><p><strong>Upload Service</strong>: Learn how to handle video uploads effectively, including chunking and managing large file transfers.</p>
</li>
<li><p><strong>Transcoder Service</strong>: Dive into transcoding with FFmpeg, a powerful tool for converting video formats and optimizing videos for different devices.</p>
</li>
<li><p><strong>Watch Service</strong>: Implement Adaptive Bitrate Streaming using HLS (HTTP Live Streaming) to ensure smooth playback across various network conditions and devices.</p>
</li>
</ol>
<h4 id="heading-technologies-covered">Technologies Covered</h4>
<p>This course leverages a range of modern technologies to build the YouTube clone:</p>
<ul>
<li><p><strong>Front-end</strong>: JavaScript and React for creating dynamic user interfaces.</p>
</li>
<li><p><strong>Back-end</strong>: Node.js and Express for building scalable server-side applications.</p>
</li>
<li><p><strong>Database</strong>: Prisma as an ORM (Object-Relational Mapping) tool to interact with databases.</p>
</li>
<li><p><strong>Frameworks</strong>: Next.js for server-side rendering and improved performance.</p>
</li>
<li><p><strong>Other Tools</strong>: Docker for containerization and Redis for caching to enhance performance and scalability.</p>
</li>
</ul>
<p>By the end of this course, you will have a deep understanding of high-level system design principles and practical experience in building a complex application. Watch the full course on <a target="_blank" href="https://youtu.be/FiXOaYnW64w">the freeCodeCamp.org YouTube channel</a> (2-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/FiXOaYnW64w" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Software System Design for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ Building large-scale distributed systems like Google, Facebook, Amazon, and Twitter requires an in-depth understanding of computer science principles. This allows systems to handle millions of users concurrently despite hardware failures. We just pub... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/software-system-design-for-beginners/</link>
                <guid isPermaLink="false">66b2066b260b867a4064ba00</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Thu, 12 Jan 2023 15:47:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/01/systemdesign.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building large-scale distributed systems like Google, Facebook, Amazon, and Twitter requires an in-depth understanding of computer science principles. This allows systems to handle millions of users concurrently despite hardware failures.</p>
<p>We just published a course on the freeCodeCamp.org YouTube channel that will teach you how to design software systems. This course is designed to provide a comprehensive understanding of the various concepts and techniques involved in designing and building software systems.</p>
<p>Gaurav Sen developed this course. He is an experienced software engineer and he also has a popular YouTube channel with almost half-a-million subscribers.</p>
<p>You will learn about basic engineering design patterns that are used to build large-scale distributed systems. In the second part of the course you will learn how to use the principles from the fist part to design and code a live streaming video app.</p>
<p>We will begin by discussing the basics of system design, including what it is and why it is important. We will then delve into specific design patterns that are commonly used in software development, such as live streaming system design, fault tolerance, extensibility, and testing.</p>
<p>The course uses video streaming service as an example for demonstrating system design principles. You will learn about different diagramming approaches, API design, database design, and network protocols. We will also discuss the importance of choosing the right datastore for your system and the process of uploading raw video footage.</p>
<p>We will also cover advanced topics such as Map Reduce for video transformation and the pros and cons of different streaming protocols such as WebRTC, MPEG DASH, and HLS. Additionally, we will discuss the role of Content Delivery Networks in system design and provide a high-level summary of the key concepts covered in the course.</p>
<p>In addition to the high-level concepts, we will also delve into the low-level design of a video player, including engineering requirements, use case UML diagrams, class UML diagrams, and sequence UML diagrams. We will also cover the process of coding the server and provide resources for further learning and development in the field of system design.</p>
<p>Overall, this course will provide a comprehensive understanding of the various concepts and techniques involved in designing and building software systems. Whether you are a beginner or an experienced software developer, this course will provide valuable insights and knowledge that you can apply to your own projects.</p>
<p>Watch the full course on the freeCodeCamp.org YouTube channel (1.5 hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/m8Icp_Cid5o" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ System Design Interview Tutorial – The Beginner's Guide to System Design ]]>
                </title>
                <description>
                    <![CDATA[ By Charles M. System Design is an important topic to understand if you want to advance further in your career as a software engineer. Even if you are just beginning your coding journey, it's a good idea to get a head start on learning about system de... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/system-design-interview-practice-tutorial/</link>
                <guid isPermaLink="false">66d45ddb8812486a37369c75</guid>
                
                    <category>
                        <![CDATA[ beginners guide ]]>
                    </category>
                
                    <category>
                        <![CDATA[ distributed systems ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Interview tips ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Job Interview ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 14 Jun 2021 22:46:31 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2021/06/youtube-system-design-thumbnail.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Charles M.</p>
<p>System Design is an important topic to understand if you want to advance further in your career as a software engineer. Even if you are just beginning your coding journey, it's a good idea to get a head start on learning about system design. </p>
<p>Early in your career you will mostly just be tested on your coding ability. In higher level interviews, however, there will often be a greater focus on testing your ability and experience at designing applications.</p>
<p>The biggest struggle engineers have with system design interviews is that they are more open-ended and there isn't any single correct answer. This lack of structure can be intimidating, so my goal with this article is to give you a roadmap for navigating these types of interviews with confidence.</p>
<p>What this article will cover:</p>
<ul>
<li>What is a system design interview and why they are used</li>
<li>The main stages of a system design interview</li>
<li>Example interview problem – Design YouTube</li>
</ul>
<h2 id="heading-video-tutorial">Video Tutorial</h2>
<p>You can also watch this tutorial on YouTube if you like:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/YEwKnGARDZI" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>And I've created a playlist of videos on specific topics related to system design and web architecture:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/undefined" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-system-design-interview-overview">System Design Interview Overview</h2>
<p>At first glance it seems silly to ask somebody to design a huge app like Twitter or YouTube in 45-60 minutes. These apps were designed over a period of years by hundreds of engineers working together, so it's clearly an impossible task to do in a short interview.</p>
<p>There are two main reasons why companies use these types of interviews. The first is, of course, to test your knowledge about the technologies being discussed. They want you to go deep enough to make sure you aren't just throwing buzzwords around without understanding how things actually work. </p>
<p>The second reason might be more important, though. The system design interview is a way to simulate a realistic scenario where you are working together with the interviewer to determine the best design decision. </p>
<p>Getting the perfect answer isn't necessarily the most important thing here – it's some of the other things you can show, like:</p>
<ul>
<li>How do you handle being challenged? Do you get defensive or take feedback with a positive attitude? Are you stubborn or narrow-minded?</li>
<li>Do you show knowledge of the various tradeoffs certain design decisions involve? There's a big difference between blindly making a decision and not realizing the consequences, and knowing the pros/cons and accepting the tradeoffs.</li>
<li>Are you able to effectively communicate and if necessary explain complex technical concepts in an easy to understand way?</li>
<li>Are you candidate somebody the interviewer would want to work with long term? Even if somebody is a genius, if they are miserable to work with they might not be a good hire.</li>
</ul>
<h2 id="heading-stages-of-a-system-design-interview">Stages of a System Design Interview</h2>
<p>In this section you'll learn a general framework for structuring how to handle a problem during a system design interview.</p>
<h3 id="heading-clarify-the-problem-and-establish-design-scope">Clarify the problem and establish design scope</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-106.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The first thing you'll want to do after your interviewer gives you the problem is to take a few minutes to ask some clarifying questions and figure out what exactly they are looking for. </p>
<p>The worst thing you could do here is just start off in the completely wrong direction because you didn't take the time to ask a few questions. You have a limited amount of time during the interview, so you want to make sure you focus on what's important. </p>
<p>Here are some examples of questions you might ask:</p>
<h4 id="heading-what-are-the-use-cases-features-of-the-app">What are the use cases / features of the app?</h4>
<p>In this article we will be using YouTube as an example. There are hundreds of different features you could design like ad delivery, authentication, recommendation algorithms, comments, video upload, video processing, and many others. </p>
<p>During an interview you only have time to cover a few of those, so make sure to ask the interviewer questions to figure out what they want you to focus on designing.</p>
<h4 id="heading-how-many-users-are-expected-what-is-the-likely-traffic-volume">How many users are expected / what is the likely traffic volume?</h4>
<p>The complexity of the system will depend on the amount of traffic it needs to handle, so make sure to gather this information. </p>
<p>You don't want to over-engineer things if the traffic is relatively low and you also don't want to get stuck with an app that can't scale because you didn't design it properly. </p>
<p>Ask questions like how many users the app will have, the average amount of data per request, how long data needs to be stored, and how reliable and available does the system need to be?</p>
<p>This step is going to help you beyond just getting more information to work with. You're also showing the interviewer that you understand how to gather information about a vague problem. </p>
<h3 id="heading-determine-rough-capacity-estimates">Determine Rough Capacity Estimates</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-107.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Using the information you gathered during the first step, you can begin to make some rough estimates and generalizations for things like storage and bandwidth requirements.</p>
<p>This process will involve some basic math like multiplying the number of users by the average request size and the amount of requests each user is expected to make daily. </p>
<h3 id="heading-create-a-high-level-design">Create a High Level Design</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-108.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Here you want to create a rough architecture for the system. Draw out things like load balancers, web servers, app servers, task queues, database, caching, file storage, and so on. You should include all the core components you need to create the system. </p>
<p>Make sure to communicate with the interviewer during this stage and check to ensure that you aren't missing anything. While they probably won't tell you directly, they will give you a nudge in the right direction if you forgot about some crucial feature.</p>
<h3 id="heading-api-design">API Design</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-114.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This part is almost cheating because you are using the structure of the interview to your advantage to confirm that you are on the right path. </p>
<p>The interviewer is never going to deliberately lead you down the wrong path, so once you've created your high level design you can start sketching out some rough API endpoints for each component. </p>
<p>For the YouTube example they might look something like this, depending on which features you are building:</p>
<ul>
<li>uploadVideo (userID, video, description, title)</li>
<li>comment (userID, videoID, comment)</li>
<li>viewVideo (videoID)</li>
<li>videoSearch (query)</li>
</ul>
<p>In some cases you might not need to drill down to this level. If the interview question is very high level like "design Youtube", you can probably skip this part. On the other hand if you get a more focused question like "design YouTube's comment system", it would make sense to go more in depth.</p>
<h3 id="heading-create-a-data-schema">Create a Data Schema</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-113.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>At this point you should have a good idea of all the requirements and data needed for the application to work, so now you can plan out how your data is structured. </p>
<p>Depending on what you are building and the requirements, you'll need to weigh the costs and benefits of things like using a relational vs non-relational database. When modeling your data you'll also want to account for things like potential data partitioning and replication.</p>
<h3 id="heading-take-a-detailed-look-at-the-components">Take a Detailed Look at the Components</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-112.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>What happens during this section will mainly depend on the feedback of the interviewer. They will probably pick out a few specific components to focus on and ask why you made certain decisions. </p>
<p>The most important part here isn't necessarily being 100% right. Instead, it's to show that you didn't just blindly make decisions and understand exactly what tradeoffs you were making. </p>
<p>You should be able to propose alternate design decisions that could have been used and explain why you didn't use them.</p>
<h2 id="heading-how-to-design-youtube">How to Design YouTube</h2>
<p>Now that you have a general idea of how a system design interview works and a framework for handling a system design problem, I'm going to show you how to put it all into practice using YouTube as an example.</p>
<h3 id="heading-step-1-define-problem-scope-and-requirements">Step 1 – Define Problem Scope and Requirements</h3>
<p>This will be a high level problem where we implement a few of YouTube's major features without diving too in-depth on any of them. The features to focus on will be:</p>
<ul>
<li>Users can upload videos</li>
<li>Users can view videos</li>
<li>Users can comment on videos</li>
</ul>
<h3 id="heading-step-2-determine-capacity-estimates">Step 2 – Determine Capacity estimates</h3>
<p>The two biggest capacity factors in an app handling large amounts of video like YouTube will be storing all that content and bandwidth requirements to deliver the content to users. In this section you'll learn how to make rough estimates for capacity requirements. </p>
<p>The main focus here is not on being highly accurate, but showing a logical thought process for calculating these numbers based on the information available to you.</p>
<p>In an interview you would be given the data, but in this case I'm using two key pieces of data that YouTube has made public:</p>
<ul>
<li><strong>YouTube creators upload 500 hours of video every minute</strong></li>
<li><strong>YouTube users watch 1 billion hours of video per day</strong></li>
</ul>
<p>You can use these numbers to calculate storage and bandwidth requirements with a few assumptions.</p>
<h4 id="heading-bandwidth-calculation">Bandwidth Calculation</h4>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-92.png" alt="Image" width="600" height="400" loading="lazy">
<em>Daily bandwidth calculation</em></p>
<p>To calculate an estimate for bandwidth, we start with the amount of video watched daily. The key assumption here is how much bandwidth is used per hour watched, as this would depend on the quality of video most users choose to watch. </p>
<p>The 3 Gigabyte estimate is based on a rough percentage of users watching in standard definition and others choosing HD or 4K, which consume much more bandwidth per hour watched.</p>
<p>The math here is fairly simple: multiply 1 billion hours by the average bandwidth of an hour of video, then divide that by 1000 to convert to terabytes, then divide by 1000 again to get to Petabytes. The final bandwidth estimate is <strong>3,000 PB</strong> used daily.</p>
<h4 id="heading-storage-calculation">Storage Calculation</h4>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-91.png" alt="Image" width="600" height="400" loading="lazy">
<em>Step by step calculations for storage</em></p>
<p>Based on a few assumptions we can calculate that YouTube will need to store around <strong>2.16</strong> <strong>Petabytes</strong> of new video every day. Here's how we get that number:</p>
<ul>
<li>Convert 500 hours to 30,000 minutes of video uploaded per minute</li>
<li>Each minute of HD video is roughly 50 Megabytes due to having copies of each video in multiple formats. We multiply that by 30,000 minutes and then divide by 1000 to convert to Gigabytes.</li>
<li>We then take the 1,500GB uploaded per minute and multiply by 60 then 24 to calculate the daily amount of video uploaded. We divide by 1000 again to convert Gigabytes to Terabytes</li>
<li>Our final total is 2,160 Terabytes uploaded daily or 2.16 Petabytes</li>
</ul>
<h3 id="heading-step-3-database-design">Step 3 – Database Design</h3>
<p>For our database we will use a standard relational database like MySQL. The schema will look something like this: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-93.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>This design is very simple but has the essentials that you'd need for a basic implementation. It would be a good idea to do some research into the differences between relational and non-relational databases so you understand what kind of situations each excel at and when to use them. </p>
<p>For certain apps with different requirements a NoSQL database might make sense. Often large systems will have many different services that use different types of databases depending on their needs.</p>
<h3 id="heading-step-4-high-level-design">Step 4 – High Level Design</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/06/image-95.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That's a pretty complex diagram, so let me break down what's happening:</p>
<ul>
<li><strong>Client</strong> – This could be a user on a mobile app or their computer trying to upload a video, make a comment, or watch a video</li>
<li><strong>CDN</strong> – A content distribution network is used to reduce latency and improve reliability when it comes to delivering static content like videos or images. A CDN works by storing content in data centers all around the world so that the content is closer to users. This results in reduced latency because requests travel a shorter distance. There's also an added benefit of content being stored in multiple locations so even if one location can't serve traffic for some reason, another location can.</li>
<li><strong>Load Balancers</strong> – A load balancer accepts requests and routes them to servers depending on a number of factors. At YouTube's scale, a single server can't handle all the traffic and you want replication to prevent a single point of failure. The load balancer can check the status of servers and verify they can handle traffic or choose another server that can handle the request.</li>
<li><strong>Services</strong> – You can think of this as the app layer of the system. Instead of using a single monolith to handle traffic, we'll use several microservices to handle specific tasks. The second box for each of these services in the diagram represents multiple servers running for each of them to increase reliability. If one replica of the service goes down, there's always another to step in and handle traffic.</li>
<li><strong>Data Stores</strong> – When using microservices it is generally best practice for each microservice to own its own data. If one service needs data from another they can access it through an API.</li>
<li><strong>Video Upload Process</strong> – Handling the video uploads will involve multiple steps, as trying to handle it synchronously with the app server would be fragile and reduce performance. I'll cover this more in depth in the next section</li>
</ul>
<p>I don't want to go too in-depth on these individual components because I could write entire articles on any of them if I wanted to explain them fully. </p>
<p>If you are interested in a more detailed explanation you can check out the system design playlist I linked to above which has videos covering most of these concepts. </p>
<h3 id="heading-step-5-go-over-specific-components-and-details">Step 5 – Go Over Specific Components and Details</h3>
<p>At this stage you have a working design. Now let's look at some of the specific components in detail.</p>
<h4 id="heading-video-upload">Video Upload</h4>
<p>Video content is the lifeblood of YouTube, and it doesn't exist without it. This means that making it quick and easy for users to upload videos is probably the most important feature. </p>
<p>Imagine uploading a multi-gigabyte video to YouTube and then seeing the upload fail after 30 minutes when it's 95% done. To prevent this you'll want to support the ability for resuming uploads if the client's connection is lost temporarily. The uploaded video can then be stored with a distributed file system like HDFS. </p>
<p>Once the upload is complete there's still a lot more to do before the video is ready for users to access. The video needs to be encoded into multiple different quality formats, you need to generate thumbnails, and push copies of the video to the global CDN. </p>
<p>Again, at any stage one of these processes could fail. To prevent this you'll have a task queue to manage this process and retry the processing attempt if it fails at any stage. </p>
<h4 id="heading-database-scaling">Database Scaling</h4>
<p>The database is often the bottleneck of an application. You will probably be tested on whether you understand some of the fundamental concepts around database scaling. This could include caching to handle read requests, sharding, and replication. </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Hopefully this article has given you a better understanding of what to expect during a system design interview. </p>
<p>This article mainly focused on the structure of the interview itself rather than the concepts you need to understand to answer the questions given during the interview. </p>
<p>Two great resources for beginning to learn about that are:</p>
<p>A great article posted here on Free Code Camp News: <a target="_blank" href="https://www.freecodecamp.org/news/systems-design-for-interviews/">https://www.freecodecamp.org/news/systems-design-for-interviews/</a></p>
<p>The system design primer repo on GitHub: <a target="_blank" href="https://github.com/donnemartin/system-design-primer">https://github.com/donnemartin/system-design-primer</a></p>
<p>Both cover just about every major concept you need to know for your system design interview and should put you in a great position for success.</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
