<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[The Polymathic Engineer]]></title><description><![CDATA[A newsletter about Algorithms, Computer Vision, and Distributed Systems]]></description><link>https://newsletter.francofernando.com</link><image><url>https://substackcdn.com/image/fetch/$s_!WkEZ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F157b59b0-a7e4-4f31-8d83-9a2034b2ff4e_354x354.png</url><title>The Polymathic Engineer</title><link>https://newsletter.francofernando.com</link></image><generator>Substack</generator><lastBuildDate>Sat, 04 Jul 2026 11:18:16 GMT</lastBuildDate><atom:link href="https://newsletter.francofernando.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Franco Fernando]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[francofernando@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[francofernando@substack.com]]></itunes:email><itunes:name><![CDATA[Franco Fernando]]></itunes:name></itunes:owner><itunes:author><![CDATA[Franco Fernando]]></itunes:author><googleplay:owner><![CDATA[francofernando@substack.com]]></googleplay:owner><googleplay:email><![CDATA[francofernando@substack.com]]></googleplay:email><googleplay:author><![CDATA[Franco Fernando]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[When to repeat yourself ]]></title><description><![CDATA[When duplicated code is the better tradeoff.]]></description><link>https://newsletter.francofernando.com/p/when-to-repeat-yourself</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/when-to-repeat-yourself</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 04 Jul 2026 06:05:31 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!anDV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 180th issue of the Polymathic Engineer newsletter.</p><p><a href="https://newsletter.francofernando.com/p/the-dry-principle">In a recent article</a>, we talked about the DRY principle and why removing duplication can lead to fewer bugs and more reusable code. As you know, DRY is one of the most well-known rules in software engineering, and most of us learn it early in our careers.</p><p>But here&#8217;s the thing: DRY is not free. Every time we remove duplication, we pay for it somewhere else. Sometimes it is the coordination between teams. Sometimes it is the tight coupling between components. Sometimes a new business requirement arrives, our common abstraction is not flexible enough to support it and starts to push back. </p><p>In a small codebase owned by a single team, this cost is small, and DRY almost always wins. In distributed systems with many services and many teams, the cost can become larger than the duplication it is trying to remove.</p><p>There is also a second question that engineers tend to skip. Two sections of code that appear the same may not mean the same thing. Sometimes what looks like duplication is just two things that look alike today and may evolve differently tomorrow, </p><p>In this issue, we will see when keeping duplication is a reasonable tradeoff, and when it is worth the effort to remove it. The outline is as follows:</p><ul><li><p>The coordination tax</p></li><li><p>Is it really duplication?</p></li><li><p>Sharing code across services: from library to microservice</p></li><li><p>The same tradeoff inside a codebase</p></li><li><p>Closing thoughts</p></li></ul><div><hr></div><h4><strong><a href="https://www.sonarsource.com/solutions/reduce-technical-debt/?utm_source=fnf&amp;utm_medium=paid&amp;utm_campaign=ss-techdebt26&amp;utm_term=newsletter-ffranco">Quantify and Eliminate the &#8220;Friction Tax&#8221; on Your Codebase</a></strong></h4><p>Technical debt isn&#8217;t just an abstract problem. As AI accelerates code generation, it also compounds debt, acting as a silent tax on your engineering velocity and exponentially increasing developer cognitive load.</p><p>SonarQube gives tech leaders and architects the exact visibility they need to stop software entropy in its tracks. By analyzing your codebase against thousands of language-specific rules, SonarQube acts as an automated, multi-layered guardrail that keeps your code predictable, adaptable, and architecturally sound.</p><p><strong><a href="https://www.sonarsource.com/solutions/reduce-technical-debt/?utm_source=fnf&amp;utm_medium=paid&amp;utm_campaign=ss-techdebt26&amp;utm_term=newsletter-ffranco">Discover how to systematically measure and manage code-level debt with SonarQube.</a></strong></p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ROkk!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ROkk!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 424w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 848w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 1272w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ROkk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png" width="240" height="85.5" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d75bf2a9-3b98-4055-9642-813601cfb439_320x114.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:114,&quot;width&quot;:320,&quot;resizeWidth&quot;:240,&quot;bytes&quot;:25553,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197145117?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ROkk!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 424w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 848w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 1272w, https://substackcdn.com/image/fetch/$s_!ROkk!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd75bf2a9-3b98-4055-9642-813601cfb439_320x114.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Thanks to the<strong> </strong>SonarQube Team for collaborating with me on this article.</p><div><hr></div><h2>The coordination tax</h2><p>To see why duplication is sometimes the better tradeoff, let&#8217;s suppose that two teams are building two services that need a similar piece of functionality. Team A writes its version in its own service, and Team B does the same. The two sections of code look almost identical, but they are in different repos and are owned by different teams.</p><p>This is the classic scenario where most engineers reach for DRY and say: it should be a shared component. On the surface, that sounds right. Two teams writing the same thing twice is exactly what DRY warns us about.</p><p>However, there is a hidden benefit to leaving the duplication in place. Both teams can move at full speed. There are no meetings to align on the design, no shared roadmap, no review process across team boundaries, and no merge conflicts on someone else&#8217;s repository. Each team owns its copy and can change it whenever it wants.</p><p>The parts of a project that need everybody in the room don&#8217;t get faster when you add more people. They slow down. The cost of sharing is in the meetings, design reviews, sign-offs, and merge negotiations. When you remove duplication by adding a shared component, you also introduce coordination among everyone who uses it.</p><p>Of course, the other side of the coin is real too. When each team owns its own copy, a bug fixed in one area doesn&#8217;t make it to the other. Knowledge doesn&#8217;t spread. The two implementations slowly drift apart, and after a few months, they may not even behave the same way for the same input. This is particularly risky for security-sensitive code because the likelihood of two independent teams getting authentication or encryption right twice is quite low.</p><p>So the question is not if we should remove duplication, but what is more expensive in our situation. Which one creates more trouble: the coordination cost of sharing, or the drift cost of duplicating? The answer is different for each team and depends on how often the code changes and how different the two versions are going to be.</p><h2>Is it really duplication?</h2><p>Before we think about how to share code, there is another crucial question to answer first: is what we are looking at really duplication?</p><p>The fact that two sections of code look very similar does not mean they solve the same problem. Two validation functions that both check &#8220;value greater than zero&#8221; can look the same on the screen, but they might have different purposes. For example, the first might be validating the user&#8217;s age and the second a product price. The validation rules are the same now, but they may evolve differently tomorrow.</p><p>This is what we call incidental duplication. It is code that looks the same but does not encode the same knowledge. It is different from inherent duplication, where the same business rule is represented in multiple places. Inherent duplication is something we usually want to remove. Incidental duplication is something we usually want to leave alone.</p><p>The tricky thing is that it can be hard to distinguish the two scenarios, especially early in a project. What appears to be duplicated code today could turn out to belong to two different domains tomorrow. Here are some hints to help: </p><ul><li><p>The two call sites are owed by different teams or driven by different stakeholders</p></li><li><p>The reason for the logic is different on each side, even though the code looks identical</p></li><li><p>You can imagine a future where one side changes and the other does not</p></li></ul><p>When any of these signals are present, it is usually better to let the duplication live for a while. If there is a pattern, it will become clear over time, and the proper abstraction will emerge from real evidence rather than a guess. But if the pattern never solidifies, you have lost nothing.</p><p>There is also an asymmetry that makes this more than a suggestion. Once you create a shared abstraction, every caller is coupled to it. As new callers are added, the coupling becomes stronger since any change needs to satisfy all of them. Splitting it back apart once there are a dozen consumers is hard, sometimes harder than the duplication you were trying to remove. Going the other way is much easier. Two similar things can be merged into one whenever you decide they really are the same.</p><p>When you are in doubt, it is better to keep the duplication. The price of being patient is small. Premature abstraction is expensive, paid over many years by many engineers in code that no one fully knows how to disentangle.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Sharing code across services: from library to microservice</h2><p>Let&#8217;s make this concrete with a real-world example. Suppose we have two services in the system: payments and notifications. Both of them expose a public API, and both need to throttle clients to protect themselves from abuse. Team payments writes its own rate limiter inside the payments service. Team notifications does the same.</p><p>After a few months, the duplication becomes a pain. A bug in the token bucket logic gets fixed on the payments side, but it is not applied on the notifications side. At this point, someone suggests the obvious next step: let&#8217;s put the rate-limiting code into a shared component. The question is: what shape should that component take?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!anDV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!anDV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 424w, https://substackcdn.com/image/fetch/$s_!anDV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 848w, https://substackcdn.com/image/fetch/$s_!anDV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 1272w, https://substackcdn.com/image/fetch/$s_!anDV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!anDV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png" width="1456" height="461" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:461,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:348316,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197145117?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!anDV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 424w, https://substackcdn.com/image/fetch/$s_!anDV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 848w, https://substackcdn.com/image/fetch/$s_!anDV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 1272w, https://substackcdn.com/image/fetch/$s_!anDV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf57c2b7-9d6d-48f8-b119-0a3cf4534bb8_1869x592.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Option one is a library. Both services pull it in as a dependency. The rate limiter itself is in its own repo. This is the cheapest path. The fixed cost of setting up the library is small: a build, a versioning scheme, an artifact repository, and some documentation. Once it is in place, bug fixes are available to all clients at the next release. Both teams work on the same codebase, and knowledge spreads naturally.</p><p>However, a library is not a black box. When you import it, its code is included in your service, with all the consequences that follow. The first is the programming language. If payments is written in Go and notifications in Java, a shared library is not going to work. The second one is transitive dependencies. Your rate-limiting library pulls in a Redis client at version 4. The payments service uses another library that requires the Redis client to be version 6. Now everyone fights about which version wins, and the resolution rules of your build tool tell you which one to use. Debugging such conflicts can be tricky and may even turn off other teams from adopting your library in the first place. The right defensive move is to keep direct dependencies to a minimum, but the issue never really goes away.</p><p>In the end, libraries exchange duplication for coupling on the dependency level. The two services cannot evolve independently, but must agree on the language stack and tolerate each other&#8217;s transitive dependencies.</p><p>The second option is to extract rate limiting as its own microservice. Both clients now invoke an HTTP (or a gRPC) endpoint rather than importing a package. The boundary between services is an API, which is a cleaner contract than a binary dependency. The rate-limiting service can be deployed and scaled on its own. The team that owns it is free to change the implementation, as long as the API stays compatible. API evolution is also easier because you can measure endpoint usage and deprecate the ones nobody calls anymore.</p><p>However, this approach is more expensive. Each request for payments or notifications now makes an extra network call to the rate-limiting service. Latency budgets become tight, and you may need caching, retries, or speculative execution to meet your service-level agreements. If the dependency breaks, well, that is also your problem now. What should payments do if the rate-limiting service is down? Remain safe and decline all incoming requests, or accept them all and stay available?</p><p>Both decisions have implications, and you need to choose thoughtfully. On top of that, running a service has operational costs such as monitoring, on-call rotations, alerting, and capacity planning. The first one in particular is a serious investment.</p><p>To wrap up the comparison, a library is cheap and tightly coupled at the dependency level. A microservice is expensive and loosely coupled at the API level. There isn&#8217;t a universal answer. The choice depends on how complex the shared logic is, whether it really has its own business domain, what your latency requirements are, and whether your organization can afford another running service. The simpler the logic, the more likely a library is to be enough. The more independent and complex the domain is, the more a microservice starts to make sense.</p><h2>The same tradeoff inside a codebase</h2><p>The same tradeoff applies one step down. The standard mechanism for sharing code inside a single codebase is inheritance. You extract the common functionalities into a base class and let subclasses provide the specialized behavior. This is clean in theory, but has the same coupling cost as a shared library or shared service, just on a smaller scale.</p><p>Let&#8217;s stick with the notifications service from the previous section. Suppose it has to send messages over two different channels: email and SMS. Both channels need the same infrastructure: a buffer for outgoing messages, logic for retrying messages that fail temporarily, and batching for when traffic is high. The call that sends the message to the provider is the only real difference between them.</p><p>The textbook solution is to extract a BaseNotificationSender class with the buffering, retry, and batching logic, and let EmailSender and SmsSender extend it by adding only the send method. DRY is satisfied. Both classes look small and easy to read. Everyone is happy until the next change arrives.</p><p>Say marketing wants to run a large campaign and asks that SMS switch to unbounded buffering for the duration. Now we have an issue. The buffering logic is in the parent class, and the parent class is shared with email. We can&#8217;t change the behavior for SMS without changing it for email too, and email has good reasons to preserve its bounded buffer.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PFfI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PFfI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 424w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 848w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 1272w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PFfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png" width="1456" height="361" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/da05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:361,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:334505,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197145117?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PFfI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 424w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 848w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 1272w, https://substackcdn.com/image/fetch/$s_!PFfI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fda05e2a6-730f-4aa3-9d23-ccbd3d9f45ee_1862x462.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The instinct is to treat it as an exceptional situation. You could add an if statement in the parent class to check whether it is an instance of SmsSender, or pass a flag, or add a hook method. All these ways work, in the sense that they make the code compile and the campaign ship. But they also leak SMS-specific knowledge into the parent class, and the parent class no longer represents a clean abstraction. It is now a place where everyone&#8217;s special cases live. The next time a divergent change comes in for email, we do the same thing again. After a few iterations of this, the parent class gets harder to understand than the two duplicated subclasses would have been in the first place.</p><p>The takeaway is that inheritance is shared code with extra coupling. The moment one child needs to diverge, the abstraction starts to push back.</p><p>However, there is another approach to deal with this, which is composition. Instead of one inheritance hierarchy, you split the responsibilities into distinct pieces: a buffer, a retry policy, and a transport. Then you plumb them together. The email sender uses a bounded buffer with HTTP transport. The SMS sender uses an unbounded buffer and a different transport. The buffer is no longer coupled to the transport. The flexibility is real, and so is the cost: there are more moving parts to understand, and the reader has to hold more pieces in their head to follow what happens when a message is sent.</p><p>There is no free lunch. Inheritance is cheap and rigid. Composition is flexible but has more moving parts. The best choice really depends on the divergence you expect.</p><h2>Closing thoughts</h2><p>DRY is not wrong. It is just not free. If we remove duplication, the risk is that we pay for it somewhere else. These costs can be in the form of coordination issues between teams, dependency problems, extra network calls, or abstractions that get in the way when a new change comes in.</p><p>The skill is being able to tell when the coordination cost of sharing is higher than the maintenance cost of duplicating. And that calculation shifts as you cross boundaries. </p><p>Inside a small codebase owned by one team, DRY almost always wins. But across team boundaries, service boundaries, or tech stacks, the math gets less straightforward, and duplication can be the better answer.</p><p>There is one more thing worth keeping in mind. It&#8217;s much easier to merge two similar things later than to split a shared abstraction once it has a dozen callers depending on it. Going from duplicated to shared is easy to revert. Going from shared to duplicated is hard, sometimes harder than living with the duplication in the first place.</p><p>So when in doubt, let the duplication live. The right abstraction will emerge from real evidence, or it will not. Either way, you are better off than committing to the wrong one too early.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Training Trap: Underfitting, Overfitting, and How to Escape Them]]></title><description><![CDATA[A practical guide to the most common problems in machine learning training and the techniques to solve them.]]></description><link>https://newsletter.francofernando.com/p/the-training-trap-underfitting-overfitting</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-training-trap-underfitting-overfitting</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 27 Jun 2026 08:34:14 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!XJhX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 179th issue of the Polymathic Engineer newsletter. </p><p>Regardless of whether you are an experienced machine learning practitioner or just a data scientist who has started their journey, you have surely had the same frustrating experience. You built a model, trained it on your data, everything looked fine, but then it fell apart in production. </p><p>There are two issues to be aware of. The first one is called <strong>underfitting</strong>. A model that underfits is too simple for the data. It makes many mistakes, even on the training data itself, because it can't capture the patterns underneath.</p><p>The second problem is called <strong>overfitting</strong>. This is the opposite of the first problem. In this situation, the model memorizes the training set perfectly; therefore, it can&#8217;t make generalizations.</p><p>In this article, we explore both underfitting and overfitting, as well as the methods to detect and address them. The outline is as follows:</p><ul><li><p>What can go wrong when training a model?</p></li><li><p>Underfitting and overfitting: two sides of the same coin</p></li><li><p>Detecting the problem with testing</p></li><li><p>The golden rule and the validation set</p></li><li><p>The model complexity graph</p></li><li><p>Solving the problem: regularization</p></li><li><p>Measuring complexity: L1 and L2 norms</p></li><li><p>The regularization parameter</p></li><li><p>When to use L1 vs L2</p></li></ul><div><hr></div><p><span>Project-based learning is the best way to develop technical skills. </span><a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a><span>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a </span><a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a><span>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</span></p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a><span>.</span></p><div><hr></div><h2>What can go wrong when training a model?</h2><p>Let&#8217;s try to answer this question starting with a simple analogy. Imagine you have to study for an exam. There are many things that could go wrong as you prepare. </p><p>You might not have studied enough. There is no way to fix that, and you are likely to perform poorly. But what if you studied a lot, just in the wrong way? For example, you choose to memorize the entire textbook word for word instead of trying to understand the material. Will you do well? Likely not, because you memorized everything without actually learning anything. You won&#8217;t know how to answer questions on the exam that you haven&#8217;t seen before. </p><p>The best approach is to study in a way that lets you answer questions you haven&#8217;t seen during your preparation.</p><p>The same things can go wrong with machine learning models. Not studying enough is like underfitting: the model is too simplistic to learn anything helpful from the input data. Memorizing the textbook is like overfitting: the model becomes too complicated and remembers the training data rather than learning the underlying patterns. A good model is one that learns the data properly and can make good predictions on new data it has never seen.</p><p>There is a fundamental trade-off in machine learning between finding the best model for the training data and ensuring it performs well with unseen data. <strong>Optimization</strong> is the process of tuning a model to work as well as possible on the training data. On the other hand, <strong>generalization</strong> is how well the model performs with data it has never seen before. The goal is always to generalize well, but there is a catch: you can only control the optimization. You adjust the model to fit the training data, and you hope it works well on other data. If you optimize too much, you can overfit, and generalization gets worse.</p><p>In the next section, we will look at underfitting and overfitting in more detail and see what they look like in practice.</p><h2>Underfitting and overfitting: two sides of the same coin</h2><p>A different way to look at underfitting and overfitting is through the lens of problem- solving. There are two mistakes you can make when you have a problem to solve. You can make the problem too simple and come up with an overly basic solution. Or you can overcomplicate the problem and come up with a solution that is too elaborate.</p><p>Imagine your task is to fix a leaking faucet. If you just put a bucket under it, you have oversimplified the problem. The faucet keeps dripping, and you are only catching the water, not fixing anything. This is underfitting: you are trying to model your data with something that is too simple to work effectively.</p><p>On the other hand, if you knock out all the plumbing in your house to fix a small drip, you have overcomplicated the solution. You may have fixed the leak, but this approach wasted resources and made the work more difficult than necessary. This is overfitting: your data might be simple, but you try to fit it with a model that is too complex. The model will fit the training data, but it will memorize it rather than learn from it.</p><p>Let&#8217;s look at a concrete example with polynomial regression. Suppose that you have a dataset that looks like a parabola, which is a curve that goes up and then down. You want to fit a polynomial to this data, but you don&#8217;t know what degree. Should you use a line, a quadratic, or something more complex?</p><p>If you fit a line (a polynomial of degree 1), the model is too simple. It is impossible for a straight line to capture the shape of a parabola, and the model underfits. If you fit a quadratic (a degree-2 polynomial), the model captures the essence of the data well. It neither underfits nor overfits. However, if you fit a polynomial of degree 10, the model becomes too flexible. It can bend and twist to reach each point in the dataset, but that is not the goal. Instead of learning that the shape looks like a parabola, it memorizes the exact positions of each point. The model overfits.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XJhX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XJhX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 424w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 848w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 1272w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XJhX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png" width="1366" height="533" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/afa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:533,&quot;width&quot;:1366,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74469,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/194885882?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!XJhX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 424w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 848w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 1272w, https://substackcdn.com/image/fetch/$s_!XJhX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fafa9c0da-ac82-4673-9964-82ca7d6632d1_1366x533.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The real problem with overfitting shows up when you try to make predictions on new data. A model that memorized the training set will perform very poorly on data it has never seen before. It learned the noise and quirks in the training data, rather than the underlying pattern. Here is a simple way to tell what is happening with your model:</p><ul><li><p><strong>Underfitting:</strong> The model performs poorly on the training data and poorly on new data. It is too simple to learn anything useful.</p></li><li><p><strong>Good fit:</strong> The model performs well on the training data and on new data. It has learned the underlying patterns.</p></li><li><p><strong>Overfitting:</strong> The model performs very well on the training data but poorly on new data. It has memorized rather than learned.</p></li></ul><p>But how can you actually measure this? How can you know if your model will perform well on new data before you deploy it? This is where testing and validation come in.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/the-training-trap-underfitting-overfitting">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Service Mesh vs API Gateway (Part III)]]></title><description><![CDATA[Two patterns that look similar, solve different problems, and work best together.]]></description><link>https://newsletter.francofernando.com/p/service-mesh-vs-api-gateway-part</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/service-mesh-vs-api-gateway-part</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 20 Jun 2026 09:02:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NfUp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 178th issue of the Polymathic Engineer newsletter.</p><p>This week, we are wrapping up our series of articles on service mesh. In the <a href="https://newsletter.francofernando.com/p/service-mesh-why-and-how-it-works">first post</a>, we talked about what a service mesh is and how its architecture works. <a href="https://newsletter.francofernando.com/p/what-a-service-mesh-gives-you-part">In the second</a>, we looked at what it actually gives you in practice and at the trade-offs that come with it.</p><p>There is one more topic that feels at home in this series, and often confuses folks: the difference between a service mesh and an API gateway. Both manage traffic, both can do things like authentication, routing, and observability, and sit somewhere between a client and a service. It&#8217;s easy to think of both as the same kind of tool, or to reduce the difference to a simple rule of thumb such as &#8220;API gateways are for north-south traffic, service meshes are for east-west,&#8221; and go on.</p><p>That shortcut is a one-liner, but it masks what each tool is actually good at. They were built to tackle different problems, and treating them as interchangeable usually leads to one of two architectural mistakes: forcing one tool to do the other&#8217;s job, or layering them together without a clear understanding of why.</p><p>In this issue, we look at API gateways and service meshes side by side. We will discuss what an API gateway does, how it differs from a service mesh, when to use which, and how they work together when used correctly.</p><p>The outline is as follows:</p><ul><li><p>What an API gateway does</p></li><li><p>Side-by-side differences</p></li><li><p>When to use which</p></li><li><p>Using them together</p></li><li><p>Wrapping up the series</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>What an API gateway does</h2><p>An API gateway lives at the edge of your system and is the front door for everything coming in from outside. Mobile apps, web browsers, partner systems, and any other external client send their requests to the gateway, and the gateway is responsible for handing them off to the right backend service. It is a centralized component, which means that a single instance, or a small cluster, takes care of all the traffic entering the system.</p><p>This central position is what makes the gateway so useful. Each external request flows through it, so you can put a lot of cross-cutting work in one place instead of spreading it across many services. The gateway works at the application layer of the OSI model. This means that it understands HTTP, HTTPS, gRPC, and WebSocket, and it can read URLs, headers, methods, and payloads to decide what to do with each request.</p><p>The most basic job of an API gateway is routing. It looks at the path or the method of an incoming request and forwards it to the right backend service. It can also translate between protocols: a common example is accepting an HTTP call from a mobile client and turning it into a gRPC call to an internal service.</p><p>Another big responsibility is authentication and authorization. The API gateway does validation once at the edge, instead of each backend service having to include its own logic. It can validate OAuth2 tokens, JWTs, API keys, and integrate with an identity provider. The requests that do not pass these checks are rejected before they reach the internal services. </p><p>The gateway also takes care of rate limiting and request shaping. It is able to throttle abusive clients, block requests that aren&#8217;t valid, and stop a single noisy customer from taking over the whole system. This keeps bad behavior on the outside from affecting services in the backend.</p><p>Some other common tasks are spreading the load across multiple backend instances, caching frequent responses to speed up the system, or gathering logs and metrics for the traffic that goes through. Since the gateway is the single point through which all external traffic passes, it is a natural place to gather observability data on how clients are using the APIs.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NfUp!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NfUp!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 424w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 848w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 1272w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NfUp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png" width="523" height="306.57042869641293" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:670,&quot;width&quot;:1143,&quot;resizeWidth&quot;:523,&quot;bytes&quot;:389018,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/195423488?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NfUp!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 424w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 848w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 1272w, https://substackcdn.com/image/fetch/$s_!NfUp!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd493ef15-b51f-4f27-9e25-ed73ad56141a_1143x670.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Many API gateways also handle API versioning. Backend services do change, but the contract that external clients rely on cannot break overnight. The gateway can give a stable interface to customers while sending requests to newer versions of the services behind the scenes. It can also change the response so that older clients get what they expect.</p><p>Besides traffic, API gateways today are typically the key to what is sometimes referred to as API productization. If you think about APIs as products which are consumed by internal teams, partners, or third-party developers, routing and auth are not enough. A lot of gateways are integrated with large API management platforms with developer portals, self-service onboarding, monetization features, sandbox environments, and access control dashboards. This transforms the gateway from a network infrastructure element into the front office of your APIs.</p><p>For a more detailed overview of API gateways, you can refer to <a href="https://newsletter.francofernando.com/p/api-gateways">our previous article</a>.</p><h2>Side-by-side differences</h2><p>Now that we have looked at both an API gateway and a service mesh in isolation, let&#8217;s put them side by side. There are 5 main differences worth knowing about.</p><p>The first one is the deployment model. A gateway is a centralized component. You can have a single instance or a small cluster at the edge that handles all the traffic coming from the outside. That makes it easier to apply global policies and observe all external interactions from a single place.</p><p>On the other hand, a service mesh is decentralized. It uses a sidecar proxy adjacent to each service replica instead of a single component. This shifts traffic control to each service, giving you fine-grained control, but adding moving parts to the system.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/service-mesh-vs-api-gateway-part">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Depth-First Search Pattern: Exploring Trees and Graphs]]></title><description><![CDATA[How to systematically search through all paths by going as deep as possible before backtracking]]></description><link>https://newsletter.francofernando.com/p/the-depth-first-search-pattern-exploring</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-depth-first-search-pattern-exploring</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 13 Jun 2026 10:01:23 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0c636e28-d379-483f-ba6d-91d89e8612ab_342x164.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 177th issue of the Polymathic Engineer newsletter.</p><p>This is the final article in our series on recursion. In the <a href="https://newsletter.francofernando.com/p/the-divide-and-conquer-breaking-problems">last article</a>, we looked at the divide-and-conquer pattern, which breaks a problem into independent subproblems, solves each one, and combines the results. This time, we examine depth-first search,  a pattern for traversing trees and graphs.</p><p>The main idea behind DFS is simple: start at a node, visit its children or neighbors recursively, and go as deep as possible before backtracking. When you reach a dead end, you return and try a different path. You keep doing this until you find what you are looking for or you run out of paths to explore.</p><p>DFS is the pattern you reach for when you need to find a path, check if something is reachable, or explore all possible paths through a structure. It works on trees, graphs, and many problems that can be represented as graphs, even if they don&#8217;t look like one at first glance.</p><p>The outline is as follows:</p><ul><li><p>The Core Pattern</p></li><li><p>Finding Paths</p></li><li><p>Handling Cycles</p></li><li><p>Finding All Paths</p></li><li><p>Matrices as Implicit Graphs</p></li><li><p>When DFS Applies</p></li></ul><div><hr></div><h4><strong><a href="https://coderabbit.link/fernando">Your AI shouldn&#8217;t grade its own homework</a></strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oBf2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" width="608" height="365.38461538461536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:875,&quot;width&quot;:1456,&quot;resizeWidth&quot;:608,&quot;bytes&quot;:86772,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Claude Code writes beautiful code. So does Codex. But here&#8217;s the thing, they also think they write beautiful code. And when you ask an AI to review code it just wrote, you get the intellectual equivalent of a student grading their own exam. Shockingly, they always pass.</p><p><a href="https://coderabbit.link/fernando">CodeRabbit CLI</a> plugs into Claude Code and Codex as an external reviewer, different AI Agent, different architecture, 40+ static analyzers, and zero emotional attachment to the code it&#8217;s looking at. The agent writes, CodeRabbit reviews, and the agent fixes. Loop until clean.</p><p>You show up when there is actually something worth approving.</p><p>One command. Autonomous generate-review-iterate cycles. The AI still does the work. It just doesn&#8217;t get to decide if the work is good anymore.</p><p><a href="https://coderabbit.link/fernando">Free tier available. Try CodeRabbit&#8217;s CLI.</a></p><p>Thanks to the CodeRabbit team for collaborating with me on this newsletter issue.</p><div><hr></div><h2>The Core Pattern</h2><p>The simplest example of DFS is searching a binary tree to check whether it contains a particular value. We start at the root and recursively search the left and right subtrees. If we find the target value, we return true, and if we reach a null node, we return false. If either subtree contains the value, the whole tree contains it. Here is a possible way to implement this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;d31d1fa1-27ba-4841-9515-c61e151d0f50&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def contains(node, target):
    if node is None:
        return False
    
    if node.value == target:
        return True
    
    return contains(node.left, target) or contains(node.right, target)</code></pre></div><p>Let&#8217;s do a quick walk-through on the following example. </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BTeK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BTeK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 424w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 848w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 1272w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BTeK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png" width="366" height="320.44303797468353" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:415,&quot;width&quot;:474,&quot;resizeWidth&quot;:366,&quot;bytes&quot;:59701,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197965772?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BTeK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 424w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 848w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 1272w, https://substackcdn.com/image/fetch/$s_!BTeK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F382d122a-90bb-4280-83c9-a58e7d1237c3_474x415.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Suppose we want to find 4. We start at 5. As it is not the target, we proceed on the left subtree rooted at 3. Node 3 is not the target either, so we proceed by checking its left subtree, rooted at 1. Node 1 is not the target, and it has no children, so we return false.</p><p>We backtrack to 3 and search its right subtree rooted at 4. Node 4 is the target, so we return true. This true value propagates back up through 3 and then 5, and the function returns true. </p><p>This is the essence of DFS: go down one path as far as possible, and if you do not find what you are looking for, backtrack and try another path.</p><p>The time complexity is O(n), where n is the number of nodes in the tree. In the worst case, we go through each node until we identify the target or determine it is not there. The space complexity is also O(n) because of the recursion stack. This happens when the tree is completely imbalanced and resembles a linked list. So the depth is n.</p><p>One thing to keep in mind when analyzing tree problems is to be clear about what n represents. Here, n is the total number of nodes. If the tree were balanced, the depth would be O(log n), and the space complexity would improve accordingly.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Finding Paths</h2><p>Checking whether a tree contains a value is useful, but often we need more. We would like to find the actual path from the root to the target node. This is a slight difference in how we think about the problem.</p><p>There are two approaches to do this. The first is to pass a path variable down through the recursive calls, adding nodes as we go and removing them when backtracking. The second is to build up the path as we return from the recursive calls.</p><p>I prefer the second approach. This is because at each node, we have two options as we travel down the tree: go left or go right. We do not know which way will lead us to the goal. But when we find the target and start returning, there&#8217;s only one way up. We just follow the successful path and construct the result as we go.</p><p>Here is a possible way to implement this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;ea581cac-ffab-4f86-9c1a-12d29032e8d9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def path_to_node(node, target):
    if node is None:
        return None
    
    if node.value == target:
        return [node.value]
    
    left_path = path_to_node(node.left, target)
    if left_path is not None:
        return [node.value] + left_path
    
    right_path = path_to_node(node.right, target)
    if right_path is not None:
        return [node.value] + right_path
    
    return None</code></pre></div><p>The function returns None if the target is not in the subtree. Otherwise, it returns the path from the current node to the target. When the target is found, the return value is a list with just that node. As we keep returning up, each node adds itself to the front of the path.</p><p>Let&#8217;s walk through an example using the same tree as before to search for 4. We start at 5 and search the left subtree. We reach 3 and search its left subtree. We then reach 1, which has no children and is not the target, so we return None.</p><p>Back at 3, we search the right subtree. We reach 4, the target, and return [4]. Back at 3, we see that right_path is not None, so we return [3, 4]. Back at 5, we see that left_path is not None, so we return [5, 3, 4]. The path is built from the root down to the target as we return from recursive calls.</p><p>Both the time and space complexities are O(n), as for the basic containment check. We still visit each node once, and the recursion depth is at most n in an imbalanced tree.</p><h2>Handling Cycles</h2><p>The DFS pattern we have seen so far works on trees. But what about when we move to graphs? The main distinction is that a graph may contain cycles. If we are not careful, we could end up visiting the same node repeatedly and entering an infinite loop.</p><p>The solution is to mark all the nodes that we have already visited. We verify whether a node is in our visited set before traversing it. If it is, we skip it. If not, we add it to the set and keep exploring.</p><p>Here is how we can check if a path exists between two nodes in a graph:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;22439b86-e2d1-44ff-9655-e4c4799cb65b&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def has_path(graph, source, destination, visited=None):
    if visited is None:
        visited = set()
    
    if source == destination:
        return True
    
    if source in visited:
        return False
    
    visited.add(source)
    
    for neighbor in graph[source]:
        if has_path(graph, neighbor, destination, visited):
            return True
    
    return False</code></pre></div><p>The structure of the code is similar to what we have seen with trees. The base case is when the current node is the target. The recursive step is to try each neighbor and see if any of them leads to the destination.</p><p>The major addition is the visited set. We check whether the current node has already been visited; if so, we return false immediately. This prevents us from going in circles. Once we visit a node, we add it to the set so we do not visit it twice.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yxp_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yxp_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 424w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 848w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 1272w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yxp_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png" width="557" height="253" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:253,&quot;width&quot;:557,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54951,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197965772?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yxp_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 424w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 848w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 1272w, https://substackcdn.com/image/fetch/$s_!yxp_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F042ca00a-4f79-424c-928f-e2840c1dff20_557x253.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Let&#8217;s go through a quick example. Suppose we have a graph in which node A connects to nodes B and C, node B connects to nodes A and D, and node C connects to node A. We want to check if there is a path from A to D.</p><p>We start at A and add it to the visited set. We try neighbor B. We add B to visited and try its neighbor A. But A is already visited, so we skip it. We try B&#8217;s other neighbor D. Node D is the destination, so we return true. Without the visited set, we would have gone from A to B to A to B to A, forever.</p><p>The time complexity of the algorithm is O(V + E), where V is the number of vertices and E is the number of edges. Indeed, we visit each vertex at most once and examine each edge at most once. The space complexity is instead O(V) for the visited set and the recursion stack.</p><h2>Finding All Paths</h2><p>In the previous section, we checked whether there is a path between two nodes. But sometimes we need to find all possible paths, not just one. This requires a subtle but important change to how we handle the visited set.</p><p>When we are searching for a single path, we add a node to the visited set and leave it there. Once we know a node does not lead to the destination, there is no purpose in visiting it again. But when we are looking for all paths, different paths may share the same node. If we leave a node in visited after exploring it, other paths will not be able to use it.</p><p>The solution is backtracking. We add the node to the visited set before looking at its neighbors, then remove it when we have finished. This makes the node available for other pathways again. </p><p>Here is a possible implementation to determine all pathways between two nodes in a graph:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;56ea9d03-5834-4c3e-87a5-a81f56645cc1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def all_paths(graph, source, destination, visited=None, path=None, results=None):
    if visited is None:
        visited = set()
    if path is None:
        path = []
    if results is None:
        results = []
    
    if source in visited:
        return results
    
    visited.add(source)
    path.append(source)
    
    if source == destination:
        results.append(path[:])
    else:
        for neighbor in graph[source]:
            all_paths(graph, neighbor, destination, visited, path, results)
    
    path.pop()
    visited.remove(source)
    
    return results</code></pre></div><p>The code structure is the same as before, except for two things. First, we keep a path variable that tracks the path we are exploring. Second, after visiting all its neighbors, we delete the current node from both the visited set and the path variable.</p><p>Let&#8217;s look at an example. Suppose node A connects to nodes B and C, node B connects to node D, and node C also connects to node D. We want to find all paths from A to D.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4NrZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4NrZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 424w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 848w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 1272w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4NrZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png" width="423" height="212.85867237687367" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a8786640-af8a-498e-b1ea-65439849b852_467x235.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:235,&quot;width&quot;:467,&quot;resizeWidth&quot;:423,&quot;bytes&quot;:44377,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197965772?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4NrZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 424w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 848w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 1272w, https://substackcdn.com/image/fetch/$s_!4NrZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa8786640-af8a-498e-b1ea-65439849b852_467x235.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>We start at A, add it to visited and path. We try neighbor B, add it to visited and path. We try B&#8217;s neighbor D, which is the destination, so we save the path [A, B, D]. We then backtrack: remove D, then remove B from the visited set and the path.</p><p>Back at A, we try neighbor C, add it to visited and path. We try C&#8217;s neighbor D, which is the destination, so we save the path [A, C, D]. We backtrack and return both paths.</p><p>The crucial point is that D is common to both paths. If we hadn&#8217;t removed B from the visited set once we had explored it, the second path through C would still work. But in a more complicated graph, failing to remove nodes would prevent us from discovering valid paths.</p><p>In the worst case, the time complexity is O(V! * V) because there could be a factorial number of paths, and each path can contain at most V nodes. The space complexity is O(V) for the recursion stack, visited set, and current path.</p><h2>Matrices as Implicit Graphs</h2><p>Many problems involve working with matrices instead of explicit graphs. The positive news is that we can use the same DFS pattern without explicitly converting the matrix into a graph. The graph is the matrix: nodes are cells, edges are neighboring cells.</p><p>The crucial finding is that it is straightforward to compute neighbors in a matrix. If we are at cell (i, j) and can move in four directions, the neighbors are (i+1, j), (i-1, j), (i, j+1), and (i, j-1). For some problems, you can only go right or down, so the neighbors are just (i+1, j) and (i, j+1).</p><p>Let&#8217;s now look at an example. Suppose we have a matrix of integers and want to find the path with the largest product from the top-left corner to the bottom-right corner. As the following implementation shows, we can only go down or right:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1574802b-a701-401f-aa77-6cb931b9ddec&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def greatest_product_path(matrix):
    rows = len(matrix)
    cols = len(matrix[0])
    result = [float('-inf')]
    
    def dfs(i, j, product):
        if i &gt;= rows or j &gt;= cols:
            return
        
        product *= matrix[i][j]
        
        if i == rows - 1 and j == cols - 1:
            result[0] = max(result[0], product)
            return
        
        dfs(i + 1, j, product)
        dfs(i, j + 1, product)
    
    dfs(0, 0, 1)
    return result[0]</code></pre></div><p>The structure is the same as the DFS we have seen before. The base case checks if we have gone outside the matrix bounds. Every time we reach the bottom-right corner of the matrix, we compare the current product with the best product found so far. In the recursive step, we try both possible moves: down and right.</p><p>Notice that we do not need a visited set here. Since we can only move right or down, there is no way to revisit a cell. We will never form a cycle, so tracking visited nodes is unnecessary.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!59QU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!59QU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 424w, https://substackcdn.com/image/fetch/$s_!59QU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 848w, https://substackcdn.com/image/fetch/$s_!59QU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 1272w, https://substackcdn.com/image/fetch/$s_!59QU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!59QU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png" width="529" height="231.5977382875606" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:271,&quot;width&quot;:619,&quot;resizeWidth&quot;:529,&quot;bytes&quot;:88725,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/197965772?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!59QU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 424w, https://substackcdn.com/image/fetch/$s_!59QU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 848w, https://substackcdn.com/image/fetch/$s_!59QU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 1272w, https://substackcdn.com/image/fetch/$s_!59QU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5478f8ae-53ed-4266-befe-f1cee07a29a0_619x271.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This technique can be applied to many matrix problems: path finding, path counting, determining whether a destination can be reached, etc. The matrix gives the structure, and DFS provides the exploration strategy. We just need to define what a neighbor is and what the target state is.</p><p>The time complexity is O(2^(n+m)) in the worst case, where the matrix has n rows and m columns. This is because, at each cell, we have two alternatives, and the path length is at most n + m. The space complexity is O(n + m) for the recursion stack.</p><h2>When DFS Applies</h2><p>DFS is the right choice when you need to explore paths across some kind of structure, whether that structure is a tree, a graph, or something that can be represented as one. </p><p>Here are some common signs that a problem calls for DFS.</p><p>The first sign is that you need to find a path or check if one exists. If the problem asks whether you can get from point A to point B, or which path you would take to arrive there, DFS is a logical fit. You explore one path at a time, moving as deep as possible before trying alternatives.</p><p>The second sign is that you need to find all paths or explore all possibilities. If there are multiple valid routes, and you need to list them, DFS combined with backtracking does the job. You explore each path fully, record it if it is valid, then backtrack and try the next one.</p><p>The third sign is that the problem involves a tree or graph structure. This covers not only explicit trees and graphs, but also implicit ones such as matrices, game states, and decision trees. If you can define what a node is and what its neighbors are, you can apply DFS.</p><p>A few more examples of classic DFS problems:</p><ul><li><p>Determining if a graph is connected: start from any node and see if you can reach all others.</p></li><li><p>Finding connected components: run DFS from each unvisited node and mark all reachable nodes as part of the same component.</p></li><li><p>Detecting cycles in a graph: track nodes currently in the recursion stack and check if you revisit one.</p></li><li><p>Solving puzzles like mazes or Sudoku: explore one choice at a time, backtrack when you hit a dead end.</p></li><li><p>Computing properties of trees: height, diameter, path sums, and similar metrics often use DFS traversal.</p></li></ul><p>One last thing that is worth keeping in mind is the distinction between DFS and BFS (breadth-first search). DFS goes deep along a path before going wide to explore other paths, while BFS first explores all neighbors at the current level and only then moves deeper. DFS is easier to implement recursively and uses less memory for deep, narrow structures. BFS is better when you need the shortest path in an unweighted graph.</p><h2>Conclusion</h2><p>DFS is one of the most flexible patterns for recursion. The basic principle is straightforward. Take one path as far as you can, and if it doesn&#8217;t get to where you want, backtrack and try another.</p><p>When it comes to trees, the pattern is simple. You traverse nodes recursively, returning results as you come back up. When dealing with graphs, you have to keep track of visited nodes to avoid cycles. In matrix problems, cells are nodes and neighboring cells are neighbors. The logic is always the same.</p><p>This brings us to the end of our series on recursion. We have covered six patterns:</p><ul><li><p><strong><a href="https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and">Iteration</a></strong>: process items one at a time and collect results as you go.</p></li><li><p><strong><a href="https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and">Subproblems</a></strong>: solve a smaller version of the problem and build on it.</p></li><li><p><strong><a href="https://newsletter.francofernando.com/p/finding-all-combinations-with-the">Selection</a></strong>: choose whether to include or exclude each item when creating combinations.</p></li><li><p><strong><a href="https://newsletter.francofernando.com/p/the-ordering-pattern-generating-all">Ordering</a></strong>: determine which item comes next to produce permutations.</p></li><li><p><strong><a href="https://newsletter.francofernando.com/p/the-divide-and-conquer-breaking-problems">Divide and conquer</a></strong>: split the input into independent parts, solve each, and combine.</p></li><li><p><strong>Depth-first search</strong>: explore paths by going deep before going wide.</p></li></ul><p>These patterns are not mutually exclusive. Many problems combine elements of several patterns. A backtracking problem might use selection with DFS. A tree problem might use divide and conquer with subproblems. The more comfortable you become with each pattern, the easier it is to recognize which ones apply and how to combine them.</p><p>Recursion can feel tricky at first, but it becomes natural with practice. The key is to focus on two things: the base case and the recursive step. Get those right, and the rest takes care of itself.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[What a Service Mesh Gives You (Part II)]]></title><description><![CDATA[The features that make a service mesh worth the trouble.]]></description><link>https://newsletter.francofernando.com/p/what-a-service-mesh-gives-you-part</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/what-a-service-mesh-gives-you-part</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 06 Jun 2026 10:27:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!OITG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 176th issue of the Polymathic Engineer newsletter.</p><p>This week, we continue our series of articles on service mesh. In the previous article, we discussed what a service mesh is and how it works. We&#8217;ve seen that a service mesh is a separate layer of infrastructure that handles all the traffic between services. The key idea is the sidecar pattern, where a small proxy runs next to each service instance and quietly takes over the networking job.</p><p>Now that we understand what a service mesh is, the natural next question is what you really get out of it. The short answer is that it gives you 4 things: traffic management, reliability, security, and observability. All microservice systems have to deal with these issues, and a service mesh solves them all in one place.</p><p>Another benefit that runs through all of these is polyglot support. As we have seen in the previous article, the library approach breaks down when teams use more than one programming language. A service mesh avoids this issue because it does everything in the proxy layer. Regardless of whether a service is written in Go, Python, Java, or C#, it gets the same retries, the same mTLS, the same metrics, and the same routing rules. There is no library for each language to build and maintain.</p><p>However, not all is for free. A service mesh has a cost in terms of latency, complexity, and resources. First, we will talk about the beneficial things, then about the trade-offs, and finally, we&#8217;ll take a quick look at the most typical service mesh implementations.</p><p>The outline is as follows:</p><ul><li><p>Traffic management and routing</p></li><li><p>Reliability</p></li><li><p>Security</p></li><li><p>Observability</p></li><li><p>Trade-offs: when a mesh isn&#8217;t worth it</p></li><li><p>A quick tour of popular meshes</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>Traffic management and routing</h2><p>The first thing a service mesh gives you is control over how traffic flows between your services.</p><p>When a service wants to call another service, it needs to know its address. In the past, when data centers were on-premises, these addresses were usually hardcoded because machines rarely changed. This isn't true anymore in the cloud. Instances come and go, IP addresses change, and services often move to different nodes. Your code will break whenever the network layout shifts if you hardcode addresses.</p><p>A service mesh takes care of this for you through service discovery. The sidecar keeps track of where each service lives in real time. The proxy finds out the service that the application is calling by name and directs the request there. The application doesn&#8217;t need to know any IP addresses, and it doesn&#8217;t need to be redeployed when something moves.</p><p>On top of this, the mesh can normalize service names across environments. The code might, for example, call a service called sessions-service. Then, when the code is put into production, it talks to a production instance. When it is put into staging, it talks to a staging instance. The mesh knows which one to use based on where it is running, so the application doesn&#8217;t have to carry environment-specific configuration.</p><p>Another key feature is load balancing. When there are multiple instances of a service, the mesh spreads the traffic across them. Because the sidecar understands application-level protocols like HTTP/2 and gRPC, it can do request-level load balancing, not just connection-level. This is important since one gRPC connection can handle numerous requests. If there is no request-level balancing, one connection would pin all the work to a single instance.</p><p>Beyond simple balancing, a service mesh supports more advanced traffic control. We can split traffic between two versions of a service: for example, send 95% of the calls to v1 and 5% to v2 to roll out a new release gradually. This is how canary deployments work at the infrastructure layer, without having to touch the application. We can also mirror traffic, sending a copy of the requests to a test instance to see how it handles production load, without affecting the real responses.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OITG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OITG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 424w, https://substackcdn.com/image/fetch/$s_!OITG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 848w, https://substackcdn.com/image/fetch/$s_!OITG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 1272w, https://substackcdn.com/image/fetch/$s_!OITG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OITG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png" width="674" height="248.16968698517297" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:447,&quot;width&quot;:1214,&quot;resizeWidth&quot;:674,&quot;bytes&quot;:75884,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/195329029?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!OITG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 424w, https://substackcdn.com/image/fetch/$s_!OITG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 848w, https://substackcdn.com/image/fetch/$s_!OITG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 1272w, https://substackcdn.com/image/fetch/$s_!OITG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b53b019-6738-41f7-b7aa-0d1581708d75_1214x447.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Traffic shaping and traffic policing are other useful tools. Shaping is when you slow down some traffic to make it fit a certain pattern. For example, you might give paying customers priority over free-tier users. Policing is making sure a contract is followed. For example, you could block a service that makes too many requests to a downstream dependency. Both are ways to keep the system stable when some parts behave badly.</p><p>You can set all of this without changing the application code. The mesh configuration is stored in files that the platform or infrastructure teams can update, and the sidecars pick up the changes.</p><h2>Reliability</h2><p>Networks fail. Services can become slow or unresponsive. Instances can crash or get too loaded during traffic spikes. In microservice systems, any call between services is a potential point of failure. If you don&#8217;t manage these failures in a consistent way, the issues spread through the system very quickly.</p><p>A service mesh can apply the well-known reliability patterns to every call, without the applications having to know about them.</p><p>The first one is retries. If a call fails because of a temporary error, the sidecar retries it automatically, usually with exponential backoff. This means that the proxy waits a bit longer between each attempt to avoid making things worse when a service is already having trouble. The mesh can also distinguish between errors that are worth retrying, like a network timeout, and errors that are not, like a 404 response.</p><p>Timeouts work in the same way. The sidecar gives up instead of waiting forever when a call takes too long. This is important because slow calls can add up quickly.If there is no timeout, a slow downstream service can use up all the system&#8217;s resources while every call upstream waits for a response that never comes. </p>
      <p>
          <a href="https://newsletter.francofernando.com/p/what-a-service-mesh-gives-you-part">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Service Mesh: Why and How It Works (Part I)]]></title><description><![CDATA[How a dedicated infrastructure layer can take over the job of managing service-to-service communication.]]></description><link>https://newsletter.francofernando.com/p/service-mesh-why-and-how-it-works</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/service-mesh-why-and-how-it-works</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 30 May 2026 09:34:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!KlZL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a2b7ab7-d16b-48d6-bf48-86c44126fb61_1176x307.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 175th issue of the Polymathic Engineer newsletter.</p><p>Building software has changed a lot in the last decade. We moved from big monolithic applications to systems made up of many smaller services that talk to each other over the network. This change helped teams in several ways, such as the possibility to pick the right language for each job, faster development, and independent deployments.</p><p>But there is something to consider. A call to a function inside a monolith is now a call between two services over the network. And network calls are a whole different story. They can be slow, break or become lost. We have to think about things like balancing the load across service instances, retrying failed requests, encrypting traffic between services, and deciding which service can call which. </p><p>These are the kinds of problems a service mesh is built to solve.</p><p>At a high level, a service mesh is a dedicated layer that sits between your services and handles all the network traffic between them. The mesh handles encryption, timeouts, retries, and service discovery, so the services don&#8217;t need to take care of them in their own code. The application code makes a standard HTTP or gRPC call, and the mesh does the heavy lifting behind the scenes.</p><p>In this issue, we break down the fundamentals of service mesh: why the pattern exists, what it actually is, and how its architecture works. This is the first of three articles in a series. In the next two, we will talk about what a service mesh gives you in terms of features and how it stacks up against an API gateway.</p><p>The outline is as follows:</p><ul><li><p>Why we need a service mesh</p></li><li><p>What a service mesh is</p></li><li><p>The sidecar pattern</p></li><li><p>Data plane and control plane</p></li><li><p>Where a service mesh is deployed</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>Why we need a service mesh</h2><p>As we discussed, switching to microservices made it harder for services to talk to each other. These problems are not new. Anybody who has worked on distributed systems knows that network calls are slow and unreliable, and getting services to trust each other is hard. The size is what changed.  With dozens of services talking to each other, every engineering team has to deal with these difficulties daily.</p><p>The first attempt to solve this was the library approach. Teams would create a shared library that managed things like retries, circuit breaking, load balancing, and service discovery, and every service would use it as a dependency. The Netflix stack from the early 2010s is a well-known example. It had Eureka for service discovery, Hystrix for circuit breakers, and Ribbon for client-side load balance. For a while, it worked well, and it became the standard way to build reliable microservice architectures. But the approach started to show its flaws as systems got bigger and teams used more than one programming language.</p><p>The biggest issue was that every programming language needed its own version of the library. If the team used C#, Python, and Go, they needed three implementations that behaved the same way. This is tougher than it sounds because even slight changes in how timeouts or retries are handled might make the system behave differently.</p><p>Another problem was version skew. Different services would use different versions of the library at any given time. To roll out a bugfix in the retry logic, every team had to redeploy their services. This slowed down changes that should have been easy to make.</p><p>Last but not least, the library model put a lot of work on the application teams to keep things running. Every developer had to know how to set up circuit breakers, discovery clients, and retries, although these have nothing to do with the business problem they were trying to solve. The code was harder to read, test, and change since application logic and networking logic were mixed together.</p><p>These limitations made engineering teams look for another approach. If networking concerns were the same across services, and if the library version of it was painful to maintain across languages, maybe the best thing to do was to move them out of the application altogether. </p><p>This is the idea behind a service mesh.</p><h2>What a service mesh is</h2><p>A service mesh is a separate layer of infrastructure that handles all the traffic between the services in a distributed system. It takes care of routing, load balancing, timeouts, retries, encryption, and observability, without the services having to be aware of it.</p><p>The last sentence is the main point. In the library approach we discussed before, each service had to consume a dependency and call specific APIs to get circuit breaking or retries. With a service mesh, the service just makes a standard call to another service. The mesh sits in the network path and quietly adds the extra behavior before the call reaches its destination.</p><p>This kind of traffic, where one internal service talks to another, is usually called east-west traffic. Traffic that comes from outside the system, such as a mobile app or a web browser calling a backend, is called north-south traffic. These 2 kinds of traffic have different needs and patterns. A user may only send a request to the backend once in a while, but with a microservice design, one request can lead to dozens of calls between services. This is why service mesh tools focus on east-west traffic and look different from the tools that manage traffic at the edge of the system. We will come back to this comparison in the third part of the series.</p><p>So how does a service mesh sit in the middle of every service call without the services having to know? The answer is the sidecar pattern.</p><h2>The sidecar pattern</h2><p>The sidecar pattern is the trick that makes a service mesh work. The key idea is to run a small proxy process next to each service instance, on the same machine or inside the same pod if we are on Kubernetes. The proxy is always deployed with the service as a single unit. This is why it is termed a &#8220;sidecar,&#8221; like the sidecar of a motorbike.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/service-mesh-why-and-how-it-works">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Divide and Conquer: Breaking Problems into Smaller Pieces]]></title><description><![CDATA[How to split a problem into independent subproblems, solve each one, and combine the results]]></description><link>https://newsletter.francofernando.com/p/the-divide-and-conquer-breaking-problems</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-divide-and-conquer-breaking-problems</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 23 May 2026 09:30:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Acz9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 174th issue of the Polymathic Engineer newsletter.</p><p>In the last article in our series on recursion, we looked at the ordering pattern, which lets us generate all permutations of a set of items. This time, we talk about a different method: divide and conquer. </p><p>Instead of working on items one at a time, the main idea is to partition the input into multiple independent chunks, solve each separately, then combine the results.</p><p>If you have worked with algorithms, you have surely already seen divide and conquer in action. Binary search divides an array in half and searches only on the proper side. Merge sort splits an array in half, sorts both halves, and merges them back together. Tree traversals split a tree into left and right subtrees and visit each one individually.</p><p>The important thing is that we are not just taking something out and solving a smaller problem. We are breaking the input into several non-trivial subproblems and merging their solutions. That is what makes divide and conquer so powerful for some types of problems.</p><p>The outline is as follows:</p><ul><li><p>The Core Pattern</p></li><li><p>Fixed Splitting: Binary Search</p></li><li><p>Trying All Splits: Unique Binary Search Trees</p></li><li><p>Other Divide and Conquer Examples</p></li><li><p>When Divide and Conquer Applies</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>The Core Pattern</h2><p>The divide and conquer pattern follows three steps. First, we divide the input into two or more smaller pieces. Then, we conquer each piece by solving it recursively. Finally, we combine the results from each piece to get the solution for the original input.</p><p>This is different from what we did with the iteration and subproblems patterns. With iteration, we processed one item at a time and collected the result. With subproblems, we removed one element and solved a smaller version of the same problem. In both cases, we were reducing the input by one item.</p><p>Using divide and conquer, we partition the input into multiple nontrivial chunks. For example, if we have an array of 8 elements, we might split it into two arrays of 4. Each of those gets split into 2 arrays of 2. We keep splitting until we reach a base case, solve each base case, and then merge the results back up.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Acz9!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Acz9!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 424w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 848w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 1272w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Acz9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png" width="714" height="204" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:416,&quot;width&quot;:1456,&quot;resizeWidth&quot;:714,&quot;bytes&quot;:145398,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/196046548?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Acz9!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 424w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 848w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 1272w, https://substackcdn.com/image/fetch/$s_!Acz9!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F72e87bd8-a7c8-4675-8f73-6c202e6915f3_3091x883.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The main criterion is that the subproblems must be independent. The solution of one part should not depend on the solution of another part. This is what allows us to solve them separately and then combine the results.</p><p>There are two main variations of this pattern, depending on whether we know where to split.</p><p>In the first variation, we know exactly where to divide the input. Binary search always splits in the middle. Merge sort always divides in the middle. The split point is fixed, and the only decision is which half to look at in the case of binary search, or how to merge the halves in the case of merge sort.</p><p>In the second variation, we do not know where to split. We have to try every possible split point, solve both sides for each split, and see which one gives us the best result. This comes up in grouping problems, where we need to find all ways to partition an input or the optimal way to group elements together.</p><p>We will look at examples of both variations in the following sections.</p><h2>Fixed Splitting: Binary Search</h2><p>Binary search is the simplest example of a divide and conquer algorithm with a fixed split point. We always divide the array in the middle and decide which half to search based on a comparison.</p><p>The problem is straightforward: given a sorted array and a target value, check whether the target is present in the array. The brute-force approach would have to check every element, which takes O(n) time. Binary search does it in O(log n) by halving the search space at each step.</p><p>This is how the algorithm works. We examine the middle element of the array. If it is equal to the target, we are done. If the target is smaller, we search the left half. If the target is larger, we search the right half. We keep doing this until we find the target or run out of elements to check.</p><p>Here is a possible way to implement this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;863726ea-f2e5-4408-80d1-b40cc5e52772&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def binary_search(arr, target, low, high):
    if low &gt; high:
        return False
    
    mid = (low + high) // 2
    
    if arr[mid] == target:
        return True
    elif arr[mid] &gt; target:
        return binary_search(arr, target, low, mid - 1)
    else:
        return binary_search(arr, target, mid + 1, high)</code></pre></div><p>Let&#8217;s walk through an example to grasp how this works. Say we have the array [1, 2, 3, 5, 7, 9, 11, 13] and we are looking for 3. We start with low = 0 and high = 7. The middle index is 3, and the element at that index is 5. Since 3 is smaller than 5, we continue our search in the left half: low = 0, high = 2.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yj9G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yj9G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 424w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 848w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 1272w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yj9G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png" width="496" height="300.2733397497594" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:629,&quot;width&quot;:1039,&quot;resizeWidth&quot;:496,&quot;bytes&quot;:68482,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/196046548?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!yj9G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 424w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 848w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 1272w, https://substackcdn.com/image/fetch/$s_!yj9G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ea0b534-c31a-46fb-b8b7-eeff3e728103_1039x629.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now the middle index becomes 1, and the element there is 2. Since 3 is larger than 2, we search the right half: low = 2, high = 2. The middle index is 2, and the element at that index is the target, so we return true.</p><p>The time complexity is O(log n) because we cut the search space in half at each step. If we have 8 elements, we need at most 3 comparisons. If we have 1024 elements, at most 10 comparisons are needed. The space complexity is also O(log n) due to the recursion stack.</p><p>The crucial thing to observe is that we always know exactly where to split. No need to attempt different split points. The midpoint is always the appropriate choice, and the comparison tells us which half to search. This is why binary search is so efficient.</p><h2>Trying All Splits: Unique Binary Search Trees</h2><p>Now, let&#8217;s look at a problem where we do not know where to divide. Instead of always splitting in the middle, we try every possible split point and combine the results.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/the-divide-and-conquer-breaking-problems">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Bloom Filters Explained ]]></title><description><![CDATA[Fast lookups with a fraction of the memory, when 'probably yes' is good enough]]></description><link>https://newsletter.francofernando.com/p/bloom-filters-explained</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/bloom-filters-explained</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 16 May 2026 07:29:59 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7X0_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0839281-aca3-451a-ac0a-d0bf35d4f8aa_750x414.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 173rd issue of the Polymathic Engineer newsletter.</p><p><a href="https://newsletter.francofernando.com/p/the-dictionary-problem-fast-lookups">In a previous article</a>, we talked about the dictionary problem and discussed different types of data structures that solve it, from unsorted arrays to hash tables and binary search trees. We have seen that hash tables are the best choice when all you need is to check if something is in a collection, since you can look up, add, and remove items in O(1) time.</p><p>But we have also seen that hash tables come with a cost: memory. Storing 100K words in a hash table means keeping all those words in memory, plus the extra space that the hash table needs. What if you are working in a context where memory is limited, like a router processing millions of packets per second, or a distributed database that needs to quickly check if an entry exists before writing to disk?</p><p>This is where Bloom filters come in. A Bloom filter is a data structure that can tell you if an item is in a set using only a small amount of memory with respect to a hash table. The catch is that it is not always right. A Bloom filter can tell you with certainty that something is not in the set. However, when it indicates something is there, it could be inaccurate. These inaccurate answers are known as false positives, and as we will see,  you get to control how often they happen.</p><p>In this article, we will look in detail at how Bloom filters work, why they can give false positives but never false negatives, how to tune them to fit your needs, and where they are used in practice. This is the outline:</p><ul><li><p>How Bloom filters differ from hash tables</p></li><li><p>How Bloom filters work</p></li><li><p>Why are there no false negatives</p></li><li><p>Why are there false positives</p></li><li><p>Tuning the filter</p></li><li><p>Real-world use cases</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, an HTTP server, or Git from scratch using your favorite programming language. Now you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>How Bloom filters differ from hash tables</h2><p>Before we go into the details, it is worth knowing what makes Bloom filters different from hash tables, since both use hashing under the hood. There are three main things to keep in mind.</p><p>First, Bloom filters don't store the actual data. A hash table stores the keys (and maybe their values) in memory. A Bloom filter doesn't do that. The sole question it answers is whether or not an item is in the set.</p><p>Second, Bloom filters use considerably less memory. This is the main reason why they exist. A hash table for 100,000 strings needs to store all those strings plus the internal structure of the table. A Bloom filter for the same 100K strings only needs a bit array, regardless of how long the strings are.</p><p>Third, Bloom filters can give wrong answers. When a Bloom filter says an item is not in the set, it is always right. But it could be inaccurate when it indicates an item is in the set. This is a false positive. There is a trade-off between the memory a Bloom filter employs and how often these false positives happen. The less memory, the more false positives.</p><p>There is also one more thing to be aware of: the basic version of Bloom filters doesn&#8217;t support deletion. Once you add an item, you can&#8217;t remove it. Some advanced variants have been developed to handle removal, but the classic version does not.</p><p>If you can live with these constraints, you get a data structure that is as fast as a hash table for lookups and insertions, but uses only a fraction of the memory.</p><h2>How Bloom filters work</h2><p>A Bloom filter is made of two things: a bit array with m elements, all initially set to 0, and a set of k hash functions. Each hash function takes a key and returns an index in the range between 0 and m-1.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!12yE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!12yE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 424w, https://substackcdn.com/image/fetch/$s_!12yE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 848w, https://substackcdn.com/image/fetch/$s_!12yE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 1272w, https://substackcdn.com/image/fetch/$s_!12yE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!12yE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png" width="750" height="162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:162,&quot;width&quot;:750,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:29229,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/194079630?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!12yE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 424w, https://substackcdn.com/image/fetch/$s_!12yE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 848w, https://substackcdn.com/image/fetch/$s_!12yE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 1272w, https://substackcdn.com/image/fetch/$s_!12yE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4ff6ea9a-c162-4f73-ac8d-f43132cd9ef4_750x162.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The most important thing to remember is that there is no direct connection between the bits in the array and the keys you add to the filter. You specify the value of k when you create the filter. Each key is stored using k bits, one for each hash function. This means that every key you add takes up the same amount of memory, k bits, no matter how lengthy it is. A 5-character word and a 500-character URL both use k bits.</p><h4><strong>Inserting an element</strong></h4><p>When you add a key to the filter, you give it as an input to all the k hash functions. Each function gives you an index in the bit array, and you set the bits at those k positions to 1. For example, let&#8217;s suppose we have a Bloom filter with 15 bits and 3 hash functions, and we want to add the word &#8220;binary.&#8221; We compute:</p><ul><li><p>h0(&#8221;binary&#8221;) = 2</p></li><li><p>h1(&#8221;binary&#8221;) = 6</p></li><li><p>h2(&#8221;binary&#8221;) = 9</p></li></ul>
      <p>
          <a href="https://newsletter.francofernando.com/p/bloom-filters-explained">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Ordering Pattern: Generating All Permutations]]></title><description><![CDATA[How to find every possible arrangement of a set of items]]></description><link>https://newsletter.francofernando.com/p/the-ordering-pattern-generating-all</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-ordering-pattern-generating-all</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 09 May 2026 08:02:08 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!J-kl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 172nd issue of the Polymathic Engineer newsletter. </p><p>In the last article in our series on recursion, we looked at the selection pattern, which lets us find all possible subsets of a set. The most important question was whether or not to include each item.</p><p>In this article, we shift our focus from which items to include to how to arrange them. This is the ordering pattern, and its purpose is to generate all permutations of a set of items.</p><p>The difference between combinations and permutations is easy. With combinations, [1, 2] and [2, 1] are the same because the order of the elements does not matter. With permutations, the order matters. A set of three items, such as [1, 2, 3], has 8 possible subsets but 6 possible permutations: [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1].</p><p>Ordering problems come up a lot in practice. Every time you need to find all possible ways to arrange, schedule, or sequence anything, you need to generate permutations. And, like selection, the ordering pattern employs backtracking to consider all options.</p><p>The outline is as follows:</p><ul><li><p>The Core Ordering Pattern</p></li><li><p>The Swap-Based Approach</p></li><li><p>Permutations of Specific Length</p></li><li><p>Handling Duplicates in the Input</p></li><li><p>Application: Binary Search Tree Orderings</p></li><li><p>When Ordering Applies</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, HTTP server or Git from scratch using your favourite programming language. Noe you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>The Core Ordering Pattern</h2><p>While selection goes through each item and asks whether to include it, ordering takes a different approach. Instead of making include/exclude decisions, it asks: which item comes next?</p><p>The underlying idea is to keep a set of available items. At each step, we pick one item from the set, add it to our current permutation, remove it from the set, and recursively find all permutations of the remaining items. When there are no remaining items, we have built one complete permutation.</p><p>Let&#8217;s start with a simple example, with three items: [1, 2, 3]. In the beginning, all items are available. We could choose 1 first, which leaves us with [2, 3]. From there, we could pick 2, which leaves us with only [3]. Then we can only choose 3, and now we have our first permutation: [1, 2, 3].</p><p>At this point, we backtrack. We put 3 back, put 2 back, and try picking 3 instead. This gives us [1, 3, 2]. We continue this process, trying every possible first item, then every possible second item, and so on.</p><p>The following code snippet shows how to implement this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;16bd9bd0-363b-4f34-8a2a-4472b8fa3961&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def permutations(items):
    result = []
    available = set(items)
    
    def helper(path):
        if not available:
            result.append(path[:])
            return
        
        for item in list(available):
            available.remove(item)
            path.append(item)
            helper(path)
            path.pop()
            available.add(item)
    
    helper([])
    return result</code></pre></div><p>The structure is quite similar to what we have seen with selection. We have a helper function that builds the permutation step by step. The base case is when there are no items left to choose from, meaning we have used all of them and produced a complete permutation.</p><p>In the recursive step, we go through each available item. We take each one out of the set, add it to the path, and call the function again. After the call is over, we go back by taking the item off the path and putting it back in the set.</p><p>One thing to note is that we iterate over a copy of the list of available items instead of iterating over them directly. This is because we are modifying the set inside the loop, and most languages do not allow you to change a collection while iterating over it.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!J-kl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!J-kl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 424w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 848w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 1272w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!J-kl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png" width="1456" height="657" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:657,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:190871,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/82350676?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!J-kl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 424w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 848w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 1272w, https://substackcdn.com/image/fetch/$s_!J-kl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F32f854f5-339c-40ea-8ab6-97762544414f_1912x863.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The time complexity is O(n! &#215; n). The branching factor starts at n and decreases by one at each level, which gives us n &#215; (n-1) &#215; (n-2) &#215; ... &#215; 1 = n! paths. Each time we complete a permutation, we then need O(n) time to copy it into the result. The space complexity is O(n) for the recursion stack and the path we are building.</p><h2>The Swap-Based Approach</h2><p>The set-based approach we looked at is straightforward, but there is a more efficient way that you can also use in interviews. This method does not store a separate list of available items. Instead, it uses the input array itself to track what has been used and what remains available.</p><p>The underlying idea is to divide the array into two parts using an index. All the items before the index already belong to the permutation we are generating. The items from the index onward are the pool of items we can still choose from. At each step, we swap an item from the available portion into the current position and then move to the next index. Let&#8217;s go back to our simple example, [1, 2, 3], to grasp how the process works. </p><p>Initially, the index is equal to 0 because the entire array is available. We swap the first item with itself, which doesn&#8217;t change anything, then we move to index 1. We can now choose either 2 or 3. We choose 2 and swap the second item with itself. Then we go to index 2, pick 3, and get our first permutation: [1, 2, 3]. At this point, we backtrack.</p><p>We go back to index 1 and swap positions 1 and 2. This places 3 in position 1 and 2 in position 2, yielding [1, 3, 2]. We keep doing this until we have executed every potential swap at every slot. </p><p>The following code snippet shows how to implement this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;49352de8-9b47-4ea6-a592-85ca6c958422&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def permutations(items):
    result = []
    
    def helper(index):
        if index == len(items):
            result.append(items[:])
            return
        
        for j in range(index, len(items)):
            items[index], items[j] = items[j], items[index]
            helper(index + 1)
            items[index], items[j] = items[j], items[index]
    
    helper(0)
    return result</code></pre></div><p>The structure is very similar to the set-based version. The base case is when our index reaches the end of the array. This means that we have made a choice for every position and produced a complete permutation.</p><p>In the recursive step, we go through each item from the current index to the end. For each item, we swap it into the current position, call the function again with the next index, and then swap it back to restore the original order.</p><p>The best thing about this method is that we don't have to make copies of the available items at every step. We are directly changing the order of the elements in the array. In practice, this makes it a bit more efficient, even though the worst-case analysis stays the same: O(n! &#215; n) for time and O(n) for space.</p><h2>Permutations of Specific Length</h2><p>Sometimes we don&#8217;t need all the possible permutations of a set, but only those of a specific length. For example, if we have [1, 2, 3, 4], we could want all the two-length permutations: [1, 2], [1, 3], [1, 4], [2, 1], [2, 3], and so on.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/the-ordering-pattern-generating-all">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How to Stop Failures from Spreading Between Services]]></title><description><![CDATA[Practical patterns to protect your services from failing dependencies and excessive load.]]></description><link>https://newsletter.francofernando.com/p/how-to-stop-failures-from-spreading</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/how-to-stop-failures-from-spreading</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 02 May 2026 08:43:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!r9Su!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 171st issue of the Polymathic Engineer newsletter.</p><p>In one of our previous articles, we looked at the most <a href="https://newsletter.francofernando.com/p/why-distributed-systems-fail-and">common root causes of failures</a> in distributed systems and how to use redundancy and fault isolation to contain them. These are protections at the architectural level, which means they work based on how you design and deploy your system.</p><p>In this article, we are going to talk about something more tactical. We will discuss the patterns that stop faults from spreading from one service to another at runtime. These are techniques you can apply to existing systems with little effort.</p><p>The article is split into two parts. The first covers patterns that protect a service when one of its dependencies fails or slows down (downstream resiliency mechanisms). The second covers patterns that protect a service when its callers send more traffic than it can handle (upstream resiliency mechanisms).</p><p>The outline is as follows:</p><ul><li><p>Timeouts</p></li><li><p>Retries</p></li><li><p>Circuit breakers</p></li><li><p>Load shedding</p></li><li><p>Load leveling</p></li><li><p>Rate limiting</p></li><li><p>Constant work</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, HTTP server or Git from scratch using your favourite programming language. Noe you can also try to build your own Claude Code (for free, still in beta).</p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>Timeouts</h2><p>When one service makes a network call, it should always set a timeout. The call fails if no response comes back within a certain amount of time. Without a timeout, the call might never return, and as we already discussed in the previous article, network calls that don&#8217;t return lead to resource leaks. Timeouts are the first protection you can use to find connectivity faults and stop them from cascading from one service to another.</p><p>You might think that setting timeouts is such a simple task that all network libraries do it for you, but that's not the case. When JavaScript's fetch API came out, there was no way to specify a timeout. Go's HTTP package doesn't employ timeouts by default, and the default timeout for the most popular Python library is infinity. Modern HTTP clients for Java and .NET do a better job and often come with default timeouts, but it's still a good idea to set them manually every time.  </p><p>There is a golden rule here: always set timeouts when making network calls, and keep a close eye on third-party libraries that make network calls but don&#8217;t let you configure a timeout. But how do you pick a good timeout value? </p><p>One method is to base it on how many false timeouts you are okay with. For example, if you don&#8217;t mind 0.1% of requests timing out even though they would have worked in the end, you can set the timeout according to the 99.9th percentile of the downstream service&#8217;s response time.</p><p>It is also important to have good monitoring in place to measure the full lifecycle of a network call. This allows you to check how long a network call took, what status code came back, and if it timed out. Without this kind of visibility at the points where your system is integrated, it's much harder to figure out what's wrong with production.</p><p>To avoid repeating the same timeout and monitoring logic everywhere, you can wrap the network call in a library that does both. Another option is a sidecar proxy running on the same machine. This proxy will intercept remote calls and take care of timeouts and monitoring for you.</p><h2>Retries</h2><p>When a network call fails or times out, the caller can either give up or try again. If the failure was caused by a short-lived connectivity issue, retrying after a short wait has a good chance of working. However, retrying immediately will make things worse if the downstream service is already too busy, </p><p>This is why retries should be slowed down. The most frequent strategy is exponential backoff, in which the delay between retries increases with each attempt. For example, if the initial wait is 2 seconds and you double it each time with an 8-second cap, the delays will be 2, 4, 8, 8, 8... seconds.</p><p>This is why retries need to be slowed down. The most common method is <strong>exponential backoff</strong>, in which the delay between retries increases with each attempt. For example, if the initial wait is 2 seconds and you double it each time with a cap at 8 seconds, the delays would be 2, 4, 8, 8, 8... seconds.</p><p>Exponential backoff is helpful, but it has a problem. When a downstream service goes down temporarily, many callers&#8217; requests will fail at the same time. They all retry on a similar schedule, which will cause the downstream service to experience load spikes that make things worse. To fix this, you can add some random jitter to the delay. This spreads out the retries over time and reduces the load on the struggling service.</p><p>Actively retrying right away isn&#8217;t always the only option, either. In batch systems that don&#8217;t need an immediate response, a failed request can be put into a retry queue. The same process, or a different one, can pick it up later and try again.</p><p>Additionally, not every failure is worth trying again. If the error isn&#8217;t temporary, such as the caller not being allowed to access the destination, retrying will fail again. If that is the case, the service should stop retrying right away. Also, if the network call is not idempotent, retrying can cause problems that affect the correctness of the application.</p><p>A last problem that is easy to overlook is <strong>retries amplification</strong>. Let&#8217;s suppose you have a chain of three services: the user calls service A, A calls service B, and B calls another service C. If the call from B to C fails, service B retries. But in the meantime, A sees a longer response time. If A times out, it retries too, and this adds even more load to the chain. If the user&#8217;s client also has retries, the total number of requests goes up quickly.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!r9Su!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!r9Su!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 424w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 848w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 1272w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!r9Su!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png" width="589" height="189.72" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:306,&quot;width&quot;:950,&quot;resizeWidth&quot;:589,&quot;bytes&quot;:210002,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/192933767?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!r9Su!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 424w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 848w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 1272w, https://substackcdn.com/image/fetch/$s_!r9Su!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0314d5d9-4c7b-4712-8619-7de2b23538db_950x306.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This amplification effect puts more pressure on a service the further down the chain it is. If you have large dependence chains, it is safer to only retry at one level and fail fast at all the others.</p><h2>Circuit Breakers</h2><p>Timeouts help you find when a downstream service is slow or unreachable, and retries help you get past short-lived failures. But what if the failure doesn't go away quickly?</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/how-to-stop-failures-from-spreading">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[How machine learning and optimization work well together]]></title><description><![CDATA[From making predictions to making choices.]]></description><link>https://newsletter.francofernando.com/p/how-machine-learning-and-optimization</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/how-machine-learning-and-optimization</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Wed, 29 Apr 2026 15:05:34 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b7dec4b0-9dfb-4b48-ac2f-ac9623dcdb28_1440x686.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Today&#8217;s special issue features a guest post from Tim Varelmann, founder of <a href="https://bluebird-briefings.kit.com/polymathicengineer">Bluebird Optimization</a>. Tim is a reader of our newsletter. He contacted me to ask if there was an opportunity to collaborate on the newsletter as a guest author for an issue, and I gladly agreed because I think what he has to say could be interesting to you all.</p><p>Tim develops optimization software that helps planners of core business operations make sound, data-driven decisions. He holds a PhD in optimization, with studies at RWTH Aachen, UT Austin, University of Queensland, and MIT, and has combined machine learning and optimization in innovative ways across multiple client projects. </p><p>He is also the author of the world&#8217;s first course on GAMSPy (&#8220;Effortless Modeling in Python with GAMSPy&#8221;). If you want a practical framework for combining ML and optimization from Tim, you can find it <a href="http://bluebird-briefings.kit.com/polymathicengineer">here</a>. Now, over to Tim.</p><div><hr></div><h4><strong><a href="https://coderabbit.link/fernando">Your AI shouldn&#8217;t grade its own homework</a></strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oBf2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" width="608" height="365.38461538461536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:875,&quot;width&quot;:1456,&quot;resizeWidth&quot;:608,&quot;bytes&quot;:86772,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw" loading="lazy" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Claude Code writes beautiful code. So does Codex. But here&#8217;s the thing, they also think they write beautiful code. And when you ask an AI to review code it just wrote, you get the intellectual equivalent of a student grading their own exam. Shockingly, they always pass.</p><p><a href="https://coderabbit.link/fernando">CodeRabbit CLI</a> plugs into Claude Code and Codex as an external reviewer, different AI Agent, different architecture, 40+ static analyzers, and zero emotional attachment to the code it&#8217;s looking at. The agent writes, CodeRabbit reviews, and the agent fixes. Loop until clean.</p><p>You show up when there is actually something worth approving.</p><p>One command. Autonomous generate-review-iterate cycles. The AI still does the work. It just doesn&#8217;t get to decide if the work is good anymore.</p><p><a href="https://coderabbit.link/fernando">Free tier available. Try CodeRabbit&#8217;s CLI.</a></p><p>Thanks to the CodeRabbit team for collaborating with me on this newsletter issue.</p><div><hr></div><h2>Machine learning and optimization</h2><p>Every day, you make a lot of decisions based on information that doesn&#8217;t actually tell you what to do. Your navigation app knows there will be heavy traffic at 8 am. That is a prediction. What you do about it is a decision that depends on things the app knows nothing about. </p><p>Can you move your first meeting? Is the alternative route through the school zone worth it? Is today the day you finally just work from home? The same forecast, ten different people, ten different choices.</p><p>A big part of our job as engineers is finding an appropriate solution to a problem. </p><p>Netflix works differently. It predicts how much you will enjoy each show in its catalog (typically using Machine Learning). And from that prediction, the decision about what to recommend to you is almost mechanical: sort by the probability that you will like a show, and fill the three recommendation slots with the top results. Here, knowing the forecast basically means knowing the answer.</p><p>Most real-world problems sit somewhere between these two. Predictions matter a lot. But constraints, trade-offs, and competing priorities sit between the forecast and the action. The gap between them is exactly where machine learning and mathematical optimization (or operations research) have to work together.</p><p><em>A forecast tells you what the world looks like. It takes something else to decide what to do in it.</em></p><p>In my experience, the combinations that actually work follow a small number of recurring patterns. Let me show you three of them.</p><h2><strong>Three Combinations of ML &amp; Optimization</strong></h2><p>I keep seeing the same structural roles in different fields, even when applications look very different at first glance:</p><ul><li><p>Making good decisions in the face of uncertain futures</p></li><li><p>Replacing math that is difficult to compute with clearly structured ML models</p></li><li><p>Optimizing systems where no physical laws exist, only observed behavior</p></li></ul><p>There is a fourth pattern where machine learning is used inside optimization solvers themselves, helping them make internal algorithmic choices. This requires deep solver-internal knowledge, which I do not want to presume here. The three patterns above already provide plenty of substance.</p><p>Let&#8217;s start with the most important one.</p><h2><strong>Pattern 1: Making good decisions when the future is uncertain</strong></h2><p>Inventory management makes this pattern very clear. Some products sell at a steady rate. Others barely move for weeks, then suddenly spike. Imagine two spare parts with the same average annual demand. On paper, they look identical. In reality, you should manage them very differently.</p><p><strong>The danger of a single number</strong></p><p>Here is the problem: rich information often dies somewhere between the ML forecast and the optimization model. A probabilistic forecast (one that shows you a whole range of possible futures) gets boiled down to one number, the expected value, before the optimizer ever sees it. This is poison for the combination of ML and optimization.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-EF6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-EF6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-EF6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg" width="649" height="304" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:304,&quot;width&quot;:649,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-EF6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 424w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 848w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!-EF6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc612d19-f999-4a5a-bb57-d6a72975bcdf_649x304.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Modern ML models are naturally good at producing rich outputs. Distributions. Confidence intervals. Samples from possible futures. That is exactly the information the optimizer needs. Throwing it away and handing over just the average is like hiring a weather forecaster and only asking them whether it will rain - yes or no.</p><p><strong>What optimization can do with the full picture?</strong></p><p>With the full distribution, the optimizer can distinguish between the two spare parts, even though their averages match.</p><p>For the part with steady demand: order just-in-time, keep safety stock lean.</p><p>For the part with spiky demand: an inventory manager might consciously accept a 10% stockout risk during high-demand phases, if that means avoiding expensive external storage. Maybe that trade-off makes financial sense. Maybe a 15% risk of stockout does not, and the cost for external storage would be preferable. The optimizer finds that line. Without the probabilistic input, it cannot even see the question.</p><p><strong>Where it works - and where it breaks</strong></p><p>This pattern shines when:</p><ul><li><p>Decisions have asymmetric consequences. A missing safety-critical spare part that shuts down a production line is a very different problem from a surplus of optional accessories gathering dust.</p></li><li><p>Different futures require fundamentally different responses. Two parts with the same average demand but different distributions need different inventory strategies.</p></li><li><p>Performance must be robust, not just good on average. If a part is both spiky and safety-critical, you might want guaranteed coverage across two consecutive demand spikes - even if that means holding more stock most of the time.</p></li></ul><p>It breaks when uncertainty gets collapsed too early. If the optimizer only sees one number, it will make one-size-fits-all decisions - and quietly leave money on the table.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2><strong>Pattern 2: Replacing math that is difficult to compute with clearly structured ML models</strong></h2><p>Sometimes the core difficulty is not uncertainty, but complexity. As a researcher, I worked on optimization problems involving air separation units. These are industrial plants that cool air to extremely low temperatures. Oxygen liquefies slightly before nitrogen, so this liquefaction helps separate oxygen from nitrogen.</p><p>The physics involved is well known, but it is also very nonlinear and hard to calculate. Also, these equations are already approximations of the quantum-chemical truth of the world. So, in optimization, the real question wasn&#8217;t whether the equations were &#8220;true,&#8221; but whether they could be used to solve a big optimization problem.</p><p>In that case, my coworkers replaced the detailed thermodynamic relationships with a neural network trained on simulation data.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gIl5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gIl5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 424w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 848w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 1272w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gIl5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png" width="301" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:301,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gIl5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 424w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 848w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 1272w, https://substackcdn.com/image/fetch/$s_!gIl5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3cc50d07-ef30-419a-a152-ede8db92d04a_301x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The neural network introduced a small additional approximation error, which was acceptable. In return, the computations became much easier. The optimizer now saw a clean, repeated structure it could work with efficiently.</p><p><em>The win was structure instead of chaos.</em></p><p><strong>Where this pattern works - and where it breaks</strong></p><p>This pattern is particularly powerful when:</p><ul><li><p>Some difficult equations are already approximations, so an additional approximation is not as problematic</p></li><li><p>The true slowdown is computational difficulty, not physical accuracy</p></li></ul><p>It breaks when surrogate models are forced to extrapolate beyond their training data, or when critical structural properties of the original equations are lost in the approximation.</p><h2><strong>Pattern 3: Optimizing systems where no physical laws exist, only observed behavior</strong></h2><p>Some systems simply have no rules in physics that can describe them well. Think of human attention, trust, or preferences. In such cases, behavior must be learned from data.</p><p>In website optimization, for example, ML models can learn how users react to layout changes, rankings, or pricing decisions. This learned behavior is then given to the optimizer, which adjusts the website while respecting multiple other constraints: revenue targets, fairness requirements, budgets, and contracts.</p><p><strong>Where this pattern works - and where it breaks</strong></p><p>This pattern is particularly powerful when:</p><ul><li><p>Human behavior must be observed</p></li><li><p>Sufficient data is available to learn stable behavioral responses</p></li><li><p>Decisions must satisfy non-negotiable constraints (otherwise, an occasional hallucination would be acceptable, but optimization guarantees no hallucinations)</p></li></ul><p>It breaks when learned behavior is treated as static truth, and its drift over time is forgotten.</p><h2><strong>A Match made in Heaven</strong></h2><p>Machine learning is a powerful tool for describing reality from data. Optimization is a strong tool for choosing actions under constraints.</p><p>They work better together when each is used for what it is best at, and when both uncertainty and assumptions are dealt with honestly rather than ignored. Once you find the pattern that your problem fits into, implementation often becomes relatively straightforward.</p><p>If you want to take these ideas further, I have put together a <a href="http://bluebird-briefings.kit.com/polymathicengineer">practical framework for you here</a>. Or just reach out, I am always happy to talk optimization.</p>]]></content:encoded></item><item><title><![CDATA[Linear Regression]]></title><description><![CDATA[How machines learn to draw a line through your data and make predictions.]]></description><link>https://newsletter.francofernando.com/p/linear-regression</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/linear-regression</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 25 Apr 2026 07:48:19 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/5b8798fd-c8b2-4401-8c30-8149b51fdb97_440x279.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 170th issue of the Polymathic Engineer newsletter.</p><p>Linear regression is a fundamental algorithm in machine learning. Even though it is simple and intuitive, many engineer treat it as a black box. They call a function from scikit-learn, get a result, and move on.</p><p>But understanding what happens under the hood is critical because linear regression is the basis for more complicated models like neural networks. In this issue, we break down linear regression from the ground up. </p><p>We start with a simple example, build an intuition for how it works, and then explore the math and algorithms that make it all possible. The outline is as follows:</p><ul><li><p>What is Linear Regression?</p></li><li><p>Building an Intuition: Predicting Salaries</p></li><li><p>Features, Weights, and Bias</p></li><li><p>How to Find the Best Line?</p></li><li><p>Measuring How Good a Model Is: Error Functions</p></li><li><p>From One Feature to Many: Multivariate Linear Regression</p></li><li><p>Polynomial Regression</p></li><li><p>Parameters vs Hyperparameters</p></li><li><p>Real-World Applications</p></li><li><p>Putting Everything Into Practice</p></li></ul><p>Regardless of whether you are an experienced engineer who wants to brush up on the basics or a younger engineer exploring machine learning for the first time, this article will give you a solid understanding of how linear regression works and why it matters.</p><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, HTTP server or Git from scratch using your favourite programming language. Noe you can also try to build your own Claude Code (for free, still in beta). </p><p><a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>What is Linear Regression?</h2><p>Let&#8217;s start with the basics. Say you have a set of data points that roughly look like they are forming a line. The goal of linear regression is to draw the line that passes as close as possible to all of them.</p><p>Think of the data points as houses in a town, and your goal is to build a road that goes through the town. Everyone wants to live near the road, and your job is to make them as happy as possible. The road should go as close as possible to all the homes.</p><p>Of course, this leads to some questions like</p><ul><li><p>What do we mean by &#8220;points that roughly form a line&#8221;?</p></li><li><p>What do we mean by &#8220;a line that passes really close to the points&#8221;?</p></li><li><p>How do we find such a line?</p></li><li><p>Why is this useful in the real world?</p></li></ul><p>We answer all these questions in the following sections.</p><h2>Building an Intuition: Predicting Salaries</h2><p>Let&#8217;s suppose that you want to guess a software engineer&#8217;s salary based on their years of experience. You get some information from a few engineers and get something like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!4dT7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!4dT7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 424w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 848w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 1272w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!4dT7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png" width="1169" height="455" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:455,&quot;width&quot;:1169,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:65678,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/190020077?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!4dT7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 424w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 848w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 1272w, https://substackcdn.com/image/fetch/$s_!4dT7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1716631b-df7f-49bb-bc5a-dadbb4511e46_1169x455.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you plot the data on a coordinate system, with years of experience on the x-axis and salary on the y-axis, you see that the dots make a line that goes up. The more you are experienced, the more money you make (we all know it doesn&#8217;t work exactly like that, but let&#8217;s pretend for the sake of this example).</p><p>Now, let&#8217;s say that a new engineer starts working for your company. She has 4.5 years of experience and you need to make her an offer. There isn&#8217;t a data point for 4.5 years, but you can do it another way: you can find out where 4.5 falls on the line and read the salary. That is linear regression in a nutshell.</p><p>What the model actually learns is a simple equation:</p><p><em>Salary = weight &#215; (years of experience) + bias</em></p><p>The weight shows you how much the salary goes up for each year of experience. The bias is the starting point: starting salary with zero experience. We will get into what these two terms mean in detail in the next section.</p><h2>Features, Weights, and Bias</h2><p>Let&#8217;s have a look at the building blocks of linear regression. There are four things you need to know:</p><ul><li><p><strong>Features: </strong>they are the inputs we use to make a prediction. In our example, years of experience are the feature. But in a more complex model, we could have many features: programming languages known, location, company size, and so on.</p></li><li><p><strong>Label: </strong>they are what we are trying to guess. In our case, the label is the salary.</p></li><li><p><strong>Weights: </strong>they tell the model how much each feature matters. Going back to our equation, the weight might be something like 6,500, since for every extra year of experience, the salary increases by that amount. If the weight was negative, it would mean that more experience is associated with a lower salary, which would be strange. A weight close to zero means the feature barely matters. If you added a feature like <em>the number of monitors </em>on the desk and its weight turned out to be near zero, the model is telling you it doesn&#8217;t help guess the salary.</p></li><li><p><strong>Bias: </strong>it is the starting point. It is the salary the model would guess if the engineer had zero years of experience. In practice, the bias is just there to give the line the flexibility to sit at the right height on the graph.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aBLS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aBLS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 424w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 848w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 1272w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aBLS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png" width="402" height="271" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:271,&quot;width&quot;:402,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22499,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/190020077?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!aBLS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 424w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 848w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 1272w, https://substackcdn.com/image/fetch/$s_!aBLS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb0d57eb4-61f3-4cd1-92ed-3c6905ee9f0b_402x271.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>In other words, the model finds the best weights and bias values that make the line fit the data. That&#8217;s the whole process of learning. We&#8217;ll talk about how it does that in the next part.</p><h2>How To Find the Best Line?</h2><p>You could draw an endless number of lines through a set of data points. The question is: which one is the best? The answer is the line that is as close as possible to all data points. But we need to be more precise. Here, &#8220;Close to all the points&#8221; means the total distance between the line and the data points is as small as possible. </p><p>In this context, the distance between what the model predicts and what the data point says is called an <strong>error </strong>or <strong>residual</strong>. There are two main ways to find this line.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/linear-regression">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Finding All Combinations with the The Selection Pattern]]></title><description><![CDATA[How to recursively explore every possible subset using include/exclude decisions.]]></description><link>https://newsletter.francofernando.com/p/finding-all-combinations-with-the</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/finding-all-combinations-with-the</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 18 Apr 2026 12:47:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!NUaz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 169th issue of the Polymathic Engineer newsletter. This week, we continue our recursion series with the third article.</p><p>In the previous two articles, we discussed the <a href="https://newsletter.francofernando.com/p/mastering-recursion-the-foundations">basics of recursion</a> and looked at the first two patterns: <a href="https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and">Iteration and Subproblems</a>. While these patterns are useful, they don&#8217;t go very deep. Now we move on to a stronger pattern: Selection.</p><p>Selection is arguably the most important recursive pattern you can learn. It frequently comes up in coding interviews, and it is the basis for dynamic programming. A lot of problems that seem hard at first become easy once you realize that they are selection problems in disguise.</p><p>The core concept is easy to understand. You look at a set of items and decide whether or not to include each one in your result. By exploring every possible combination of these choices, you generate every possible subset of the original set. From there, you can filter, count, or optimize to get the answer you need.</p><p>This article will explore how this pattern works and how to apply it to real problems.</p><p>The outline is as follows:</p><ul><li><p>The core selection pattern</p></li><li><p>Counting combinations</p></li><li><p>Generating Combinations</p></li><li><p>Combinations of a specific length</p></li><li><p>The 0/1 Knapsack problem</p></li><li><p>When selection applies</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, or Git from scratch. <a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>The Core Selection Pattern</h2><p>The selection pattern answers a specific type of question: &#8220;Can this problem be solved by finding all possible combinations?&#8221; If the answer is yes, then you can use the same approach every time.</p><p>The starting point is a set of items. For each item, you have two options: include it in the result or leave it out. You go through the items one by one, and at every step, you branch into two paths. One includes the current item, and the other path excludes it.</p><p>As a concrete example, consider the array [1, 2, 3]. You start at the first item. You can either include 1 or exclude it. If you include it, your partial result is [1]. If you exclude it, your partial result is [].</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!NUaz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NUaz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 424w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 848w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 1272w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NUaz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png" width="1082" height="403" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aff94781-9396-411a-ba37-42998198c514_1082x403.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:403,&quot;width&quot;:1082,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111702,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193139411?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!NUaz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 424w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 848w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 1272w, https://substackcdn.com/image/fetch/$s_!NUaz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faff94781-9396-411a-ba37-42998198c514_1082x403.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now you move to the second item. For each of the two partial results, you again have two choices. Include or exclude 2. This gives you four partial results: [1, 2], [1], [2], and []. You continue this process for the third item. Each of the four results branches into two, and you end up with eight total combinations: [1, 2, 3], [1, 2], [1, 3], [1], [2, 3], [2], [3], and []. This is the complete set of subsets. </p><p>There are always 2^n subsets for an array of n elements, including the empty set. The selection pattern generates all of them by examining every possible path through the decision tree.</p><p>The word &#8220;selection&#8221; comes from the choice you make at each step. You are selecting which items to include in your result. People also call this the "combinations" pattern, but I prefer "selection" because it captures what you do: you either choose the item, or you don't.</p><h2>Counting Combinations</h2><p>Before looking at how to generate all the combinations, it helps to start with a simpler task: counting how many combinations exist. This is a good approach since it lets you focus on the recursive structure without worrying about building up results.</p><p>The logic is straightforward. You return 1 if you have reached the end of the array and discovered one valid combination. Otherwise, you make two recursive calls, one which includes the current item and one that excludes it. You add the results together.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;2bd45dfb-ae69-4d6e-8f7d-2f6223b71780&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def count_combinations(arr, i=0):
    if i == len(arr):
        return 1

    include = count_combinations(arr, i + 1)
    exclude = count_combinations(arr, i + 1)

    return include + exclude</code></pre></div><p>Let&#8217;s trace what happens when we run this code with the array [1, 2, 3]. The initial call branches into two: one where we include 1 and one where we don't. Then we move to item 2, and each of those branches splits into two other branches. The same happens for item 3. When we reach the end of the array, we return 1. All those 1s add up as the calls return, and we get 8 at the top.</p><p>You might notice that the include and exclude calls are identical. That is because the number of remaining combinations stays the same regardless of whether you include an item. There are always the same number of paths forward. This will not hold when we add constraints.</p><p>The time complexity is O(2^n) because we make two recursive calls at each level, and the depth is n. The space complexity is O(n) because of the call stack. These numbers match what we expect: there are 2^n combinations, and we visit each one.</p><h2>Generating Combinations</h2><p>Counting is straightforward, but for most problems, you need to actually generate the combinations. This is where things get more interesting. There are two main ways to do this: either build up the results as you return them, or update a passed variable as you go.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/finding-all-combinations-with-the">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Dictionary Problem: Fast Lookups in Large Collections]]></title><description><![CDATA[From simple arrays to hash tables and BSTs: exploring the trade-offs behind one of the most common problems in computer science.]]></description><link>https://newsletter.francofernando.com/p/the-dictionary-problem-fast-lookups</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-dictionary-problem-fast-lookups</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 11 Apr 2026 09:32:49 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/0eea31a5-227a-48a7-b58d-db9a23cbec64_814x412.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 168th issue of the Polymathic Engineer newsletter.</p><p>Let&#8217;s say you are building a spell checker that runs directly in the browser. You have a dictionary of over 100,000 words, and every time the user types something, you need to quickly check if the word exists in your dictionary. If it doesn&#8217;t, you underline it in red.</p><p>The feature sounds easy, but there is a problem: how can you store and search through more than 100,000 words quickly? It would be too slow to send a request to the server for every single word. You need to maintain the dictionary locally and search through it fast, without consuming too much memory.</p><p>This is a classic problem in computer science, since it comes up all the time: checking if a username is already taken or if an email is in a contacts list, or verifying whether a URL exists in a cache are all good examples. The main point is always the same: what is the most efficient way to check if something is in a large collection of items?</p><p>In this article, we will explore several data structures that solve this problem, starting from the simplest approach and working our way up to more sophisticated solutions. Along the way, we will see how each one makes different trade-offs in terms of speed, memory, and flexibility. This is the outline:</p><ul><li><p>Abstract data types</p></li><li><p>Unsorted arrays</p></li><li><p>Sorted arrays and binary search</p></li><li><p>Hash tables</p></li><li><p>Binary search trees</p></li><li><p>Comparing the options</p></li></ul><div><hr></div><h4><strong><a href="https://coderabbit.link/fernando">Your AI shouldn't grade its own homework</a></strong></h4><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oBf2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png" width="608" height="365.38461538461536" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:875,&quot;width&quot;:1456,&quot;resizeWidth&quot;:608,&quot;bytes&quot;:86772,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oBf2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 424w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 848w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1272w, https://substackcdn.com/image/fetch/$s_!oBf2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F66478278-656b-4112-a0f1-49bb03ea0435_1558x936.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Claude Code writes beautiful code. So does Codex. But here&#8217;s the thing, they also think they write beautiful code. And when you ask an AI to review code it just wrote, you get the intellectual equivalent of a student grading their own exam. Shockingly, they always pass.</p><p><a href="https://coderabbit.link/fernando">CodeRabbit CLI</a> plugs into Claude Code and Codex as an external reviewer, different AI Agent, different architecture, 40+ static analyzers and zero emotional attachment to the code it&#8217;s looking at. The agent writes, CodeRabbit reviews, and the agent fixes. Loop until clean.</p><p>You show up when there&#8217;s actually something worth approving.</p><p>One command. Autonomous generate-review-iterate cycles. The AI still does the work. It just doesn&#8217;t get to decide if the work is good anymore.</p><p><a href="https://coderabbit.link/fernando">Free tier available. Try CodeRabbit's CLI.</a></p><p>Thanks to the CodeRabbit team for collaborating with me on this newsletter issue.</p><div><hr></div><h2>Abstract data types</h2><p>Before jumping into specific solutions for the dictionary problem, it is worth taking a step back and talking about abstract data types.</p><p>An abstract data type (ADT) defines what operations a data structure should support, without saying anything about how those operations are really done. It is a contract: you know what you can do with it, but the internal details are hidden.</p><p>The reason why it&#8217;s important to distinguish between abstract data types and concrete data structures is that it forces you to figure out what you need before thinking about how to make it work.</p><p>The ADT we care about here is the dictionary, which is also known as a symbol table, associative array, or map. The ADT dictionary keeps a collection of (key, value) pairs and supports three operations:</p><ul><li><p><strong>insert(key, value)</strong>: adds a new pair to the collection</p></li><li><p><strong>remove(key)</strong>: removes the pair associated with the given key</p></li><li><p><strong>contains(key)</strong>: checks if a key exists and returns the associated value</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KPNP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KPNP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 424w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 848w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 1272w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KPNP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png" width="625" height="316.33906633906633" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:412,&quot;width&quot;:814,&quot;resizeWidth&quot;:625,&quot;bytes&quot;:164434,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!KPNP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 424w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 848w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 1272w, https://substackcdn.com/image/fetch/$s_!KPNP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5acbb453-0187-4ab2-95a0-d14370cc1a34_814x412.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The most crucial thing is that there is only one copy of each key in the collection, and you may get any value directly by using its key. The easiest way to think about this is to consider a regular array as a special case.</p><p>In an array like [&#8221;the&#8221;, &#8221;lazy&#8221;, &#8221;fox&#8221;], the keys are simply the indices 0, 1, and 2, and you can always get a value by providing its position. A dictionary takes this idea further by letting you use keys from almost any domain, such as strings, numbers, or objects.</p><p>For the above-mentioned spell checker problem, we actually need a simpler version of the dictionary called a set. A set only knows if a key is there or not; it doesn&#8217;t store any values with the keys. You can think of it as a dictionary where values are always either true or false.</p><h2>Unsorted arrays</h2><p>The simplest way to solve our spell checker problem is to dump all 100,000 words into an unsorted array. You don't have to do anything special. You simply store the words in the order you get them.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7QB6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7QB6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 424w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 848w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 1272w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7QB6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png" width="359" height="95.91603053435115" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:105,&quot;width&quot;:393,&quot;resizeWidth&quot;:359,&quot;bytes&quot;:56843,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!7QB6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 424w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 848w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 1272w, https://substackcdn.com/image/fetch/$s_!7QB6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F76854f3e-b8f7-49c0-a77c-20df58ebc7dc_393x105.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>It is easy to add a new word since you can just put it at the end of the array. That takes O(1) time on average. If you don't care about keeping things in order, it is also quick to remove a word. You just swap it with the last element and make the array smaller.</p><p>However, things fall apart when it comes to search. If someone types "algorithm" and you need to see whether it is in the dictionary, the only option is go through the array word by word, starting at the beginning and proceeding until you find it or reach the end. In the worst case, that is O(n), which means that for 100,000 words, you may have to look at all the 100,000 entries before you can notify the user that the word is spelled correctly.</p><p>Now imagine doing this for every single word the user types. A short paragraph of 200 words would mean up to 20 million comparisons in the worst case. That is not going to work for a feature that needs to feel instant.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!PVEr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!PVEr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 424w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 848w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 1272w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!PVEr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png" width="650" height="199.55357142857142" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:447,&quot;width&quot;:1456,&quot;resizeWidth&quot;:650,&quot;bytes&quot;:41164,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!PVEr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 424w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 848w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 1272w, https://substackcdn.com/image/fetch/$s_!PVEr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec551de7-ce3e-4e26-903d-084ff4a72a3d_1546x475.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The one good thing about the unsorted array is that it is simple. No extra memory, no upfront cost, and insertion is as fast as it gets. But the O(n) lookup makes it useless for anything beyond very small collections. We need a faster way to search.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.francofernando.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">The Polymathic Engineer is a reader-supported publication. To receive new posts and support my work, consider becoming a free or paid subscriber.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Sorted arrays and binary search</h2><p>What if we sort the array first? We could take advantage of the ordering to find things much faster instead of scanning word by word.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!R9QW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!R9QW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 424w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 848w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 1272w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!R9QW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png" width="393" height="106" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:106,&quot;width&quot;:393,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57073,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!R9QW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 424w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 848w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 1272w, https://substackcdn.com/image/fetch/$s_!R9QW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F71cce44d-1f56-4e50-9958-c8018f9478c7_393x106.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>You probably already do this without thinking about it. When you look up a word in a paper dictionary, you don&#8217;t read every entry in a paper dictionary from the beginning. You open it somewhere in the middle, look at the first word on that page, and choose whether to go ahead or backward. If you are looking for &#8220;binary&#8221; and you land on the page starting with &#8220;method,&#8221; you know you can ignore everything after that page and focus on the first half.</p><p>This is exactly how binary search works. You start in the middle of the array, compare the target word with the word at that position, and then discard half of the remaining elements based on the result. Then you repeat the process on the surviving half. Each step cuts the search space in two, so it takes at most O(log n) comparisons to find any word. For 100,000 words, that is circa 17 comparisons, which is a huge improvement with respect to the unsorted array.</p><p>The problem is that keeping the array sorted comes at a cost. It takes O(nlogn) time to sort it in the first place, which is acceptable if you only do it once. But you have to find the correct place for the new word and move everything that comes after it every time you want to add a new word. That is O(n) for each insertion. Removing a word has the same issue because you need to shift elements to fill the gap.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GhY-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GhY-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 424w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 848w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 1272w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GhY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png" width="617" height="197.89766483516485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:467,&quot;width&quot;:1456,&quot;resizeWidth&quot;:617,&quot;bytes&quot;:43192,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!GhY-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 424w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 848w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 1272w, https://substackcdn.com/image/fetch/$s_!GhY-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0e10cb5e-ba4a-4652-9aa5-c41046ecc0b5_1528x490.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>If the dictionary doesn't change, a sorted array could be a reasonable solution for the spell checker example. But if the dictionary changes a lot, the O(n) cost of adding and removing words will be a problem. We need something that can quickly look up and add things at the same time.</p><h2>Hash tables</h2><p>The idea behind hash tables is simple. What if instead of searching through the array, we could compute exactly where a word should be stored?</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8vEL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8vEL!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 424w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 848w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 1272w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8vEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png" width="505" height="284.31102362204723" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b3d84928-26f0-4fea-aee0-367809aea271_762x429.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:429,&quot;width&quot;:762,&quot;resizeWidth&quot;:505,&quot;bytes&quot;:81277,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8vEL!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 424w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 848w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 1272w, https://substackcdn.com/image/fetch/$s_!8vEL!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb3d84928-26f0-4fea-aee0-367809aea271_762x429.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A hash table uses a hash function to do this. You give it a word, and it gives you back a number. This number is an index in an array. To insert the word &#8220;binary,&#8221; you run it through the hash function, get back something like 2,512, and store the word at that position. To check if &#8220;binary&#8221; is in the dictionary, you run the same function, look at position 4,712, and see if it is there. No scanning, no sorting, or comparing.</p><p>This makes both insertion and lookup O(1) on average, which is exactly what we were looking for. However, there is a catch. Since the hash function maps a very large set of possible words to a much smaller array, it is only natural that two different words will sometimes end up in the same position. This is what is called a collision. For example, both "binary" and "garden" might hash to index 2,512.</p><p>There are two common ways to handle collisions. With <strong>chaining</strong>, each position in the array has a short list, and you add colliding words to that list. With <strong>open addressing</strong>, you look for the next available slot in the array when a collision happens. Both ways work well as long as the array is large enough compared to the number of elements it holds.</p><p>One distinction to keep in mind is the one between hash maps and sets. You can use a hash map to link a value to a key, like putting a word and its meaning together. A hash set only keeps track of whether a key exists or not. We don't require definitions for the spell checkers; we just need to know if the word is there. All we need is a hash set.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kl1C!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kl1C!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 424w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 848w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 1272w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kl1C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png" width="678" height="212.8063186813187" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:457,&quot;width&quot;:1456,&quot;resizeWidth&quot;:678,&quot;bytes&quot;:49440,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Kl1C!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 424w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 848w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 1272w, https://substackcdn.com/image/fetch/$s_!Kl1C!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe53b3e4b-538b-41a3-840e-f9b2e50839c3_1530x480.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>With O(1) on average for all the operations that matter, hash tables seem like the clear winner. But they come with a cost: memory. A hash table needs an array that is larger than the number of elements you plan to store, plus extra space to deal with collisions. </p><h2>Binary search trees</h2><p>A binary search tree (BST) takes a different approach. Instead of using a hash function to find where a word goes, it sorts words based on how they compare to each other.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!njTw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!njTw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 424w, https://substackcdn.com/image/fetch/$s_!njTw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 848w, https://substackcdn.com/image/fetch/$s_!njTw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 1272w, https://substackcdn.com/image/fetch/$s_!njTw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!njTw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png" width="452" height="284" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:284,&quot;width&quot;:452,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39613,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!njTw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 424w, https://substackcdn.com/image/fetch/$s_!njTw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 848w, https://substackcdn.com/image/fetch/$s_!njTw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 1272w, https://substackcdn.com/image/fetch/$s_!njTw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6b147e2-19ed-4f4f-a090-549b463745b5_452x284.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A BST is a tree where each node holds a key, and for every node, all the keys in its left subtree are smaller and all the keys in its right subtree are larger. To add a new word, you start at the root and compare it to the current node. If the word is smaller, you go left. If it is larger, you go right. You keep doing this until you reach an empty spot, and that is where the word goes.</p><p>Lookup works similarly. To check if "binary" is in the tree, you start at the root and go down the comparisons. If you get to the word, it is there. If you get to an empty space, it isn't. The number of comparisons is equal to the height of the tree because you get one level deeper each time.</p><p>Things start to get interesting here. If the tree is balanced, its height is O(logn), which means that all operations, such as insertion, removal, and lookup, take O(logn) time. It is the same as binary search on sorted arrays, but it doesn't cost O(n) to add or remove items.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!gO6v!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!gO6v!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 424w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 848w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 1272w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!gO6v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png" width="692" height="209.12087912087912" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:440,&quot;width&quot;:1456,&quot;resizeWidth&quot;:692,&quot;bytes&quot;:44509,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!gO6v!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 424w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 848w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 1272w, https://substackcdn.com/image/fetch/$s_!gO6v!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd0fb433e-c5bf-4f43-97db-bae72a7dbae6_1545x467.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The main benefit of BSTs over hash tables is ordering. Since the words are organized by comparison, a BST can give you things like finding all words between &#8220;apple&#8221; and &#8220;banana,&#8221; or getting the word that comes right before or after a given one. In a hash table, these operations take O(n), whereas in a BST, they take O(log n).</p><p>For the spell checker, we don&#8217;t need any of that. But if you are building something like an autocomplete feature, where you need to find all words within a range, a BST is the better choice.</p><h2>Comparing the options</h2><p>Now that we have gone through the main data structures, let&#8217;s put them side by side and see how they compare.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Dndm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Dndm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 424w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 848w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 1272w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Dndm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png" width="630" height="258.3173076923077" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:597,&quot;width&quot;:1456,&quot;resizeWidth&quot;:630,&quot;bytes&quot;:83273,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/193795555?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Dndm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 424w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 848w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 1272w, https://substackcdn.com/image/fetch/$s_!Dndm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd112a473-dd85-4743-aa3d-ba011b5ab23a_1528x626.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If all you need is to check whether an element is in a collection and don&#8217;t care about the order, hash tables are the clear winner. O(1) on average for lookup, insertion, and removal is hard to beat.</p><p>If order is important, BSTs are a better choice. A BST can let you find all the words in a range, get the next or previous element, or get the whole collection in sorted order in O(log n) time. Those operations would take O(n) or more time using a hash table.</p><p>Sorted arrays are a solid option when the data doesn&#8217;t change. You pay the O(n log n) cost once to sort, and then you get O(log n) lookups with no extra memory. But if you need to add or remove things often, the O(n) cost makes those operations impractical.</p><p>Unsorted arrays are only worth considering for very small collections where simplicity and cache speed up outweigh the slow lookup.</p><p>For the spell-checker problem, the hash table is the best approach. But something is worth considering: a hash table for 100,000 words still occupies a significant amount of memory. </p><p>What if we could trade a small amount of accuracy for a huge reduction in memory? What if we could have a data structure that is as fast as a hash table but uses only a fraction of the space, at the cost of occasionally saying a word is in the dictionary when it is not?</p><p>That is exactly what Bloom filters do, and we will explore them in a follow-up article.</p>]]></content:encoded></item><item><title><![CDATA[Why Distributed Systems Fail and How to Limit the Damage]]></title><description><![CDATA[A look at the most common failure modes and the techniques to reduce their impact.]]></description><link>https://newsletter.francofernando.com/p/why-distributed-systems-fail-and</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/why-distributed-systems-fail-and</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 04 Apr 2026 05:30:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uJXx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f1391e2-e82d-49f3-af33-ab5be5edb902_645x395.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 167th issue of the Polymathic Engineer.</p><p>A rule of thumb when working on a distributed system is to assume that anything can go wrong. The way engineers usually deal with this is by using scalability patterns like data partitioning, functional decomposition, and replication.</p><p>The problem with these patterns is that they all add more moving parts and make our systems more complicated. Since each part has a chance of failing, the more there are, the higher the chance that at least one of them will fail at any given moment. Software crashes, power outages, hardware faults, and memory leaks can happen all the time.</p><p>This isn't only a worry in theory. As we discussed in <a href="https://francofernando.substack.com/p/availability">another article</a>, a system can only be down for approximately 15 minutes a day to guarantee it is available 99.99% of the time. For three nines, that drops to 43 minutes per month. In general, the more nines you want, the faster your system needs to detect failures and recover from them when they occur. There is no time for a person to notice anything went wrong and fix it.</p><p>In this article, we will discuss the most common root causes of failures in distributed systems and see how to address them. The outline is as follows:</p><ul><li><p>Common root causes of failures</p></li><li><p>Managing risk</p></li><li><p>Redundancy</p></li><li><p>Correlation</p></li><li><p>Fault isolation</p></li><li><p>Shuffle Sharding and Cellular Architecture</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, or Git from scratch. <a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>Common root causes of failures</h2><p>To build fault-tolerant distributed systems, you first need to fully understand what can go wrong. Let's start looking at the most common failure modes you can encounter.</p><h4>Infrastructure Faults</h4><p>Hardware doesn&#8217;t last forever. Hard drives fail, memory modules go bad, and network cards stop working. Power outages happen, and fiber cuts or electrical storms can take down whole data centers. The good news is that redundancy makes it straightforward to remedy these infrastructural problems. The underlying idea is simple: if one of your servers goes down, the others may take over.</p><h4>Incorrect Error Handling </h4><p>A <a href="https://scispace.com/pdf/an-analysis-of-production-failures-in-distributed-data-igrujmovmy.pdf">study of user-reported failures</a> from well-known distributed data storage showed an unexpected result. Most major failures were not caused by infrastructure issues. They were caused by mistakes made when dealing with non-fatal faults. The bugs were very easy to fix. In some cases, error handlers didn&#8217;t even look at errors; in others, handlers were only partially set up, or caught too generic exceptions. The main point was that most bugs could have been found with simple unit tests.</p><h4>Configuration Changes </h4><p>Changes to configurations cause failures more often than most engineers think, and it is not always a simple mistake like a typo in a database connection string that makes them risky. One example is a valid configuration change turning on a feature flag that hasn&#8217;t been used in years. Even if there is a code path, and the value looks correct, the behavior is no longer what was meant. </p><p>Configuration changes are challenging because they may not take effect right away. A change that is not valid might not be seen for hours or days if an application only gets a configuration value when a certain code path runs. This is why configuration should be managed like code: version-controlled, reviewed, and released incrementally.</p><h4>Single points of failure</h4><p>A single point of failure is any component that, brings the entire system down when it fails. The tricky part is that they are not always obvious. </p><p>People are a surprisingly common one. If someone has to manually execute a sequence of operational steps in the right order without making a mistake, it is only a matter of time before something goes wrong. Computers, on the other hand, are much better at executing instructions. This is why you should automate whenever possible.</p><p>But systems have non-human SPOFs as well. DNS is a good example: if clients can&#8217;t resolve your domain, it doesn&#8217;t matter how healthy your servers are. TLS certificates are another one: if yours expires, every client connection will be refused. </p><p>When designing a system, a useful exercise is to go through each component and ask what happens if it disappears. Some SPOFs can be removed with redundancy. Others can&#8217;t, but you can at least work to reduce how much damage they cause when they do fail.</p><h4>Network faults</h4><p>When two processes talk to each other over the network, a lot can go wrong. Messages might get lost, arrive late, or be sent more than once. The sender rarely knows which of these things happened because they only see a timeout. </p><p>But timeouts are the easy case. A more difficult situation is when the network doesn&#8217;t completely fail; it just gets slow. A process sends a request and gets a response, but it takes 10 times longer than usual. There is no error to catch and no timeout to trigger. The caller just waits, hanging on to resources while they do. These partial failures are sometimes called <em>gray failures</em> and are considerably harder to find than a clean crash. A slow dependency can silently bring the entire system to a halt before anybody knows something is wrong.</p><h4>Resource leaks</h4><p>Every process has a limited number of resources it can use, such as memory, threads, file handles, and database connections. When a bug causes one of these resources not to be released properly, you get a leak. The hard part is that leaks don&#8217;t show up right away. A slow memory leak might take days to fill up the heap. A connection pool that loses one connection per hour won&#8217;t cause problems until there are none left.</p><p>This is what makes resource leaks so dangerous: they pass all the tests in staging but don't show up in production until the app has been running long enough. And when they do ultimately fix the problem, it often doesn't seem like the original defect at all. A leaky connection doesn't make a sound, but it just shows up as a strange timeout in another part of the system.</p><h4>Load pressure</h4><p>A system can perform perfectly fine under normal conditions, but it can break down when the load gets too high. The hard aspect is that the extra load doesn't necessarily come from the users. Sometimes the spike comes from places you don&#8217;t expect: a web scraper hitting your API thousands of times per second, or a denial-of-service attack flooding your servers with traffic.</p><p>What makes load-related failures difficult is that they can turn a healthy system into an unhealthy one very quickly. When the thread pool is full, a service that responds in 50 milliseconds under normal load can take 5 seconds. And that slowdown does not stay in one place, but extends to all the other services that are waiting for a response.</p><h4>Cascading failures</h4><p>Cascading failures happen when a fault in a component causes faults in some others, which then trigger more faults, and so on. A common example is when a server gets too many requests, starts to answer slowly, and callers start to time out. Those callers retry, adding even more load to the already struggling server. The situation gets worse and worse until the whole system grinds to a halt.</p><p><em>Metastable failures</em> are the most dangerous kind of cascade failures. This happens when the system gets stuck in a bad state that doesn't go away even after the original cause has gone away. For example, a short spike in traffic creates a lot of retries. The retries themselves create enough load to produce more retries, and the system persists in this self-sustaining failure mode long after the original spike is over. When this happens, it is not possible to just wait for the problem to pass. The system needs an intervention to break the cycle.</p><h2>Managing Risk</h2><p>By now, the list of things that can go wrong may seem a bit too long. But just because a mistake could happen doesn't imply we have to fix it immediately.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/why-distributed-systems-fail-and">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[What Really Makes a Succesful Software Engineer]]></title><description><![CDATA[Five skills no course teach you, but matter more than your tech stack.]]></description><link>https://newsletter.francofernando.com/p/what-really-makes-a-succesful-software</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/what-really-makes-a-succesful-software</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 28 Mar 2026 11:31:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!Gb7G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Gb7G!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Gb7G!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 424w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 848w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 1272w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Gb7G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png" width="1254" height="508" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:508,&quot;width&quot;:1254,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:332980,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/192201133?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!Gb7G!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 424w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 848w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 1272w, https://substackcdn.com/image/fetch/$s_!Gb7G!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff53db50e-3d1d-4dff-a350-87a2860212bb_1254x508.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Hi Friends,</p><p>Welcome to the 166th issue of the Polymathic Engineer newsletter.</p><p>When you think about becoming a better software engineer, your mind probably goes straight to technical skills. Learning another framework or mastering a programming language, or practicing system design patterns. And yes, these things matter because they are what get you throug&#8230;</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/what-really-makes-a-succesful-software">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Recursion in Practice: Iteration and Subproblems ]]></title><description><![CDATA[How to apply the first two recursive patterns to real problems.]]></description><link>https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 21 Mar 2026 09:32:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!HcFg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends, </p><p>Welcome to the 165th issue of the Polymathic Engineer. This week, we keep going our recursion series with the second article.</p><p>In the <a href="https://newsletter.francofernando.com/p/mastering-recursion-the-foundations">previous article</a>, we covered the foundations: base cases, recursive steps, return strategies, tracing code, and complexity analysis. Now it&#8217;s time to put those basics to work.</p><p>This article covers the first two recursive patterns: Iteration and Subproblems. These are the most approachable patterns and a natural starting point before tackling all the others.</p><p>The outline is as follows:</p><ul><li><p>The Iteration pattern</p></li><li><p>Iterating forward and backward over arrays</p></li><li><p>Inserting at the bottom of a stack</p></li><li><p>Nested iteration with recursion</p></li><li><p>The Subproblems pattern</p></li><li><p>Towers of Hanoi</p></li><li><p>Checking if a string is a palindrome</p></li><li><p>The stair step problem</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, or Git from scratch. <a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>The Iteration Pattern</h2><p>The iteration pattern is the simplest of the six. The basic idea is to replace a for loop with a recursive function. Each recursive call handles a step of the iteration, and the base case is when there are no more elements to process.</p><p>At first, this may seem like a waste of time. Why change a for loop that already works to recursion? There are a few times when it makes sense.</p><p>The first is when you need to access data in reverse order. Consider printing a linked list backward. With iteration, you would have to push all the values onto a stack and then pop them off. With recursion, the call stack does this for you automatically. The code becomes shorter and easier to follow.</p><p>The second is when the recursion takes care of memory for you. Some problems need temporary storage that matches the structure of the input. You don&#8217;t have to explicitly create and manage that storage yourself; instead, you can let the recursive calls hold the data in their stack frames. We will see a concrete example of this when we insert an element at the bottom of a stack.</p><p>The third is when you use functional programming. Languages like Haskell or certain styles of JavaScript avoid mutable state entirely. Recursion takes the place of loops in these contexts because there is no loop that doesn&#8217;t change a counter variable.</p><p>There is one thing to keep in mind: the iteration pattern doesn&#8217;t usually improve time complexity. Most of the time, the recursive version does the same amount of work as the iterative one. The advantage is that the code is cleaner, not that it runs faster. And since recursion uses stack frames, it can actually use more memory than a simple loop.</p><p>When you use this pattern, there are two questions to ask yourself. The first is: what is the iterative step? In a regular for loop, this might be i + 1, or i + 2, or something more complex. Your recursive call will mirror this. The second is: which way do you want to go? Going forward or backward? The answer affects where you put the recursive call relative to the work you do in each step. We'll see both directions in the next section.</p><h2>Iterating Forward and Backward</h2><p>Let&#8217;s start with a simple example: print every element in an array. Here is the iterative version (I know the code is not pythonic but it is better for the sake of the examples):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;7ef6ba97-325a-49e9-9b78-2319af186948&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def print_array(arr):

    for i in range(len(arr)):

        print(arr[i])</code></pre></div><p>To make this code recursive, we need to think about what happens at each step of the iteration. In this case, we move from index i to index i + 1. The base case occurs when the index reaches the array's length, which means that there are no more elements to print. The recursive version does the same. It prints the current element, then moves to the next. When it runs out of elements, it stops.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;265093d2-80c9-4985-857c-d4c967026252&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def print_array(arr, i=0):
    if i == len(arr):
        return

    print(arr[i])
    print_array(arr, i + 1)</code></pre></div><p>Now here is something interesting. If you swap the order of the last two lines, you get the array printed in reverse:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;c975c189-af3d-4cb1-b11a-35b03ae2c1e6&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def print_array_reverse(arr, i=0):
    if i == len(arr):
        return

    print_array_reverse(arr, i + 1)
    print(arr[i])</code></pre></div><p>The only change is that the print statement comes after the recursive call. This means we first go to the end of the array, then print the elements as the calls come back. The last element in the array is printed first, and so on.</p><p>This is a pattern you will see often: the order of the recursive call relative to the work determines the direction of processing. If you do the work first and then recurse, you process forward. If you recurse first and then do the work, you process backward.</p><p>You can use the same idea when you want to return a result rather than print it. As we discussed in the first article, you can either use a passed variable or build up the result as you return. Here is an example that sums all the elements in an array with the build-up approach:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;a1ac03a4-f360-4576-a2b6-e412338b21e9&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def sum_array(arr, i=0):
    if i == len(arr):
        return 0

    return arr[i] + sum_array(arr, i + 1)</code></pre></div><p>The function finds the sum from index i to the end. The base case says that the sum of an empty slice is 0. The recursive step states that the sum is the current element plus the sum of everything after it. As the calls return, the values add up.</p><p>If you prefer using a passed variable, the code looks like this:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;04eae0cd-1c94-479e-975a-a4f468ba8136&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def sum_array(arr, i=0, result=None):
    if result is None:
        result = [0]

    if i == len(arr):
        return result[0]

    result[0] += arr[i]
    return sum_array(arr, i + 1, result)</code></pre></div><p>Both approaches lead to the same answer. The build-up version is usually cleaner for simple cases, while the passed-variable can be better as the logic gets more complex.</p><h2>Inserting at the Bottom of a Stack</h2><p>Now, let&#8217;s look at a problem where recursion does more than just take the place of a for loop. You have to put something at the bottom of a stack without using any extra data structures.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HcFg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HcFg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 424w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 848w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 1272w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HcFg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png" width="244" height="241.27678571428572" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9e94954f-53d6-42d3-ace9-615351e76822_448x443.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:443,&quot;width&quot;:448,&quot;resizeWidth&quot;:244,&quot;bytes&quot;:134067,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/189814178?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HcFg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 424w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 848w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 1272w, https://substackcdn.com/image/fetch/$s_!HcFg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e94954f-53d6-42d3-ace9-615351e76822_448x443.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>If you try to do this iteratively, you run into a problem. You can only access the top of a stack, so you have to pop everything off, insert your item, and push everything back on. This requires a temporary stack to hold the elements (In Python, we use a list as a stack, with append() to push and pop() to remove from the top):</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;86348a9e-417a-4d32-ad2c-7ebf2d67b8f0&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def insert_at_bottom_iterative(stack, item):
    temp = []
    while stack:
        temp.append(stack.pop())

    stack.append(item)
    while temp:
        stack.append(temp.pop())</code></pre></div><p>The code works, but you need to create and maintain the temporary stack. When you use recursion, the call stack takes care of this implicitly. The recursive approach pops elements off one at a time, makes a recursive call, and then pushes each element back on after the call comes back. The base case is when the stack is empty, at which point we insert the item. As the recursive calls return, each previously popped element gets pushed back on top:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;f8aeb9db-74ff-4f60-b17d-369335e05f30&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def insert_at_bottom(stack, item):
    if not stack:
        stack.append(item)
        return

    top = stack.pop()
    insert_at_bottom(stack, item)
    stack.append(top)</code></pre></div><p>Let&#8217;s trace through this with a concrete example. Suppose the stack is [1, 2, 3, 4] (with 4 on top) and we want to insert 5 at the bottom.</p><p>The first call pops 4 and saves it. The stack is now [1, 2, 3]. The second call pops 3. The stack is [1, 2]. The third call pops 2. The stack is [1]. The fourth call pops 1, leaving the stack empty.</p><p>Now we hit the base case. We append 5, so the stack is [5]. Then the calls start coming back. The fourth call appends 1, giving us [5, 1]. The third call appends 2, giving us [5, 1, 2]. The second call appends 3, giving us [5, 1, 2, 3]. The first call appends 4, and we end up with [5, 1, 2, 3, 4].</p><p>The time complexity is O(n) because we need to pop and push each element once. The space complexity is also O(n) since the call stack must hold all popped elements. This is the same as the iterative version, but the recursive code is shorter and easier to read.</p><h2>Nested Iteration with Recursion</h2>
      <p>
          <a href="https://newsletter.francofernando.com/p/recursion-in-practice-iteration-and">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Mastering Recursion: the Foundations]]></title><description><![CDATA[Understanding when to use recursion, how to structure recursive functions, and how to go through any recursive code.]]></description><link>https://newsletter.francofernando.com/p/mastering-recursion-the-foundations</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/mastering-recursion-the-foundations</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Sat, 14 Mar 2026 07:31:04 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BbZc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faefe698d-ed70-48d8-ae3f-e6884e0f6f92_832x379.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 164th issue of the Polymathic Engineer newsletter.</p><p>Recursion is one of those topics that many software engineers &#8220;understand&#8221; in theory but struggle with in practice. They know the textbook definition (a function that calls itself). They have seen the Fibonacci example multiple times. But when they are asked to solve a recursive problem in an interview, something doesn&#8217;t work.</p><p>The problem is not that they are not smart, but that they don&#8217;t have the correct mental models. It took me a while to figure this out. When someone explained it to me, I was able to follow recursive code, but writing it from scratch often felt like guessing. The turning point was when I stopped thinking of recursion as a single, isolated technique and began seeing it as a set of patterns, each with its own structure.</p><p>In this article, we will start building the basic mental models you need to actually use recursion. In future articles, we will apply these foundations to specific patterns and problems.</p><p>The outline is as follows:</p><ul><li><p>When to use recursion</p></li><li><p>The two building blocks: base case and recursive step</p></li><li><p>Three ways to return results from recursive functions</p></li><li><p>How to trace any recursive code (even when you&#8217;re lost)</p></li><li><p>Time and space complexity for recursion</p></li><li><p>A preview of the six recursive patterns</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, or Git from scratch. <a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>When to Use Recursion</h2><p>There is a fact about recursion that people often overlook: any problem that can be solved recursively can also be solved iteratively. There is no case where you need to use recursion.</p><p>So the right question isn&#8217;t &#8220;can I use recursion here?&#8221; but rather &#8220;is recursion easier here?&#8221; There are a few questions you can ask yourself to figure this out.</p><ol><li><p><strong>Does the problem break down into subproblems? </strong>This is mostly what recursion is about. If a problem breaks into smaller versions of itself, recursion is probably a good fit. For example, sorting an array can be decomposed into sorting two halves and merging them. Finding a path through a maze can be broken into taking one step and finding a path from there.</p></li><li><p><strong>Could you solve the problem using an arbitrary number of for loops? </strong>This point gets people confused. Let&#8217;s say you are asked to print all possible 6-digit numbers. It sounds easy: you can just write six nested for loops, one for each digit. But what if you are told to print all possible n-digit numbers? You can't write n nested for loops anymore since the number of loops changes depending on the input. This is an excellent example of when to use recursion, because it effectively allows you to create arbitrary nesting.</p></li><li><p><strong>Does the problem fit one of the common recursive patterns? </strong>For most recursive problems you see in interviews, there are six patterns that you can look for. We&#8217;ll preview them at the end of this article and cover them in depth in future ones. If you recognize that a problem fits one of these patterns, that is a strong hint to use recursion.</p></li><li><p><strong>Is it easier to solve recursively than iteratively? </strong>Sometimes the recursive code is just cleaner. Consider the task of printing a linked list in reverse order. Without recursion, you would have to push all the values onto a stack, then pop and print them. Recursively, the process reduces to three lines of code:</p></li></ol><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;9f4d3456-0fdc-4f38-94e8-8cac1b7e8a5c&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def print_reverse(node):

  if node is None:
    return

  print_reverse(node.next)
  print(node.value)</code></pre></div><p>The recursive version uses the call stack as your stack, so you don&#8217;t need to explicitly manage one yourself. The code is more concise and probably easier to grasp.</p><p>However, there is a trade-off. If the time complexity is the same, the iterative solution usually runs faster. Function calls, stack frames, and other things that recursion does all add up. In general, if you have the possibility to choose between two equally simple recursive and iterative solutions, the iterative one will perform better. </p><p>But in an interview, clarity often matters more than micro-optimizations, so it is wise to pick whatever approach lets you write correct, readable code faster.</p><h2>Base Case and Recursive Step</h2><p>Every recursive function has two essential building blocks: a base case and a recursive step. Getting these right is what separates working code from infinite loops and stack overflows.</p><p>The base case is the point at which the recursion stops. It is the simplest form of the problem, and it can be solved immediately without any further recursive calls.</p><p>A useful technique for identifying the base case is to start with small examples. What is the answer when the input size is zero or one? These edge cases are usually the base cases.</p><p>For example, if you are writing a function to compute the sum of an array of integers, an empty array has a sum of 0, and an array with one element has a sum equal to that element. Both can be solved without recursion, making them natural base cases.</p><p>There is one important rule to follow here: the base case must match the return type of your function. If your function returns a list, your base case must return a list (not None or <code>0</code>). This sounds obvious, but it is a common source of bugs. People often get stuck on this because they try to guess what the base case is instead of using concrete examples to figure it out.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;3880c540-d3df-49f9-8865-254ac40bd018&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Wrong: function returns a list, but base case returns None
def get_combinations(items):
    if len(items) == 0:
        return None  # This will break the calling code
    ...

# Correct: base case returns an empty list
def get_combinations(items):
    if len(items) == 0:
        return [[]]  # A list containing the empty combination
    ...</code></pre></div><p>In the recursive step,  you divide the problem into smaller subproblems and combine the results. There are two things to keep in mind here.</p><p>The first is that a subproblem should have the same shape as the original problem. If your function computes the sum of elements of an array from index i to the end, then your recursive call should compute the sum of elements from index i+1 to the end. The meaning is the same, just applied to a smaller input.</p><p>The second critical thing is that the recursive step must converge to the base case. If your base case is i == 0 and your recursive call passes i + 1, you are moving away from the base case, not toward it. This causes an infinite recursion and eventually a stack overflow.</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;8d8de827-f213-4e0a-82a7-fe79f180cff1&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python"># Wrong: moves away from base case
def countdown(n):
    if n == 0:
        return
    countdown(n + 1)  # n grows, never reaches 0

# Correct: moves toward base case
def countdown(n):
    if n == 0:
        return
    countdown(n - 1)  # n shrinks toward 0</code></pre></div><p>When you are writing code in an interview, you don&#8217;t get to run your code and see the stack overflow error. You need to find this mistake by looking at the code you wrote. A quick check that your recursive step gets you closer to the base case can save you from an embarrassing bug.</p><h2>Three Ways to Return Results</h2><p>When you write a recursive function, you need to decide how to collect and return the results. There are three main ways you can do this: use a global variable, use a passed variable, or build up results as you return. Each has its trade-offs.</p><p>The simplest approach is to use a global variable. You set a variable outside the code and change it as the recursion proceeds. As an example, consider the following code snippet that counts all the even numbers in an array:</p><div class="highlighted_code_block" data-attrs="{&quot;language&quot;:&quot;python&quot;,&quot;nodeId&quot;:&quot;1ea670e3-d346-4aa4-8a4b-d4d9abf59e02&quot;}" data-component-name="HighlightedCodeBlockToDOM"><pre class="shiki"><code class="language-python">def count_evens(arr, i):
    global result
    if i == len(arr):
        return
    if arr[i] % 2 == 0:
        result += 1
    count_evens(arr, i + 1)</code></pre></div><p>This works, but global variables are generally not a good idea. They make code harder to test and reason about. A better alternative is to use a passed variable.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/mastering-recursion-the-foundations">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[The Building Blocks of Concurrent Programming]]></title><description><![CDATA[Understanding processes, threads, and how they communicate.]]></description><link>https://newsletter.francofernando.com/p/the-building-blocks-of-concurrent</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/the-building-blocks-of-concurrent</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Fri, 06 Mar 2026 13:13:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!O5o3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 163rd issue of the Polymathic Engineer newsletter.</p><p>Concurrent programming involves breaking down applications into separate units of work. These units, known as <em>tasks,</em> set up the execution flow of an application. At their core, tasks are just abstractions. At some point, they must eventually be mapped to the physical devices that execute the code.</p><p>Thank goodness, we don&#8217;t have to worry about this. The operating system, which is another layer of abstraction, handles this for us. Its role is to make the best use of the available hardware as possible, but it is not a magical box. Still, programmers need to organize their work in a way that helps the operating system use hardware in the best way possible.</p><p>In this article, we will discuss processes and threads, which are the two main building blocks of concurrency, and understand how they can talk to each other (Demo code in this <a href="https://github.com/FrancoFernando/system-design-mastery/tree/9d66439a032232a9808274109f6a007317c9796b/low-level/concurrency-building-blocks">GitHub repo</a>).</p><p>The outline is as follows:</p><ul><li><p>Processes</p></li><li><p>Threads</p></li><li><p>Processes vs Threads</p></li><li><p>Interprocess Communication</p></li><li><p>Message-Passing Mechanisms</p></li><li><p>The Thread Pool Pattern</p></li></ul><div><hr></div><p>Project-based learning is the best way to develop technical skills. <a href="https://app.codecrafters.io/join?via=FrancoFernando">CodeCrafters </a>is an excellent platform for tackling exciting projects, such as building your own Redis, Kafka, a <a href="https://app.codecrafters.io/join/dns-server?via=francofernando">DNS server</a>, SQLite, or Git from scratch. <a href="https://app.codecrafters.io/join?via=FrancoFernando">Sign up, and become a better software engineer</a>.</p><div><hr></div><h2>Processes</h2><p>The informal definition of a process is simple: it is a running program. A program by itself is just a file sitting on a disk. It contains a set of instructions, but it does nothing on its own. When the operating system takes those instructions and starts executing them on hardware, it becomes a process.</p><p>Think of it like a recipe in a cookbook. The recipe itself is just words on paper listing ingredients, steps, and timings. It doesn&#8217;t make anything by itself. But if a chef reads it and starts cooking, it becomes a meal in progress. Source code works the same way.</p><p>It is just a passive sequence of instructions. Developers write code using abstractions like memory, files, and network connections, but the actual resources must be given at runtime. The OS wraps all of this into what we call a process.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O5o3!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O5o3!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 424w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 848w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 1272w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O5o3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png" width="395" height="374" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:374,&quot;width&quot;:395,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:140300,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/189250784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!O5o3!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 424w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 848w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 1272w, https://substackcdn.com/image/fetch/$s_!O5o3!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0d1633e3-f246-461c-a881-28cf61faacb2_395x374.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A process is made of several parts that the OS keeps track of:</p><ul><li><p><strong>Address space</strong>: the memory the process can see and access</p></li><li><p><strong>Executable</strong>: the file with the machine instructions</p></li><li><p><strong>Process ID (PID)</strong>: a unique name to identify the process</p></li><li><p><strong>Process state</strong>: whether it is running, waiting, or finished</p></li><li><p><strong>Open files and connections</strong>: any resources the process is using</p></li></ul><p>All of these together make up the <strong>execution context</strong>. Since so many things are packed into a process, starting a new one is a pretty heavy thing to do, and processes are often called <em>heavyweight</em>.</p><p>If you look at a process as a whole, its lifecycle is, its lifecycle is straightforward. First, it doesn&#8217;t exist. After that, the OS creates it and places it in memory (the <em>Created</em> state). From there, it moves to the <em>Ready</em> state: it can run at any moment, but the CPU hasn&#8217;t picked it up yet. When the OS scheduler selects it, the process goes into the <em>Running</em> state. Once it completes or fails, it reaches the <em>Terminated</em> state.</p><p>Creating and terminating a process is relatively expensive since so many resources are attached to it and must be assigned or released.</p><p>Processes can create their own processes, called <em>child processes</em>, using system calls like fork() or spawn(). This is called <em>spawning</em>. Each child process gets its own independent memory address space, which means it is completely isolated from the parent process and any other children.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6vka!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6vka!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 424w, https://substackcdn.com/image/fetch/$s_!6vka!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 848w, https://substackcdn.com/image/fetch/$s_!6vka!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 1272w, https://substackcdn.com/image/fetch/$s_!6vka!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6vka!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png" width="1051" height="143" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:143,&quot;width&quot;:1051,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:96782,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/189250784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!6vka!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 424w, https://substackcdn.com/image/fetch/$s_!6vka!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 848w, https://substackcdn.com/image/fetch/$s_!6vka!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 1272w, https://substackcdn.com/image/fetch/$s_!6vka!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a81d2b6-a2d8-4631-b725-4bed034b70ef_1051x143.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This is where things get interesting for concurrency. By spawning multiple processes, we can split execution across them and run them simultaneously on parallel hardware.</p><p>However, the isolation is both a good and a bad thing. Since two processes don&#8217;t share much by default, communication between them requires special mechanisms, which are usually several orders of magnitude slower than direct memory access. We&#8217;ll talk about that more later in this article.</p><h2>Threads</h2><p>Most operating systems let you share memory between processes, but this takes extra effort. However, there is a different abstraction that lets you share memory and other kinds of resources in a simpler way: threads.</p><p>At the end of the day, a program is simply a list of machine instructions that must be carried out in the right order. The OS uses the concept of a thread to do this. A thread is an independent stream of instructions whose execution can be scheduled by the OS.</p><p>In the previous section, we said that a process is a running program plus resources. If we break that apart, a process is a container of resources (address space, files, network connections, and so on), while a thread is the dynamic part: the set of instructions that run inside that container. From the OS&#8217;s point of view, a process can be seen as a unit of resources, while a thread can be seen as a unit of execution.</p><p>The idea behind threads is that sharing a common address space is the most efficient way for processes to communicate. Threads in the same process can share resources (address space, files, connections, and data) with each other and their parent process, in an easy way.</p><p>Each thread also keeps its own state to allow for safe, local, independent execution of its instructions. Each thread is unaware of the other threads unless it is trying to mess with them on purpose. The OS manages threads and distributes them across available processor cores. This makes a multithreaded program an effective way to run multiple tasks concurrently.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f5Me!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f5Me!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 424w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 848w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 1272w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f5Me!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png" width="577" height="333.54322580645163" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:448,&quot;width&quot;:775,&quot;resizeWidth&quot;:577,&quot;bytes&quot;:412035,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.francofernando.com/i/189250784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!f5Me!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 424w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 848w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 1272w, https://substackcdn.com/image/fetch/$s_!f5Me!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb14302e-4a13-484d-bfdb-ff9cdc44abe0_775x448.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Historically, hardware vendors implemented their own versions of threads, and these implementations were quite different. This made it challenging for programmers to write portable multithreaded applications. For UNIX systems, IEEE POSIX defined a standard programming interface. Implementations that follow this standard are called POSIX threads or Pthreads.</p><p>Every program we run causes the OS to create a process, and every process has at least one thread. A process without a thread cannot exist. When we start a program, a main execution thread is created. Any thread, even the main one, can create child threads at any time.</p><p>Threads have much less memory overhead than the standard <code>fork()</code> function. Since threads use the same process, nothing is copied. This is one reason why threads are sometimes called &#8220;lightweight processes.&#8221; It takes less time for the OS to assign and handle thread resources, plus starting and ending threads is faster than starting and ending processes.</p><p>However, there is a catch. The OS provides complete independence of processes from each other, so if one of them crashes, other processes are not harmed. This is not true for threads. All threads in a process use the same shared resources, so if one crashes or messes up something, it&#8217;s likely that the others will too. To avoid this, programmers need to make sure that shared resources are only used by one thread at a time and give them more control over how threads behave.</p><h2>Processes vs Threads</h2><p>To illustrate the difference between processes and threads, let&#8217;s go back to our recipe example. Imagine you run a restaurant and need to prepare three different dishes.</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/the-building-blocks-of-concurrent">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Getting Started with OpenCV: A Practical Introduction]]></title><description><![CDATA[Learn how images work under the hood and build your first computer vision application.]]></description><link>https://newsletter.francofernando.com/p/getting-started-with-opencv-a-practical</link><guid isPermaLink="false">https://newsletter.francofernando.com/p/getting-started-with-opencv-a-practical</guid><dc:creator><![CDATA[Franco Fernando]]></dc:creator><pubDate>Fri, 27 Feb 2026 10:14:09 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!GcAE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b0da3ca-92a3-47d9-b436-e1bf8737c951_1002x382.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hi Friends,</p><p>Welcome to the 162nd issue of the Polymathic Engineer newsletter. </p><p>As everybody knows, computer vision is everywhere. From the filters on your phone camera to self-driving cars, from medical imaging to security systems, the ability for machines to &#8220;see&#8221; and understand visual information has become integral to modern software.</p><p>If you want to get&#8230;</p>
      <p>
          <a href="https://newsletter.francofernando.com/p/getting-started-with-opencv-a-practical">
              Read more
          </a>
      </p>
   ]]></content:encoded></item></channel></rss>