<?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[Packt Deep Engineering: Interviews]]></title><description><![CDATA[In-depth conversations with Packt authors and engineering leaders, offering firsthand insight into how modern software systems are designed, scaled, and maintained.]]></description><link>https://deepengineering.net/s/interviews</link><image><url>https://substackcdn.com/image/fetch/$s_!H5BJ!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F736bc1ee-d689-497e-83a8-7d9bf9022eb9_600x600.png</url><title>Packt Deep Engineering: Interviews</title><link>https://deepengineering.net/s/interviews</link></image><generator>Substack</generator><lastBuildDate>Sun, 28 Jun 2026 17:17:15 GMT</lastBuildDate><atom:link href="https://deepengineering.net/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Packt]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[deepengineering@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[deepengineering@substack.com]]></itunes:email><itunes:name><![CDATA[Packt]]></itunes:name></itunes:owner><itunes:author><![CDATA[Packt]]></itunes:author><googleplay:owner><![CDATA[deepengineering@substack.com]]></googleplay:owner><googleplay:email><![CDATA[deepengineering@substack.com]]></googleplay:email><googleplay:author><![CDATA[Packt]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Sovereign AI and Agentic Infrastructure with Rick Spencer]]></title><description><![CDATA[On running AI disconnected from the internet, the three-tier framework SUSE uses to match tools to work, why output metrics are vanity metrics, and MCP as a control layer for enterprise infrastructure]]></description><link>https://deepengineering.net/p/sovereign-ai-agentic-infrastructure-rick-spencer-suse</link><guid isPermaLink="false">https://deepengineering.net/p/sovereign-ai-agentic-infrastructure-rick-spencer-suse</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 24 Jun 2026 18:06:00 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/8PdtwqLL6YI" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Most engineering organizations adopting AI do it without compliance regimes scrutinizing every decision. SUSE works under exactly that scrutiny, and the way it solved for AI adoption under strict data sovereignty requirements is instructive for any team that cares about where its data goes and what its AI actually costs.</p><p><a href="https://www.linkedin.com/in/rickspencer3">Rick Spencer</a> is the General Manager for Technology and Product at <a href="https://www.suse.com/">SUSE</a>, where he leads the engineering teams behind the company&#8217;s full product portfolio, from SUSE Linux Enterprise and Multi-Linux Manager to the cloud native stack of Rancher, RKE2, K3S, and SUSE AI. </p><p>SUSE has one of the longest and deepest open source infrastructure histories in the industry, and its enterprise customers are operating under strict compliance regimes. Rick joined Deep Engineering Live to talk about how SUSE adopted AI agents without breaking its promises on data sovereignty, the framework his teams use to decide which AI tools fit which work, why he rejects output-based developer metrics, and the role MCP now plays in managing enterprise infrastructure.</p><p>Watch the full conversation below or read the full interview.</p><div id="youtube2-8PdtwqLL6YI" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;8PdtwqLL6YI&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/8PdtwqLL6YI?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em>This session was recorded offline as part of the Deep Engineering Interview Series. The transcript below has been lightly edited for clarity and readability.</em></p><div><hr></div><p><em><strong>Q. Tell us a little about yourself and what you do at SUSE, and the kinds of engineering challenges your teams are working through right now.</strong></em></p><p><strong>Rick Spencer:</strong> I&#8217;m the General Manager for Technology and Product at SUSE. That means I lead the engineering teams for all the products that we offer to customers. That includes Linux, like SUSE Linux Enterprise, SUSE Linux Enterprise Server for SAP, and Multi-Linux Manager. We also have a suite of cloud native products like RKE2, K3S, and Rancher of course. We have a lot of things built on top of both of those, like the application collection, which are certified Kubernetes applications that you can run. We have other products composed of those building blocks like SUSE AI that you can use to run your own sovereign AI stack. There is SUSE Edge, and SUSE Edge&#8217;s cousins like SUSE Telco and SUSE Industrial Solutions.</p><p>Our customers tend to be enterprises with pretty serious enterprise requirements. They work under compliance regimes, they typically face a lot of scrutiny, they need important things like L3 support, reliable lifecycle models, a lot of predictability, high quality, and low CVE counts. So we take the open source software in the world and we create packages of it that are usable by enterprise companies.</p><div><hr></div><p><em><strong>Q. SUSE has a longer and deeper open source infrastructure history than most companies in the space. When AI agents started becoming the real workflow tool for engineers, how did that land internally, and what did adoption actually look like on the ground?</strong></em></p><p><strong>Rick Spencer:</strong> There is a lot to unpack here, so let me try to go at least somewhat systematically. All the software that we write is open source, so we are not worried about leaking the code. We publish the code. That was not the concern. But there are things like, let&#8217;s say you are debugging a customer environment. You do not want to let your engineers just take those logs and send them to random AI bots. We promise that we won&#8217;t do that. They trust us to not do those kinds of things. So there was a phase that we went through trying to figure out how to use AI in an effective way that maintained our promises to the customers.</p><p>Part of that solution was really realizing that engineers are going to use AI to go faster wherever they can. It is not like, oh, don&#8217;t use AI. That would just not be workable. So it was really about setting up our engineering management team to coach those engineers effectively. Besides keeping promises of data sovereignty, costs can also really run out of control. We see a lot of people run into that. For us, we never really had the problem of engineers deleting things in production. Our engineers tend to be very cautious. But it is easy to rack up pretty big bills on Anthropic and Copilot and so on.</p><p>A big part of the solution was that we have our own sovereign AI. We make this thing called SUSE AI, which is a stack you can use to manage AI workloads on your own infrastructure. We use that pretty heavily internally and we run Llama on it. If you are doing things, and we have a few different places that we do that, it is all within our private infrastructure, so we make sure there is no chance that any data can escape. We only use models which can be vetted effectively.</p><p>Then there was more to it on the coaching and oversight side. What we ended up doing is getting pretty precise about how engineers use AI. We broke it down into three categories. The first is using it for your daily work, which is your statement completion and your debugging and that kind of thing. The second is using it for agentics, which is relieving your toil, letting agents take care of some things that used to create a lot of work or interruptions. The last part I call curve jumping. That is when you are going from zero to infinity, doing things with AI that you would not have tried before, like solving really deep problems in one big step.</p><p>We created a framework around those three different kinds of uses, and then we help engineering managers help their engineers pattern match. Okay, if I just need statement completion and debugging help, these are the tools I can use for that. If there is a sovereignty aspect to it, these are the tools for that work. These are good tools for this kind of agent. And then these are the frontier models that we provide to you for those curve jumping capabilities. It sounds very organized now, but there was a lot of experimentation and a lot of rapid innovation from the engineers. Some of the early adopter engineers led, and then we went back and tried to create some order out of that so we could spread what we learned around.</p><div><hr></div><p><em><strong>Q. Digital sovereignty is central to how SUSE thinks about its stack. How does that principle shape where AI can and cannot go inside your engineering workflows?</strong></em></p><p><strong>Rick Spencer:</strong> I don&#8217;t think it&#8217;s can or cannot. It&#8217;s more so how. Let me give you an example. For digital sovereignty, a lot of the things we build, we actually build in something called the internal build service, which is an instance of something called Open Build Service, which is a service we provide to everybody. Kubernetes is built on it. There are thousands and thousands of things built on it. The interesting thing in terms of digital sovereignty is that all the builds are offline. They literally are not connected to the internet. This is super important because you need to be able to prove that nothing happened during the build process. That is a lot easier to do if there was no internet connection.</p><p>So we have to go around the hard way. We have to make sure all the sources are there. You cannot pull anything live in the heat of the moment during build time. You cannot run post-install scripts. Now if you want to apply AI in that environment, let&#8217;s say you want to backport a patch to previous stable releases, can you do that in a sovereign way? Yes, you can, as long as we are running AI in a way that is able to run disconnected from the internet and we have complete visibility into everything it is doing. These things are not easy, but we have decades of experience with it that we can apply. In some cases we even train our own models to accomplish these things, and that way we know the model does not have some naughty time bombs built into it.</p><div><hr></div><p><em><strong>Q. When your engineers started integrating AI agents, where did it deliver productivity gains, and where did it create new problems?</strong></em></p><p><strong>Rick Spencer:</strong> Let me give you some examples. My favorite example isn&#8217;t really about code. We are an open source company, and we have all of this code within our dominion of control, in GitHub and here and there and everywhere. I don&#8217;t know if you remember the Trivy attacks last month, and just a spate of tool chain attacks. We had a response process for that, but it was predicated on those things occurring occasionally, not twice a week. So our security team wrote an agent that scans certain sites every hour. It says, okay, there is a reported compromised package, typically in NPM, sometimes in PyPI, different places. It finds those, and then it scans all of our open source code to see if we are using it anywhere. If we are, it writes a report and notifies us on Slack. So we know right where to pay attention right away.</p><p>Fortunately, so far we have not really been impacted because we have really good hygiene around our tool chains. But bad guys are really smart and work really hard, so we still want to stay super vigilant. That has just been such a huge relief, because now if you see a report of a tool chain attack, our agent was on it before we even knew about it. It saves so much toil, because we don&#8217;t have to send people out to check if we are using this package or do a search in this area of GitHub.</p><p>There are other areas like CVE mitigation. A CVE comes in, and an agent examines it. Is it even applicable? A lot of times the CVE comes in and the package is in the repo, but it is not being exposed in any way that it would matter. There is this thing called VEX, which is basically a file you provide along with the CVE database to explain whether the vulnerability is impacting you or not. That is really hard to do at the scale that CVEs are coming in, but the agents can do that for us pretty easily. That means we are focusing our attention not on keeping up with the crush of CVE reports, but on the actual vulnerabilities. Our attention is reserved to actually keep our customers safe.</p><div><hr></div><p><em><strong>Q. How do you think about measuring the impact of AI on your engineering teams?</strong></em></p><p><strong>Rick Spencer:</strong> We might have a different view on that than some people. We are really tending away from measurements that measure output and utilization, and we are trying to focus on impact. What that means is we don&#8217;t have leaderboards that show every developer and how many lines of code their agents submitted. I consider that garbage vanity metrics. Not helpful.</p><p>One of the main things we want to do is measure the impact of our use of AI without it being an extra burden on the development team. A lot of the tools out there assume you are a proprietary software company where everyone is working on a single code base, which is just not how an open source enterprise works. We are working on hundreds, if not thousands, of repositories all the time, and the work to maintain them is very different. So those utilization numbers and those developer-to-developer comparisons just don&#8217;t have value. I&#8217;d rather have engineers working than reporting.</p><p>So we are setting up metrics for different things, like how fast CVEs are being addressed, how fast patches are being backported, how fast our L3 responses are getting closed while maintaining the same NPS score. These are things where we have applied AI to different areas, so let&#8217;s focus on the business impact, not on the utilization.</p><p>That said, we are working on a set of dashboards right now so an engineering manager can look at the cost and utilization on their team to help with coaching. Let&#8217;s say an engineering manager has an eight person team. Hey, I noticed we are burning a lot of tokens. What are we actually doing that is burning that many tokens? I&#8217;m not sure we are getting value out of that. Or, hey, we have these seats for this LLM or code assistant we bought, but we are not using them. Are there areas where we could be? So we are definitely measuring the value from a business perspective, but we are really trying to decentralize and allow engineering managers to guide their teams on getting the most value out of AI, without it becoming a leaderboard game where developers feel exposed in some game that is not about providing value to customers.</p><div><hr></div><p><em><strong>Q. Whenever we talk about AI agents, we cannot avoid MCP. Why does it matter to so many companies today, and what does it unlock for engineering teams in your kind of environment?</strong></em></p><p><strong>Rick Spencer:</strong> MCP is critical, actually. If anyone is listening and does not know what I mean, a Model Context Protocol server is a little bit of code that runs and offers to an LLM, hey, here is some context you can use, and here are some tools, some actual things you can do. That turns a chatbot into an agent, because an agent can actually do things.</p><p>MCP does a few things that are really important. The first is just ease of use. A good MCP server provides structure to the LLM so that it is way easier to write a prompt to get the results you want. The LLM sees the MCP server is for this purpose, and these are the kinds of information the humans want out of it. You don&#8217;t have to include all that in the prompt. And it is actually really easy to write an MCP server with an LLM. If you have a decent model, it does not even need to be top tier. You say, hey, we have this bit of software we want to control with agents, this is our use case, and it will write an MCP server for you pretty easily. Then you can have a human go in and edit it.</p><p>But there is another part to it. We ship MCP servers with all of our products, and we think this is really important. In our view, the world is moving to a new paradigm. Before, as an administrator, you would think about all the applications you use to monitor and control your servers, your Kubernetes clusters, your workloads. Now we are moving into a mode where you don&#8217;t think that way. You think about writing agents, or chatting with the infrastructure to get the information you need, and then it is able to take action on your behalf without you having to worry about the specific syntax.</p><p>For that to work, those MCP servers have to have really good human knowledge encoded into them. If you think about our MCP servers for SLES, for Rancher, for Multi-Linux Manager, the key is that the experts in using that tool have crafted those MCP servers. It would be like, instead of you sitting down in front of a chatbot saying I need to figure out how to use Rancher, you are sitting down with the whole Rancher development team telling you how to prompt the chatbot. That encoding of knowledge makes the agentics way more powerful, because it is not guessing. Otherwise it has to look at the raw APIs and make a bunch of guesses, and there is no way as a human you will know if that is the right thing to do.</p><p>All that said, there is another really important thing MCP servers do, which is provide a place where you can, as an enterprise, bring some sanity and control to the usage. If you have MCP servers running, they are just servers. That means you can provide access ACLs to them. You can say the MCP server for this user is allowed to use these tools and not these tools. You can log the use of the MCP servers. We have our own gateway, but we also partnered with a company called StackLok that we talked a lot about at the last SUSECON. There are different gateways you can put into place as an enterprise to keep the MCP servers under control. You don&#8217;t give the LLMs access directly to tools, only the MCP servers, and then you can have that oversight and meet your compliance needs.</p><p>Even at a low level, you can put the MCP server, I call it, in jail. You can say, on the server, here is the user for this MCP server, here is a systemd process that only presents the actual compute resources it needs. Because you have to be thinking, for every MCP server you are running, there is an LLM out there trying to use it, and who knows what kind of prompt injections people are running. MCP servers also guard against things like the AI hallucinating something and deleting your production server, because you simply don&#8217;t provide that tool to it. This to me is one of the main roles SUSE has to play as part of this disruption, because we are bringing this agentic notion of how to manage all of your infrastructure.</p><div><hr></div><p><em><strong>Q. Cost is a real constraint when you are running AI at any scale. What does a practical cost mitigation approach look like for an engineering organization working the way SUSE does?</strong></em></p><p><strong>Rick Spencer:</strong> I can speak from our own experience. The fact of the matter is, if you are using a self-hosted AI, sure, you spend a lot on the big iron, and you are probably paying a company like SUSE for support. But nonetheless, there is a maximum cost there. Then the real question is, do you have the observability in place to make sure it is being utilized fully? That is a very different conversation. Are we getting full utilization out of our fixed costs, where we never have to worry about overrunning?</p><p>There is digital sovereignty, and sometimes they call this cost sovereignty, because no one can come back later and say, oh, by the way, we are changing our model. We have some suppliers where a lot of our developers were using seat-based pricing, and then over time they let us know, in plenty of time, that they are moving to usage-based pricing. That is a big change. We did not have sovereignty over the way they price it, whereas if you are hosting your own, you have that sovereignty over the pricing. So it is something to think about.</p><p>Another thing we use a lot is circuit breakers. Hey, we just noticed our Claude usage in the last minute was way too high, or Gemini, whatever you are using. That keeps runaway agents in check. It can be very frustrating for developers if they are trying to get work done and every single minute they are getting rate limited, but we are talking about cost controls, so you need to do the thing.</p><p>The other thing to say is that we are big believers in frontier models. We are not saying don&#8217;t use frontier models, but it is important to use them for the right things. You do not need a frontier model to understand your Python module and give you code completion. You just don&#8217;t need it for that. The frontier models are really for when you are in that curve jumping, super strategic mode. We have projects where we spend tens of thousands of dollars on frontier models, but they generated, who knows, a million or two million dollars in value, so the cost benefit was definitely there.</p><p>One thing we do with frontier models is, let&#8217;s say we need an agent for something. We use the frontier model to create an agent that can then be run on a much lower cost model. It will say, sure, I&#8217;ll write the Python scripts it needs to use, so it doesn&#8217;t have to try to do that inference every time. I&#8217;ll write the context file that works for that model. So you can start with the frontier model and then tell it to do things with your less expensive models, or even your own models that are in your own infrastructure.</p><p>When you see that needle move, you see people start adopting it, and you&#8217;ll see step functions in utilization of tokens. A certain engineer, the penny drops for them that they are in a new paradigm where, as a developer, they suddenly realize they are empowered to be ten times, a hundred times more effective using these tools. You can see day to day these little jumps. Oh, somebody figured it out. Someone figured it out. So then you need to go back, because you don&#8217;t want to stop them from getting that 100X improvement. You need to give them the right tools for the job.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Try Rust With Your Own Hands and Eyes with Francesco Ciulla]]></title><description><![CDATA[On adoption strategy, flat latency, and the year he stopped hedging on Rust for production]]></description><link>https://deepengineering.net/p/try-rust-with-your-own-hands-and-eyes-francesco-ciulla</link><guid isPermaLink="false">https://deepengineering.net/p/try-rust-with-your-own-hands-and-eyes-francesco-ciulla</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Thu, 11 Jun 2026 10:33:12 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b7c13c92-97ba-4fd9-949b-7a3eed180812_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://it.linkedin.com/in/francesco-ciulla-roma/en">Francesco Ciulla</a> has been building with Rust since 2022, working across web development, developer tooling, and content creation for a large technical audience online. He is a Docker Captain since 2021, a former full-stack developer at the European Space Agency on the Copernicus project, and currently head of developer relations at Zerops. </p><p>He is the author of <em><a href="https://www.packtpub.com/en-in/product/the-rust-programming-handbook-9781836208860">The Rust Programming Handbook</a></em>, published by Packt in December 2025. Francesco joined Deep Engineering Live to talk about Rust adoption strategy, organizational challenges, concurrency, deployment workflows, and where the language is headed in 2026.</p><p>You can <strong>read</strong> or <strong>watch</strong> the full conversation here:</p><div id="youtube2-KTKe3_dx9_8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;KTKe3_dx9_8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/KTKe3_dx9_8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em>This session was recorded live as part of the Deep Engineering Live Interview Series. The transcript below has been lightly edited for clarity and readability. Audience members joined the conversation and asked questions directly during the session.</em></p><div><hr></div><p><em><strong>Q. What does Rust adoption actually look like at the organizational level, and what does success look like for engineering teams introducing it?</strong></em></p><p><strong>Francesco Ciulla:</strong> Rust has been growing a lot in the past few years and I am glad I started learning it a bit earlier than most people. I started creating content in 2022 and 2023 and then began working on the Rust Programming Handbook around April 2023, which took about two years to publish.</p><p>On adoption at scale, there is the famous meme about rewriting everything in Rust, and like every good meme there is a bit of truth in it. But I think the best approach, from a practical perspective, is not to rewrite everything in Rust at the beginning. The best way to introduce Rust in a big project is to find the hard part that is slowing things down, the bottleneck of your services, and try to write one single service in Rust. That is the best way to approach it. And then you will probably see Rust slowly take over more of your codebase, but I mean that in a good sense.</p><div><hr></div><p><em><strong>Q. Amazon and other large organizations have noted the high cost and risk of adopting Rust without internal expertise, and the talent pool is also quite thin. What advice do you give engineering leaders planning to introduce Rust and acquire the right people?</strong></em></p><p><strong>Francesco Ciulla:</strong> As with every new technology, the problem is not the technology itself. It is how well the technology is understood by the people in the organization. I remember when I was working at the European Space Agency and Docker adoption was slow, not because of anything wrong with Docker, but because a new technology that is not well known internally creates friction. That is the bottleneck.</p><p>The best approach is to have a shepherd, someone who can bring real knowledge into the organization. Basically a senior Rust developer who already knows all the flows and who people can refer to when they get stuck. This is especially true in the AI era where everyone is writing code with AI assistance, but you still need validation. Who decides whether the AI-generated Rust service is safe to put in production? You need the validation of an expert. That said, this is not just a Rust rule. It is the golden rule of adopting any new technology.</p><div><hr></div><p><em><strong>Q. The Rust learning curve has a reputation for being steep. The borrow checker in particular causes a period of deep soul searching for newcomers. What is the best way for an experienced developer to learn to think in Rust, especially in an AI-accelerated world?</strong></em></p><p><strong>Francesco Ciulla:</strong> I actually gave a talk at Rust Nation UK recently with the deliberately provocative title Rust Is Hard to Learn, so feel free to fight me on this. Because I think the idea that Rust has a steep learning curve is more of a myth than a reality, and I believe it can be addressed quite quickly.</p><p>The biggest challenge is not the concepts themselves but the mindset. Rust has a unique way of handling memory, and even if you are a senior developer with 20 years of experience, if you try to learn Rust by comparing it directly to other programming languages, you will struggle. The more experience you have, the more you think you already know how things work. Fighting the borrow checker, understanding lifetimes and ownership, can feel overwhelming if you bring that baggage with you. But if you are open-minded and approach it as something genuinely new rather than as a variation on what you already know, you will get the full power of the language and understand why so many people are enthusiastic about it.</p><p>Rust has been voted the most loved programming language year after year, and loved means the people who have used it still want to use it. That is a meaningful metric. I would rather trust the judgment of people who have actually worked with Rust than form an opinion based on what someone said on Twitter. When I am an engineer, I prefer to try things with my own hands and my own eyes.</p><div><hr></div><p><em><strong>Q. When is Rust not the right choice for a team, despite all its advantages?</strong></em></p><p><strong>Francesco Ciulla:</strong> I should probably say never, because I am supposed to be biased for Rust. But I prefer to be honest. There are genuinely cases where Rust is not the ideal tool.</p><p>First, when you need something simple and it needs to be done immediately. If you are a junior developer who needs to deliver a working API today and you do not know Rust, this is not the moment to start learning it under deadline pressure. You can always refactor something later. When you need something that just works, go with the technology you already know well.</p><p>Second, the ecosystem argument is real. Python has better libraries for data science. JavaScript has a larger package ecosystem for certain kinds of web work. Rust integrates well with other languages, but if you need something that is native to another ecosystem, that is a real constraint rather than just a preference. Good engineers use the right tool for the problem, and the case for Rust is strongest when the problem involves performance, memory efficiency, or concurrency at a level where other languages start showing their limits.</p><div><hr></div><p><em><strong>Q. Google&#8217;s Android security team reported that memory safety vulnerabilities fell below 20 percent after prioritizing memory-safe languages. Are those kinds of productivity and code quality benefits common in practice when teams use Rust?</strong></em></p><p><strong>Francesco Ciulla:</strong> Yes they are, but I think productivity in Rust is not primarily about typing speed. Rust can feel verbose and you might write more slowly in some ways than in other languages. The biggest benefit is the lack of debugging depth. You spend more time thinking carefully upfront, but you spend almost zero time chasing segfaults or memory leaks in production. And we always underestimate that part.</p><p>We talk a lot about how efficiently we can write code, but if you need less time to debug your code you are effectively writing more logic per unit of time. That is the part people consistently underestimate. We only tend to count the time it takes to write the thing, not the time it takes to find and fix what breaks later.</p><p>I also think Rust is one of the best programming languages for working with AI-generated code specifically because of what it requires from you as the reviewer. After the AI generates code you still need to touch it, understand it, and validate it. Otherwise there is no difference from copying boilerplate off Stack Overflow. You still have to understand what the code does. If you have no control, either you are useless or you cause a problem. In both cases, that is not a good place to be.</p><div><hr></div><p><em><strong>Q. What makes Rust&#8217;s approach to concurrency different, and how does it actually help teams building multi-threaded systems?</strong></em></p><p><strong>Francesco Ciulla:</strong> The first time I learned concurrency was at university in Java, and it was treated as the final, advanced session of the course, something dangerous that required extra care and specialized knowledge. I think many engineers carry that experience as a kind of trauma around concurrency.</p><p>When I went to teach concurrency in Rust for a YouTube video, I expected it to be challenging. I started the example and in two minutes I was done. I had the opposite problem. It was too easy.</p><p>This is structural rather than accidental. Rust was created when multi-core processors were already standard. Concurrency was not retrofitted onto a model designed for single-threaded execution. It was built in from the start. And the ownership system that prevents data races at compile time is the same ownership system that governs memory safety everywhere else in the language. There is no separate concurrency model to learn. The properties that make Rust memory safe are the same properties that make concurrent code safe.</p><p>That said, I personally prefer to add concurrency once an application is already working and you want to use memory more efficiently. If doing that takes a day of extra effort and it reduces your server costs from a hundred dollars a month to twenty, it was worth it. If it takes three weeks of fighting with the runtime, you would probably rather just spend more on the server. We are talking about production-grade applications here, not weekend side projects.</p><div><hr></div><p><em><strong>Q. How does Rust&#8217;s concurrency model hold up when you are building low-level networking components like proxies, packet processors, or kernel modules, territory that has traditionally belonged to C?</strong></em></p><p><strong>Francesco Ciulla:</strong> There are more repositories in C for that kind of work, and that is just a historical fact. But I am already seeing people build protocols and low-level networking components in Rust, and I think with AI assistance this is becoming more practical and more doable than it was even a year ago.</p><p>Just the fact that we are seriously considering writing these things in Rust is a win for the language. Nobody ever suggested doing this kind of work in JavaScript, because at that level you need pure efficiency and developer experience is not on the table. Only languages with the right performance profile can even enter this conversation. And Rust&#8217;s readability is a genuine advantage in that domain specifically. Low-level code becomes very complex very fast, and the fact that you can read your own code tomorrow is a significant practical benefit when you are dealing with networking protocols. I am not saying Rust will replace C or C++. Languages rarely disappear. But we now have an option, and having that option is already a meaningful shift.</p><div><hr></div><p><em><strong>Q. What are the most common pitfalls you see developers run into with Rust?</strong></em></p><p><strong>Francesco Ciulla:</strong> I wrote an eighteen-page chapter on pitfalls in the book, so I can probably remember about half of them now. The biggest one is trying to write Rust as though it were another language. If you approach it with the patterns and assumptions you bring from other languages, you will have problems. The second is not trusting the compiler. Especially at the beginning, the instinct is to try to fix things your own way without reading what the compiler is actually telling you. The compiler is giving you very specific information and the errors are basically tutorials. They are helping you write better code. Learning to read them carefully rather than working around them is probably the single most important habit to develop early.</p><div><hr></div><p><em><strong>Q. Rust&#8217;s trait system and rich type semantics let developers encode invariants directly in the type system, but this power can also lead to very complex code. How should an experienced architect balance using the advanced type system versus keeping things simple?</strong></em></p><p><strong>Francesco Ciulla:</strong> If you want to build rockets, you need to use more complex tools. That is just the nature of it. I think the best approach is to build a solid foundation in the basics first. You should not be using traits without understanding what they are. In terms of code organization, Rust is the best language I have worked in for how it structures modules, files, and folders. At some point you will stop writing everything in a single file, and having a clear module structure helps you manage complexity as it grows.</p><p>You can also write straightforward Rust code up to a certain point. If you want to unlock the full potential, including unsafe Rust and raw performance, Rust allows you to remove the seat belts. But of course the code becomes more complex at that level. This is not unique to Rust though. The complexity of any project is always exponential. It starts simple, simple, simple, and then suddenly nobody knows what is going on anymore. Having solid fundamentals gives you the tools to manage that curve.</p><div><hr></div><p><em><strong>Q. Rust versus Go for microservices is a question many backend teams face right now. Is Rust ready to challenge Go in the web backend space?</strong></em></p><p><strong>Francesco Ciulla:</strong> Let me be clear first that Go is not a bad language. Docker is written in Go and I would never say that makes Go bad. I have a Docker bottle on my desk and I have been a Docker Captain since 2021. I also currently work at Zerops where we have a CLI written in Go and I read a lot of Go code myself. So I am not dismissing it.</p><p>On pure performance, the comparison between Rust and Go is not really a contest. Check the benchmarks yourself and send me the link where Go beats Rust. Sometimes they are at the same level. In terms of pure performance there is no story.</p><p>Where Go genuinely wins is on CLIs, cloud tooling, and developer ecosystem. Docker is written in Go, Kubernetes is built on Go. If you want to be in the DevOps space and write tools in that ecosystem, Go is the natural choice. It also has more engineers available in the job market right now, which matters if you are hiring.</p><p>From a developer perspective though, I would argue the opposite is worth considering. If there are fewer Rust engineers than Go engineers, being expert in Rust means less competition. I would rather be expert in a language where there is less competition than be one of millions of Go developers. But I understand that a company hiring today will probably find a good Go developer faster than a good Rust developer. Both arguments are valid depending on which side of that conversation you are sitting on.</p><div><hr></div><p><em><strong>Q. How does Rust affect build and deployment workflows in practice?</strong></em></p><p><strong>Francesco Ciulla:</strong> This is one of my favorite questions because it combines two of my favorite things. When you build a Rust application, you get architecture-specific executable binaries. If you run cargo build on your machine, you get an exe on Windows, an executable on Mac, and an executable on Linux. You can also cross-compile, changing the target architecture through the compiler, to produce a Linux binary on a Windows machine.</p><p>My preferred workflow for production is to build the Rust binary directly when building the Docker image. That way I have a Linux executable compiled inside the container, ready to run everywhere Docker is installed. The dream for operations teams is having a single lightweight binary inside a Docker container. You get the portability and scalability of containers with the minimal footprint of a Rust binary.</p><div><hr></div><p><em><strong>Q. Are there real operational benefits to shipping Rust services as smaller container images with lower runtime overhead?</strong></em></p><p><strong>Francesco Ciulla:</strong> Absolutely. The binary is the dream for operations teams because it is the most lightweight option. I mentioned earlier that my Rust web server uses four megabytes of RAM in development and five in production. On a one-gigabyte droplet you could theoretically run more than 200 such services in idle. That kind of resource profile changes what is economically viable to deploy.</p><p>You could in theory run the Rust executable directly without Docker, and for a single service that works fine. But if you need to orchestrate ten services on the same machine, containers are still the right answer for production-grade applications that need to scale. The slight overhead Docker adds is worth it many times over in terms of scalability, replaceability, and operational consistency. We are not in the 90s anymore.</p><div><hr></div><p><strong>Q. What use cases in web development do you see Rust excelling at?</strong></p><p><strong>Francesco Ciulla:</strong> When performance is really important, Rust is where it shines. If performance is not your main concern, you can go with more conventional choices. I am still a fan of Node.js for certain kinds of work.</p><p>One of the most underappreciated arguments for Rust in web services is flat latency. Languages with garbage collectors, including Go, Java, and Node.js, introduce periodic pauses when the collector runs. Those pauses can last hundreds of milliseconds. An HTTP request that arrives during a GC cycle gets a worse experience than one that does not. By not having a garbage collector on the backend side, you have flat latency. You do not rely on luck or on the user not being the unlucky one. That problem is simply removed.</p><p>The other scenario where Rust makes a clear case is when you have one service in your system that is significantly slower than everything else. There is always one in any sufficiently complex system. Sometimes it is not the service itself but the upstream dependencies it is calling. But when the service itself is the bottleneck, spending time to optimize it in Rust makes sense. Writing the whole application in Rust from scratch is only necessary if you are starting a new project or you genuinely want to have fun with the language. For most teams, the bottleneck service is where to start.</p><div><hr></div><p><em><strong>Q. What are the main challenges when integrating Rust into CI/CD pipelines and monitoring?</strong></em></p><p><strong>Francesco Ciulla:</strong> The honest answer is that since Rust has been in production for less time than other languages, there are fewer examples in the documentation for some specific integrations. This gap is closing quickly and will probably disappear in a couple of years, but if you are using a specific technology and looking for a Rust integration example, you may occasionally find the documentation lacking compared to what exists for JavaScript or Python.</p><p>The Rust toolchain itself is actually one of the language&#8217;s strongest points once you get used to it. Running tests is cargo test. It is integrated natively into the language and there is no equivalent of the npm versus yarn versus pnpm decision that JavaScript teams have to navigate before writing a single line of code. The ecosystem and toolchain are famous within the Rust community for being one of the things people love most about working in the language.</p><div><hr></div><p><em><strong>Q. The Linux kernel maintainers have declared Rust permanent and are planning components that require it. What does that endorsement signal about where Rust is headed?</strong></em></p><p><strong>Francesco Ciulla:</strong> It is great news and very bad news for Rust skeptics. The fact is that Rust is slowly getting adopted at bigger and bigger levels. You can see this on the government side, in military applications, and in security-critical domains where safety requirements are the highest. Just the fact that Rust was considered a viable option for kernel-level work was already a meaningful milestone, even before it succeeded. The language was competing in a domain that had been exclusively C and C++ territory for decades and it earned a permanent place there.</p><p>I will be genuinely happy when we stop having the conversation about whether Rust should be used, and we just start using it. Python does not get these conversations. Nobody asks whether you should use Python. I will be happy when Rust reaches that level of acceptance as a normal production choice. We are moving in that direction. Every day I see another positive signal.</p><div><hr></div><p><em><strong>Q. Where do you see the next phase of Rust growth happening?</strong></em></p><p><strong>Francesco Ciulla:</strong> I think the biggest shift is coming in web development backends, and I know this is an unpopular opinion in the Rust community where the language is traditionally associated with systems programming. But I am seeing companies with hundreds of developers reach out to tell me they are rewriting their backend services in Rust. These are not random side projects. These are companies making deliberate production decisions.</p><p>Two years ago I would not have committed to building a paid SaaS product in Rust. In 2024, probably not. In 2025, maybe. In 2026, yes, I would use it. The Axum framework in particular has matured to the point where I am confident recommending it for production. That was not true a year ago.</p><p>In the embedded space, Rust is already winning and I have largely stopped advocating for it there because the argument is settled. I am also seeing companies that manufacture embedded devices ship them with Rust as the default, which is a different story from developers experimenting with Rust on embedded hardware. When the producers of these devices choose Rust before selling them, that is a commercial signal.</p><div><hr></div><p><strong>A member of the audience asked: </strong>With the state of the industry right now, is it challenging for a junior developer to start their journey with Rust and find their first job?</p><p><strong>Francesco Ciulla:</strong> With the state of the industry right now, I think it is challenging for a junior developer to find a job regardless of which language they know. So let us remove Rust from that equation first.</p><p>You have two approaches here. One is to go as mainstream as possible, learn the most used framework, and compete for the highest volume of jobs. The problem with that approach is that you are also competing with the largest number of other candidates. I personally prefer the opposite approach. Since there are fewer Rust engineers than engineers in most other languages, being expert in Rust gives you a real differentiator.</p><p>If a job description lists JavaScript, React, SQL, Docker, Kubernetes, and then also mentions Rust, and there are two candidates and one of them knows Rust, that extra knowledge might be the thing that gets you the role. That is my honest view. The era of becoming strong in exactly one technology and finding a job with that alone is probably over. We need to be flexible. But dedicating some time to understanding the basics of Rust might make you shine in an interview in a way that knowing only mainstream technologies will not.</p><div><hr></div><p><em>Francesco Ciulla is the author of The Rust Programming Handbook, published by Packt, and head of developer relations at Zerops.</em></p>]]></content:encoded></item><item><title><![CDATA[Hands-On Software Engineering with Python with Brian Allbee]]></title><description><![CDATA[Brian Allbee joins Deep Engineering to discuss the mindset shift from writing code to engineering systems.]]></description><link>https://deepengineering.net/p/hands-on-software-engineering-python-brian-allbee</link><guid isPermaLink="false">https://deepengineering.net/p/hands-on-software-engineering-python-brian-allbee</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 03 Jun 2026 12:30:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/d4a795b5-9037-483c-9276-02a30b0de712_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.linkedin.com/in/brianallbee">Brian Allbee</a> has been writing Python almost exclusively since 2012, working across cloud-based application development, machine learning integration at Dice.com, and backend systems in AWS using Step Functions and Python Lambdas.</p><p>Allbee, Staff Software Engineer at Cleerly and author of <em><a href="https://www.packtpub.com/en-us/product/hands-on-software-engineering-with-python-9781835888018">Hands-On Software Engineering with Python</a></em>, now in its second edition published by Packt, joined Deep Engineering Live to talk about what separates engineering from programming, how to scale and refactor Python systems responsibly, and what it actually takes to grow into senior and staff-level roles.</p><p>Watch the full conversation below.</p><div id="youtube2-s_3X-ERgvhg" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;s_3X-ERgvhg&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/s_3X-ERgvhg?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em>This session was recorded live as part of the Deep Engineering Live Interview Series. The transcript below has been lightly edited for clarity and readability. Audience members joined the conversation and asked questions directly during the session.</em></p><div><hr></div><p><em><strong>Q. Tell us about your background and the kinds of systems you have worked on.</strong></em></p><p><strong>Brian Allbee:</strong> I have been programming almost exclusively in Python since early 2012. Prior to that I worked in C Sharp dot net, Flex markup language, and PHP for application development. I landed on Python at a job I started early in 2012 at an ad agency where they needed somebody to come in and build an internal application that was more performant than their off-the-shelf solution for asset management. I fell in love with the language a little before that position started, but I was very happy that a language audit I did reinforced that Python was still the way to go because it had everything they needed.</p><p>Since then I have done client-facing cloud management application work, a handful of customer-facing applications I cannot get into too much detail on because they are still covered under NDAs, and the last six years I spent doing machine learning implementation and integration for Dice.com on the team that eventually became their applied data science and AI team. Currently I am doing backend system development in an AWS cloud context with Step Functions and Python Lambdas to deal with health insurance processing.</p><div><hr></div><p><em><strong>Q. What distinguishes a true software engineering approach from just programming, particularly for Python developers working on real-world systems?</strong></em></p><p><strong>Brian Allbee:</strong> I think learning to think in terms of systems, not just implementations, is probably the main thing. I feel that holds true whether the backing language is Python or not, and it does not stop with just the systems that an engineer is writing. On the technical side it extends out to the entire toolchain, anything that shapes the code itself or determines how the code is managed or handled. But it also extends to what I would call nontechnical systems in the sense of a set of principles or procedures that define how something is done.</p><p>I basically feel that programming is really focused on making sure that the code is correct, the correctness of the code itself. Where software engineering starts expanding out into more of a focus on sustainability as change occurs.</p><div><hr></div><p><em><strong>Q. For Python developers aiming to move into senior or staff engineering roles, given how much AI is now part of development workflows, what skills or mindset shifts do they need beyond raw coding proficiency?</strong></em></p><p><strong>Brian Allbee:</strong> I think that same systems-oriented thinking is still the big dividing line, and I believe that will hold true even if LLM-based code generation turns out to be the next big thing that all of its proponents argue it will be. Even in those scenarios, manual interaction with code at the level of the syntax of the code itself might dwindle over time, but there are still going to be sensitive domains where some of that remains necessary. More importantly, engineers need to understand how that code fits together even if they did not write it, and why it fits together the way it does.</p><p>Hand in hand with that is a broader understanding of the problems being solved. Software engineering, like every other engineering discipline I am aware of, is concerned with solving problems usually while operating within some set of constraints. Software engineering focuses on solving those problems by creating systems, and that goes back to the whole systems-oriented thinking. But solving the problem requires understanding that problem first. Even if code generation becomes largely or completely automated, someone still has to own that system and understand its constraints and its potentials for failure and how it is expected to evolve over time.</p><div><hr></div><p><em><strong>Q. What are some of the best practices for updating, refactoring, and scaling an existing Python codebase as it evolves?</strong></em></p><p><strong>Brian Allbee:</strong> I think most of the paths to success in that context, at least the ones I can think of that I have seen, do not really start with the architecture but with discipline behind the process. The approaches that have worked for me or that I have seen work well for others include keeping things as simple as possible, wrapping processes that get used over and over again into functions or methods or whatever context works best whenever possible, and not being afraid to use structured data.</p><p>If you are working in a team, make sure the team has some agreement about how much in-code documentation and by extension comments are expected and what it should provide. The same kind of team-level agreement about what code standards you want to apply. Stick to those until something comes up that is not covered, and revise them as needed. It is a growing process.</p><p>Writing code with an eye towards making it testable is key even if there is not an immediate need for testing. Future you, if you are writing good tests, will come back and thank you if they could.</p><div><hr></div><p><em><strong>Q. Can you share an example from your experience of tackling technical debt or redesigning a Python system to improve its maintainability and performance?</strong></em></p><p><strong>Brian Allbee:</strong> Honestly, I really cannot. I do not have any dramatic war stories here because I have worked with generally exceptionally healthy teams that treated technical debt as a first-class concern, not as an emergency. Technical debt is one of those product-level priorities. Whoever is making the prioritization decisions is going to be in control of when those get tackled if they get tackled.</p><p>If there is significant technical debt, making sure that you can communicate effectively, here is what the impact of this technical debt is to your product-level people or whoever is making those decisions, is going to be a key thing. That means being able to sit down and say, I understand that you do not want us to deal with this bug or whatever it happens to be. If we do not deal with this, it is going to lead to this, then that, and the longer we put that off, the more likely it is to lead to a really significant problem and the longer it is going to take for us to get past that.</p><div><hr></div><p><em><strong>Q. Modern teams often grapple with how much upfront system design to do versus driving straight into coding. How do you find the right balance between careful architecture and rapid agile execution?</strong></em></p><p><strong>Brian Allbee:</strong> I think understanding the full final scope of a project, even if it is just at a very high level, is critical to one side of that balance. The other side is knowing, again even if just at a high level, what constraints and non-project expectations are in play. You mentioned agile. Even if some form of agile is not part of a team&#8217;s day-to-day processes, there are some takeaways from agile that I think can still be beneficial. The entire idea of delivering work and software frequently on some sort of cadence is one of those. Iterating against the smallest deliverable units that can be identified would be another major factor.</p><p>I do not think the real risk is too much design or too little. It is designing without understanding the constraints that are involved. Iterating on the smallest meaningful units of work is going to be the most practical way to find that right balance between design and execution.</p><div><hr></div><p><em><strong>Q. Python has introduced features like data classes, type hints, and static typing tools in recent versions. How have these modern language features shaped your approach to designing Python software, and how would you recommend engineers fully embrace type hints in large projects?</strong></em></p><p><strong>Brian Allbee:</strong> Not as much as it might sound like, actually. Before I turned to Python I was working with C Sharp dot net, which is a statically typed compiled language. I came to really like the idea of static typing from a programming perspective even in dynamically typed languages like Python and JavaScript. If you dig back far enough into some of my very old and now unsupported blogs, you will find I even wrote some blog posts about implementing that sort of a structure in Python back as far as version 2.7.</p><p>I definitely recommend using the typing system that is in the language right now. At worst, it is additional documentation that modern IDEs can pick up on to help an engineer working with the code. With the inclusion of just one third-party package, I like TypeGuard myself but there are others, it is possible to achieve runtime static-like type safety and static type-like behavior in Python code. And pre-deployment tools like MyPy are going to pick up on your type hints to give you some extra quality control going into that process. I think about whether you enforce types at runtime or not, the design clarity is worth it.</p><div><hr></div><p><em><strong>Q. In building robust Python applications, how do you approach data modeling and validation? When does Pydantic make sense and when are simpler options sufficient?</strong></em></p><p><strong>Brian Allbee:</strong> I think it depends on the scope and intentions of the project. Pydantic is great for projects where there are complex requirements that can be derived from something like a JSON or OAS schema. It is also good for projects that are responsible for generating those JSON or OAS schemas. The downside is it is a larger package, coming in at around two and a half megabytes or so for the module itself and its primary dependency. So if package size is a concern, that might not be the best choice.</p><p>There are other options. Fast JSON schema combined with regular Python dictionaries and lists is a solid alternative and it is much smaller. If there is no need for any kind of schema documentation but there is still a desire for type checking, type-annotated Python classes will probably get you 80 plus percent of the way there in my experience. If you need mutable data structures and that is all you need, data classes are a good option. If you need an immutable data structure and type checking is not a concern at the level of the code structure, I usually start with something like a named tuple.</p><p>I think the right data modeling tool depends less on popularity and more on the system&#8217;s constraints and the scale, scope, and longevity of the project.</p><div><hr></div><p><em><strong>Q. What are your go-to best practices for testing Python systems at scale? How do you balance unit, integration, and end-to-end tests, and how do you ensure the test suite stays reliable and useful over time?</strong></em></p><p><strong>Brian Allbee:</strong> For my own projects, I tend to like a testing approach that exercises valid and invalid inputs for all of the parameters of every callable in the project. You combine that with judicious monitoring of missing lines in a code coverage report, and that has served really well for me in making sure that the targets of those tests are being both thoroughly and realistically exercised.</p><p>In a working environment, I like for the team to come to some sort of consensus about how the tests are organized, what tools are involved, and so on. I have seen what happens when different engineers who are not communicating with each other each go their own way. The tests that result, even if they are rigorous and well thought out, are oftentimes difficult to follow across different test modules because different people write tests in different ways.</p><p>When integration or end-to-end testing is not feasible, I try to push unit tests closer to behavioral testing even if that increases mocking complexity. Those kinds of scenarios, unit testing can still go a long way. Ultimately, though, tests are most valuable when they reflect how the system is actually used, whether that is managed at a unit testing, integration testing, or system testing level, not just how that testing code or the code itself is structured.</p><div><hr></div><p><em><strong>Q. AI is now being used in areas like test generation, debugging, and even test maintenance. How should engineers think about AI in testing without compromising reliability and confidence in their systems?</strong></em></p><p><strong>Brian Allbee:</strong> I think the fundamental important thing there is going to be getting some sort of a consensus from anybody who is involved, all of the stakeholders, and anybody who is going to be held accountable for failures in the code, as to what the guardrails need to be. I like AI from the standpoint of code generation for things that are not sensitive. If it affects somebody&#8217;s lives or livelihoods, I do not want randomly generated code out there without really good guardrails.</p><p>The approach that I have seen and tried myself that has the most promise is to take a test-driven development approach and define a test suite, and only allow humans to modify that test suite. Make sure it is really good, really solid, really rigorous, and covers all of the business needs, everything that you can come up with. And then you can let an AI process go to town on the code as long as there is a clear boundary there. You can tell it, you can write all the code you want, it must pass this test suite, you do not get to modify that test suite. Let it go to town. At that point I think you are probably about as well covered as you can be.</p><div><hr></div><p><em><strong>Q. How should Python teams set up CI/CD pipelines to improve code quality and deployment reliability? What best practices help the most and what pitfalls should be avoided?</strong></em></p><p><strong>Brian Allbee:</strong> The goals are all the same for CI/CD regardless of the language involved. You have to fetch the code, you have to test it, you have to build it and package it, and you have to deploy it. The basic sequence is common across the board. There may be additional tasks like checking that the deployment process is well-formed, or aspects that are not tied directly to the code itself.</p><p>The main value that CI/CD adds is not necessarily the automation. It is the fast, automated, trustworthy feedback that you get from one of those processes. I would say look for places where you can generate that feedback, find the break points that are going to happen, and make sure that anything that fails gets surfaced in a meaningful, timely, and useful fashion.</p><div><hr></div><p><em><strong>Q. What makes a Python application cloud-ready, and what are the most important design principles to bear in mind?</strong></em></p><p><strong>Brian Allbee:</strong> Cloud-ready can mean different things to different organizations, different teams, or even individual people. A containerized application is cloud-ready provided that it can be deployed appropriately, but so too are function-as-a-service constructs like AWS Lambda functions and their equivalents in other provider spaces. It all depends ultimately on the final deployment expectations.</p><p>Some key things to bear in mind include leveraging environment variables to help control behavior in different cloud accounts or environments within those accounts. You will find that they can carry over from local development to deployment processes and build pipelines all the way out to your final deployed product. They can always be replicated and manipulated locally, and that makes things easier and faster to change in a deployed application without having to redeploy an entire stack.</p><p>Be aware of and actively seek out systems that cloud providers offer for things that you need to deal with. Secret storage is a great example. Pull a secret in one time when a container initializes or a Lambda starts up, and then do not touch it after that. Know the best practices and constraints for your final deployed code. A great example is AWS Lambda functions. You cannot run a Lambda function for more than fifteen minutes, so once you have a good idea of how long a process can take, set that timeout accordingly and test against it.</p><p>I think cloud-ready is less about where code runs and more about designing for volatility and external constraints.</p><div><hr></div><p><em><strong>Q. How do statelessness and containerization fit into building scalable cloud systems, and why do they matter?</strong></em></p><p><strong>Brian Allbee:</strong> If you think about it at a basic structural level, the key concept that ties almost every cloud resident system together, containerization, stateless design, any of those, is that they are inherently disposable. A container can be killed at any time. A Lambda invocation could be terminated before it reaches a successful completion. Kubernetes pods restarting are probably routine events. Even in a serverless context, a virtual machine can be stopped and restarted without warning. Recognizing that means designing your processes around the expectation that your hardware can disappear at any point in time.</p><p>Statelessness in that context is in a very real way about making failure of your hardware cheap. There is no state to manage, there is no need to write code to reacquire that state. A process ends and is restarted. Planning for failures and designing around the idea that recovery from a failure is just starting a new instance is probably near the top of the list of factors shaping design decisions.</p><p>In a container-based context, the container is at that point the smallest unit to replace. The key factor to keep in mind there is making sure that the startup behavior is consistent and predictable. Ensure that the environments are repeatable and allow a failed container to be replaced automatically and seamlessly rather than relying on any kind of manual troubleshooting process.</p><p>Statelessness and containerization matter more because they make failure cheap and recovery routine than for any other purpose or reason. That is what it comes right down to.</p><div><hr></div><p><em><strong>Q. A member of the audience asked: </strong>How critical is containerization to scaling systems?</em></p><p><strong>Brian Allbee:</strong> Containerization is one of the more popular mechanisms but it is not the only mechanism out there. Most of my experience with containerization has been in a cloud-oriented context, and the alternatives in an AWS context at least include things like Lambda functions, which technically are their own containers but you do not have to worry about containerization as one of the factors in your code that you are concerned with. You are literally just writing code to fit inside the context of that Lambda container and letting it go. It is a good skill to have, most definitely, something that is going to be of use and interest in a lot of jobs these days, but I do not know that it is a critical skill for all cases.</p><div><hr></div><p><em><strong>Q. A member of the audience asked: </strong>Does Flask scale well?</em></p><p><strong>Brian Allbee:</strong> The scaling question is so context-dependent that it is really hard to say definitively. In a containerized structure where your data store is completely separated from your Flask environment and application, and you can spin up and drop new instances of containers, I think it scales as well as anything else out there.</p><p>You will probably find that FastAPI is going to be more performant, but there is also a lot more work that has to happen in a FastAPI context. Flask is probably about in the middle. It is a good balance between a lot of stuff already supported versus speed of operation. And then at the other end you have something like Django where it does everything for everyone but it is not going to be as performant at an instance-by-instance level. After that, it is really going to end up depending on how well you can spread that load out through load balancing across containers running your application, regardless of whether it is Flask or FastAPI or Django or something completely homebrewed. That is probably where you are going to see the most scalability capability out of all of those options.</p><div><hr></div><p><em><strong>Q. What is one trade-off you see Python developers consistently get wrong when building systems?</strong></em></p><p><strong>Brian Allbee:</strong> The one I would say is most consistently seen in my experience is going back to the idea of overengineering. I want to write this as an object-oriented system because object-oriented is the way to go. And the same could be said for functional programming. Understand your problem space and design the solution around that problem space, because that is what you are trying to do, provide a solution for that specific problem space.</p><p>The best example in my personal experience was a system that was written in Python by somebody who came from a C Sharp background. The project was ridiculously huge. Every function had its own module. Every class had its own module. You put everything together, the functional layers of the system were seven or eight deep depending on the context.</p><p>Way more complicated than it needed to be, and it was hard to manage and hard to maintain. If I could have gone back and talked to, let us call him Steve, I probably would have said, Steve, there is this really good book out there called Code That Fits In Your Head. Read that. It is all about keeping things at a manageable level because psychologically humans can only keep five to seven bits of information in the front of their memory at a given point in time. You are talking about seven layers worth of depth in a project structure. That is already saturating things. Keep it simple. Collapse things down to the point where you do not have to have nineteen different classes and fifteen different instances of all these other classes to deal with something that really should be capable of being managed as a single function.</p><div><hr></div><p><em><strong>Q. How can you tell when an engineer is ready for senior-level work?</strong></em></p><p><strong>Brian Allbee:</strong> It goes back to what we started with. If they start demonstrating that they are concerned with more than just whether the code is doing what it is supposed to do, whether this function works, if there is a certain amount of curiosity about why are we doing it this way, what is the advantage of taking a functional programming approach versus a procedural versus an object-oriented one, what are the trade-offs, and do they recognise the trade-offs. Those are the things that start really indicating somebody is actually ready to go beyond just, I have written this function, it is done, it is tested, it works, I am finished.</p><p>The senior engineers that I have tried to emulate and that I have seen do their best work really are not defined by the code that they write but by the systems that they shape and the teams that they are enabling. That involves some gatekeeping, asking why we are going down this particular design path, or why are we not using this brand new library that has just shown up in the last three months. There is a lot of broader scope in asking those questions of less senior engineers and guiding them to learn how to ask those same questions on their own. Why do we go down this road? What is the benefit? What are the trade-offs? Because there are always trade-offs. Always.</p><div><hr></div><p><em><strong>Q. What motivated the second edition of Hands-On Software Engineering with Python, and what changed?</strong></em></p><p><strong>Brian Allbee:</strong> A good part of it was just the time lag. It was seven years between the first edition and the second. But Python itself has changed significantly, not so much in the language core but in the maturity of its tooling and the breadth of problems that it is now used to solve. The ecosystem around testing, packaging, automation, and deployment has grown in ways that significantly change how Python is used in real-world systems. Its adoption has also expanded dramatically, particularly in cloud and large-scale environments and also in AI, where it is very much a go-to language right now.</p><p>Today, Python engineers are frequently expected to think about architecture, performance, testing, and operational concerns in ways that just were not as common when the first edition was written. All of those growth areas and the dramatically increased surface area of use of the language kind of begged for further discussion.</p><p>The first edition tells the story of a fictitious company called Handmade Stuff that is just starting to develop an application structure to deal with what they are trying to accomplish as a company. The second edition takes that story forward and says, okay, we have this application and it is functional but less than optimal, and there is a significant impetus from the organization to move into the cloud. So what would that look like? A lot of the principles are still absolutely the same. You still need that system-level thinking, you still need to understand the problem space, you still have to work out how you are going to deploy this. What it looks like is going to be extremely different.</p><div><hr></div><p><em><strong>Q. What is the one piece of advice you would give to Python developers trying to grow into stronger engineers in an AI-accelerated world?</strong></em></p><p><strong>Brian Allbee:</strong> If you come away from thinking differently about why you write the code that you are writing, not just how, then you are moving in the right direction. Engineers are on the hook to develop and ship working code. But I will go back to my basic principles more than anything else. Think in systems. Design for change. If you are working in a team, optimise for your team.</p><p>Ask how easily the code could fit into a larger system, how it will change over time, and how your design choices will affect the product later for the people who have to work with it after you are done with it. That mindset shift is a key thing in enabling an engineer to grow into more senior roles and to build software that holds up under the real-world pressures that you are going to run into.</p><div><hr></div><p><em><a href="https://www.linkedin.com/in/brianallbee">Brian Allbee</a> is a Staff Software Engineer at Cleerly and the author of <a href="https://www.packtpub.com/en-us/product/hands-on-software-engineering-with-python-9781835888018">Hands-On Software Engineering with Python</a>, published by Packt.</em></p>]]></content:encoded></item><item><title><![CDATA[Building Agent-Ready APIs in Production with Erik Wilde]]></title><description><![CDATA[On OpenAPI 3.2, agent-ready APIs, and why MCP might not be the answer you think it is]]></description><link>https://deepengineering.net/p/building-agent-ready-apis-in-production</link><guid isPermaLink="false">https://deepengineering.net/p/building-agent-ready-apis-in-production</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 20 May 2026 20:06:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/iJXKkD5ySsY" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://ch.linkedin.com/in/erikwilde">Erik Wilde</a> has spent more than 12 years working on APIs in every form, from communication protocols to enterprise API platforms, governance frameworks, and now the question of what it takes for APIs to actually work for AI agents. He holds degrees in computer science from TU Berlin and a PhD from ETH Zurich, has contributed to multiple open standards, and is an OpenAPI Ambassador at the <a href="https://www.openapis.org/">OpenAPI Initiative</a>. He currently works at <a href="https://jentic.com/">Jentic</a>, where he focuses on making API landscapes usable for the next generation of agentic consumers. </p><p>Erik joined Deep Engineering Live interview session to talk about OpenAPI 3.2, what agent-ready APIs actually look like, and why he is more skeptical about MCP than most people expect.</p><p>Watch the full conversation below.</p><div id="youtube2-iJXKkD5ySsY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;iJXKkD5ySsY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/iJXKkD5ySsY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em><strong>A note on format</strong>: this session was recorded live as part of the Deep Engineering Live Interview Series. The transcript below has been lightly edited for clarity and readability. Audience members joined the conversation and asked questions directly during the session.</em></p><div><hr></div><p><em><strong>Q. Tell us about your background and how you ended up working on APIs.</strong></em></p><p>I have been working on APIs, in some shape or form, all of my life. I started with communication systems and protocols and then moved into the API space proper about 12 years ago. I have mostly worked for companies that sell enterprise software in that space, so typically API gateways and API platforms, the kinds of things where large companies have a lot of digital capabilities and a lot of those have APIs. More and more, companies have realized that the better you maintain, manage, extend, and govern that real estate, the easier it becomes to develop new applications and to realize potential that is within the company but needs a little bit of digging to get to.</p><p>Then about a year ago I met the two founders of Jentic, and they described to me what they were building. Very briefly, what they want to do is build a platform where agents can use APIs, because oftentimes the APIs that exist might not be the ideal ones for agents, and you also might want to control those agents a little more because you might not be confident they always do the right thing. We all know that AI has a tendency to sometimes have surprising ideas. I really liked that idea, so I decided to join. I have been at Jentic now for just over half a year and it has been a great experience. I still talk about APIs because in the end, without APIs, there is simply no AI.</p><p><em><strong>Q. OpenAPI 3.2 shipped last September. What changes have the highest operational impact for engineering teams, and which are mostly nice to have?</strong></em></p><p>3.2 is a maintenance release. It is backwards compatible and does not change things dramatically. What it is not, and I want to start there, is AI focused. That is what we are planning for the next version, 3.3, where we really want to think more aggressively about what it would take to make OpenAPI specifically more AI friendly.</p><p>That said, even in 3.2, some of the improvements are more meaningful than they might first appear. The tag system has been extended so that tags, which you use to group and annotate operations, are now a hierarchical space rather than a flat one. You can have tags and subtags and so forth. That is something people always wanted to do. The reason it matters for AI is that anything that makes an API description semantically richer, anything that allows descriptions to carry more meaning, is valuable for agents. So thinking about how you describe your APIs not just as technical endpoints but as semantic services, with rich schemas, descriptions at every level, and well-defined error messages, that is where I think the real operational value lies right now.</p><p>At Jentic we have released a scoring mechanism for APIs so you can find out whether your API is AI friendly or not. A lot of what that scoring looks at is the kinds of things that have always been good API design practice: put in more descriptions, include examples, make your error messages clear and actionable. The difference now is that where a human developer might look at a poorly described API and figure it out from experience and context, an agent that cannot figure out how an API works will simply move on to the next one. It has less context and less tolerance for ambiguity. So the APIs you design now will probably be around for a couple of years, and starting to think about this new class of consumers is worth doing today.</p><p><em><strong>Q. Streaming is also now explicitly supported in 3.2. When teams document streaming, what details separate readable from implementable and testable?</strong></em></p><p>Streaming always was something people were doing. I think it has just become so much more visible because that is how all the AI APIs work. When you use a chatbot and you watch the response appear word by word, that is streaming in action. And what 3.2 does is give you a slightly more explicit way to document that in OpenAPI. That is actually a very common pattern with OpenAPI improvements over the years. It is not that something entirely new is added. It is more that people can now formally document something they have been doing all along, but that was not well covered by the specification.</p><p>WebHooks are another good example of this. WebHooks have been popular for a long time. I was surprised when somebody gave me a statistic saying that around 60 percent of the 100 most popular APIs use WebHooks. That is a remarkably high number, but it makes sense because WebHooks are a convenient pattern. You do something with an API, and at some point the API can call you back and say this process is finished, go and fetch your results. People had been doing that for a long time, but it was never explicitly supported in OpenAPI. And then at some point the specification simply gets extended to cover what practitioners are already doing. That is what makes it more complete over time.</p><p><em><strong>Q. The 3.2 tag structure now supports nesting. How do you use tags as information architecture for large API catalogs, and how do you govern that taxonomy across teams?</strong></em></p><p>That is a good and very demanding question, because it goes well beyond OpenAPI and into whether you have a data dictionary or some general framework for how things get named in your organization. Organizations always have a hard time doing that because it is hard to agree on terms, and it is hard to make sure that everyone understands which terms exist, what they mean, and when to use them. Tags are no different. They give you a way to assign meaning to things in your OpenAPI description, but what that meaning is is entirely up to you.</p><p>Until now tags were relatively minor things. The typical pattern was to say here are all the operations about customers, here are all the operations about products, and so on, and documentation tools would then group things by tag. With the hierarchical tag structure in 3.2, you could go much further. You could have a hierarchy of unlimited depth if you want, where each thing in your API is linked to some kind of data dictionary or ontology. I have not seen people doing that yet, but I am pretty sure they will start.</p><p>That said, my recommendation would be not to go crazy building a complex standalone tag taxonomy inside OpenAPI. If you start introducing complex terminology with different hierarchies and groupings, you probably also need to align that with every other place in your organization where things get tagged, whether that is databases, document stores, or wherever you manage information. So check what your general information architecture looks like. What dictionaries or terminologies are already established? Then think about how you map those into the OpenAPI tag model rather than inventing a whole new taxonomy that lives only in your API descriptions.</p><p><em><strong>Q. On linting as a quality gate: how do you design a rule set taxonomy that maps cleanly to real ownership, the way platform teams and product teams each have different responsibilities?</strong></em></p><p>What linting is being used for right now is governance and a level of automation. The goal is that when people start designing or changing APIs they get quick feedback on whether they are following guidelines or not. A good number of organizations publish their rule sets openly on GitHub. I have a collection of around 30 or 40 publicly accessible ones. The Zalando ones are popular because they have been around for a while. Adidas has some solid ones. There are also some published by government and e-government initiatives. So there are plenty of references.</p><p>Linting is useful but it has real limitations. The popular tools, whether that is Spectral, Vacuum, or Redocly, all work in a similar way. You have rules that apply to certain parts of your OpenAPI description and they check for structural conditions. Something like, this operation must have a description and the description must be at least 20 characters. It is really a structural check. And that is useful. I would absolutely recommend doing it.</p><p>What I am not a big fan of is just reusing existing rule sets wholesale. I would always say start owning this, build up your own in a collaborative fashion. Have a GitHub repository somewhere where developers can propose and discuss new rules, argue for whether a guideline is worth following, and then get it merged into your shared rule set once there is enough agreement. You might also have different rules for different stages of the API lifecycle. Some rules are so important that every code check-in has to follow them. Others might only apply to APIs you expose to external partners, where you want higher quality standards. So you end up with rule sets that are tuned by the consumer type or the lifecycle stage, or both.</p><p>But as I said, linting has limits. At Jentic we use Spectral and Redocly as part of our API scoring checks, but we also have a good number of LLM-based checks, because if you are scoring APIs for AI readiness, what matters is not just whether a description field exists but whether it is written in a way that is actually useful for an agent. Those are the kinds of checks that typical linting tools cannot do because they operate at the structural level. So linting is a solid and by now fairly standard first line of defense, but also look a little beyond it.</p><p><em><strong>Q. How do you set severity levels like error, warning, and informational, and what is an exception policy that avoids lint fatigue without lowering the floor?</strong></em></p><p>Severity levels really should be what you would expect. If something is non-negotiable and needs to be fixed before anything moves forward, that is an error. There is no discussion. Then you have warnings, where the message is that this is not great but it is acceptable, though you should consider fixing it. It gives the developer a signal without blocking them. And then informational messages, which honestly I am not sure are that interesting for developers to act on directly. What I have seen done a couple of times is that informational-level messages are not really meant for developers to read at all. They are intended for downstream tooling. The linter surfaces an observation that is then picked up by some other tool in the pipeline. So the informational channel becomes a way for the linter to communicate with tooling downstream rather than with the developer.</p><p><em><strong>Q. On large specs with tens of thousands of lines, linting performance and PR feedback loops become real constraints. What repository or spec structuring patterns reduce friction without fragmenting the contract?</strong></em></p><p>What you probably want is to avoid always linting the whole thing. Large specifications are never in one file. They are assembled from a whole bunch of sources, schemas, references, and components from various places. So it makes much more sense to have your checks in place at those individual source locations rather than only at the assembled specification level. Instead of linting the full spec at the end of every pipeline run, start linting when you make changes to the schemas and the smaller pieces that feed into the overall description.</p><p>If you do that with a reasonable level of discipline, you avoid the compounding effect where you finally lint the big spec and get hit with hundreds of errors you have been quietly accumulating. Do not treat linting as the last step. Do it as early as possible, as close to where the change is actually happening as you can. That is the pattern that keeps the feedback loops short and the debt manageable.</p><p><em><strong>Q. There is a proposal for OpenAPI 3.3. What are you personally most interested in seeing there?</strong></em></p><p>For me, because of where I work right now, the big issue is how we could improve OpenAPI specifically with a focus on AI. We have not done that so far in any serious way. There are a whole bunch of discussions within the OpenAPI Initiative around how that could be done.</p><p>Some of it is about semantics. Some of it is about making clearer when and how long an API is actually going to be around, which is something agents care about in ways that human developers traditionally have not. Agents always use an API at runtime. They discover it, decide it looks like a good API to use, and then need to figure out what it does, what it does not do, what its side effects and constraints are. All of that could be surfaced in a much more accessible way through the API description itself rather than sitting only in human-facing documentation.</p><p>One idea I find genuinely interesting is the relationship between OpenAPI and Arazzo. Arazzo is a workflow language, published by the OpenAPI Initiative, that lets you orchestrate sequences of OpenAPI interactions. You can say: to accomplish this goal, call this endpoint, then that one, then that one. It is a simple orchestration language layered on top of OpenAPI. What would be really cool is if an OpenAPI description could link to an Arazzo workflow and say, if you use this operation, it actually makes the most sense as part of this workflow you can find over there. Figuring out multi-step workflows is one of the hardest things for agents to do right now, and Arazzo is genuinely good at describing those. We just need to make it discoverable. So that is one of the directions I would love to see 3.3 move in.</p><p>And as a reminder, the OpenAPI Initiative is open source and open to everyone. You do not need to be a member, you do not need to pay anything. The discussions happen primarily on Slack. If you have ideas or questions, just come and join. It is a very active and welcoming community. Check out openapis.org, and note that the S matters.</p><p><em><strong>Q. With MCP consolidating under the Linux Foundation&#8217;s AI foundation, what is the minimum governance surface an enterprise needs before agents can use tools broadly?</strong></em></p><p>I am still a little skeptical about MCP, honestly. I may very well be wrong, but what I would really encourage everyone to do is first think about your API estate and really invest in your APIs, rather than obsessing too much over MCP specifically. Whatever you invest in better APIs becomes useful for everyone. Developers can use it, agents can use it, partners can use it. If you invest specifically in MCP, that investment is effectively scoped to LLM consumers. And that may sometimes make sense, but it is important to keep in mind that the API landscape is the foundational layer you will be working with long term, and MCP may or may not stick around.</p><p>At Jentic we do support MCP because at this point you have to, but we are not deeply invested in MCP itself. If MCP went away and something else came along, that would not be a significant problem for us. We think of what we do as delivering capabilities to agents, and MCP is the current delivery mechanism. You need a delivery mechanism, but I would not build too many things that are MCP-specific. That would be my personal view.</p><p><em><strong>Q. From an audience member: what makes an API truly agent-ready in production compared to a standard REST API?</strong></em></p><p>One of the things I like to use as an illustration is the GitHub API. The current GitHub API version three has around 1,100 operations. GitHub is a complex product and there is a lot you can do with it, so 1,100 operations is not unreasonable. But for an agent to work directly with that API is quite complex, because a large number of those operations need to be combined in a certain way to produce the workflows that you actually want to accomplish on GitHub.</p><p>Now compare that to the GitHub MCP server, which has around 70 tools. Way fewer, and they are much higher level. They represent entire workflows, entire things you might want to do on GitHub, rather than the more atomic operations you find in the native API. What I would argue is that if you had a genuinely agent-friendly GitHub API, it might also just have around 70 operations. Not 1,100. Right now those 70 are available through MCP because that is what GitHub decided to build, and that is fine, but the point is that if you have an agent that wants to get things done, it will be significantly happier with 70 well-described higher-level operations than with 1,100 lower-level ones.</p><p>The properties that make an API agent-ready follow from that. It should not be too fine-grained. The descriptions should be written at a level that is meaningful for an LLM, which means intent-based and human-readable, not just technical. It should have examples, and ideally multiple examples rather than just one. Error messages should be meaningful and actionable, giving the agent enough information to understand what happened and what it might do next. And if you make those improvements, you almost certainly also improve the developer experience as a side effect, so it is not a speculative investment.</p><p><em><strong>Q. On API deprecation and sunsetting: how should agents handle the lifecycle signal that an API they depend on is entering a sunset cycle?</strong></em></p><p>Deprecation and sunsetting are genuinely important to me. I have written some small standards for how an API can actually surface that information at runtime. And I think we will see more and more of these runtime mechanisms being built out, because agents consume APIs at runtime by design. They discover an API, start using it, and then ideally they should also be able to discover that the API is only going to be available for another two weeks. At that point, a well-designed agent might alert someone, or start looking for a replacement, or whatever the right behavior is for that situation. What exactly to do about it is a separate design question. But as a consumer of an API, this is information that is relevant, and if we can surface it at runtime, consumers can react at runtime. That feels like an obviously good thing to pursue.</p><p><em><strong>Q. On request and response schema design: how do you design schemas so that an LLM can reliably choose the correct operation, handle partial failures, and avoid duplicating side effects?</strong></em></p><p>Schema design becomes part of the general question of how you design OpenAPI for AI consumption. You want descriptions in your schemas, not just in your operations, so that an LLM can understand what individual fields actually mean rather than just their names and types. Names that carry meaning help too. Parameters named X, Y, and Z are much harder for an agent to reason about than parameters with names that reflect their actual intent.</p><p>Beyond that, I think we are going to see interesting evolution in how APIs handle the granularity of what they return. Right now the standard REST model is relatively static: here is a request schema, here is a response schema. But if you are working with agents that are trying to minimise token usage and context pollution, there is a real case for APIs that can return only the fields that were actually asked for. GraphQL has a nice built-in capability for this, which is one of the things that makes it interesting for agentic use cases. REST does not have that natively, but you could layer something on top. We will see how that evolves, but it is one of the more interesting design questions in this space right now.</p><p><em><strong>Q. What workflow patterns show up repeatedly when enterprises actually start working with Jentic, and what makes them stable as APIs underneath them change?</strong></em></p><p>One example we were not expecting, which is always a good sign when you start talking to real enterprises, is the partner integration scenario. If you have a relatively complex API that you expose to partners, that is a large engineering effort for each of those partners. They have to understand the whole API even if they only need a small part of it.</p><p>What we now actively pursue, because it keeps proving useful, is creating specific workflows for specific partners. You say, this partner only wants to do these particular things, so they get a set of workflows built on top of the API that match their actual use cases. They do not need to understand the full API surface. They just need to understand the workflows that were created specifically for them.</p><p>And the stability point is interesting. As long as you develop your APIs in a backwards compatible way, those workflows remain stable even as the underlying APIs change. As a workflow user you do not even need to know that the APIs underneath now do additional things. You just keep invoking the same workflows and they continue to work. The moment you break a backwards compatible API is the moment you also break the workflows depending on it. So the discipline of backwards compatibility pays off at every layer.</p><p><em><strong>Q. Looking ahead six months, what should a senior engineer or platform engineer watch closely in standards, tooling, or governance for agent-facing APIs?</strong></em></p><p>What I would recommend, starting from tomorrow morning, is to begin thinking about agents in your planning even if you do not have them yet. And I acknowledge that the term agent has become fairly meaningless at this point. Everything seems to be called an agent now. But what I do see when talking with organisations is that certain types of agents are already getting real use, customer support agents and some HR agents being the most common. These are agents that are useful across industries, and you can mostly buy them, hook them up to your documentation, and they work.</p><p>What you see much less of right now, despite all the talk, is what I would call real business agents in production, where a piece of software can sense things, take action, and make decisions. Agents that actually have agency. And I believe we will see more and more of these, not necessarily all at once, but incrementally. You trust them with a little more next year, and a little more the year after.</p><p>Because of that, I would highly recommend making the AI readiness of your APIs part of your standard practice now. API landscapes evolve slowly. Whatever you design or change today will probably be around for a year or two or three before you touch it again. So ask yourself whether your linting and your design practices are optimising only for developer experience, or whether they are also starting to account for agent experience. The good news is that optimising for agent experience tends to improve developer experience as a side effect. You are not making a speculative bet. You are making something better for everyone while also preparing for what is coming. If you work on API platforms or in platform engineering, start thinking now about how your API landscape will need to evolve as you have more and more agentic consumers. Because it is going to arrive. That is at least my personal view.</p><div><hr></div><p><em><a href="https://ch.linkedin.com/in/erikwilde">Erik Wilde</a> is Head of Enterprise Strategy at <a href="https://jentic.com">Jentic </a>and an OpenAPI Ambassador at the <a href="https://www.openapis.org/">OpenAPI Initiative</a>. He is the creator of the Getting APIs to Work channel on YouTube. This interview was conducted by <a href="https://in.linkedin.com/in/s-jan">Saqib Jan</a>, Editor-in-Chief of Deep Engineering.</em></p>]]></content:encoded></item><item><title><![CDATA[Design Patterns, Ownership Models, and Building Resilient Systems in Rust with Evan Williams]]></title><description><![CDATA[Why experienced developers have the hardest time, and what that tells you about the language]]></description><link>https://deepengineering.net/p/design-patterns-ownership-models-resilient-systems-rust-evan-williams</link><guid isPermaLink="false">https://deepengineering.net/p/design-patterns-ownership-models-resilient-systems-rust-evan-williams</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 13 May 2026 18:07:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/-ElpmT7DCX4" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.linkedin.com/in/evan-williams-1512092">Evan Williams </a>has been writing software for more than 40 years, across every layer of the stack and more programming languages than most engineers will encounter in a career. His book, <em><a href="https://www.packtpub.com/en-nz/product/design-patterns-and-best-practices-in-rust-9781836209461">Design Patterns and Best Practices in Rust</a></em><a href="https://www.packtpub.com/en-nz/product/design-patterns-and-best-practices-in-rust-9781836209461">, </a>published by Packt, is not a pattern catalogue. It is an argument for a different way of thinking about code entirely, aimed squarely at experienced developers who arrive in Rust carrying instincts that the language will refuse to accommodate. </p><p>He recently sat down with Deep Engineering to talk about what that shift requires, which traditional patterns break in Rust, the typestate pattern he finds almost impossible to stop talking about, and why he discovered it is harder to write bad Rust than good Rust.</p><p>Watch the full conversation below.</p><div id="youtube2--ElpmT7DCX4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;-ElpmT7DCX4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/-ElpmT7DCX4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em>A note on format: the transcript below has been lightly edited for clarity and readability.</em></p><div><hr></div><p><em><strong>Q. You have been in software for a long time. Tell us about your background and how your journey started.</strong></em></p><p><strong>Evan Williams:</strong> I have been in the software business for horrifyingly more than 40 years. Surprisingly, given that, my journey started when I was 14 years old, in the 1970s. My father was a very skilled electrical engineer, and we built a computer in the basement together, which had a 6502 processor and 1K of RAM. He hoped that I would become an electrical engineer because of my interest in the electronics. I became a programmer because I thought the programming was the most fun part. I have since then grown up with the industry, not from the very beginning of it, but certainly from fairly early on, and in particular grown up with professional software development from the beginning. I&#8217;ve touched every part of the stack, many, many programming languages, many systems, and I am intensely interested by this. If they didn&#8217;t pay me to do it, I&#8217;d do it anyway.</p><p><em><strong>Q. You have worked with languages like C and Python. What initially drew you toward Rust?</strong></em></p><p><strong>Evan Williams:</strong> I&#8217;m interested in programming languages in general, and I had heard about Rust almost at the very start. I looked at it and I said, this looks kind of mildly interesting, I&#8217;ll just remember that. And then I forgot about it. But years later, Rust had just barely reached 1.0, and I was working on a hardware project that had a few interesting characteristics. It needed to be in a location that we couldn&#8217;t access easily. It wasn&#8217;t going to be on the internet. It needed to be rock solid because people depended on it. And it was remarkably complicated software. The team talked about it and we made the decision to go with Rust, which none of us knew at the time. We all learned it together. And that was where I caught the Rust bug and have not lost it since.</p><p><em><strong>Q. What motivated you to write a book on design patterns in Rust at this stage of your career?</strong></em></p><p><strong>Evan Williams:</strong> There are sort of two questions tied up in there. One is, why would I be writing a book at all at this stage of my career? And the answer to that is, I have been remarkably fortunate to have people help me throughout my career and help me grow. And it makes me incredibly happy to be able to pay that forward and help the people who are learning at this point to grow themselves. This book is a vehicle for me to help people. Why Rust design patterns? Because I think the interesting thing about dealing with design patterns in Rust is very often they&#8217;re not what you need, and very often the traditional ones are not exactly what you need, and very often they can cause you problems that you didn&#8217;t have to have. So I wanted to save people that frustration and help them move along the path in a way that is a lot less painful, by not making the same mistakes that I made.</p><p><em><strong>Q. Rust has been around for some time but is now seeing increased adoption. Why do you think this is the moment?</strong></em></p><p><strong>Evan Williams:</strong> For a long time there were a lot of people like me who were excited about Rust, and there were people who were early adopters who were pushing for it. But right now what we&#8217;re seeing is a world where the language has matured, the ecosystem has matured, it&#8217;s reached a kind of critical mass. Now when someone is thinking about doing a Rust project, they&#8217;re not pioneers who are wandering out into lands unknown. There&#8217;s a huge community, a huge set of packages and software, great learning resources, and there are people who have had success that you can see who have done amazing things with Rust. People feel more confident and feel safer venturing into using Rust for things, whereas before it was a little bit more of a risk in their mind.</p><p><em><strong>Q. What gaps does Rust fill compared to more established languages like C, Java, or Python?</strong></em></p><p><strong>Evan Williams:</strong> I could just go over sort of the normal thing and say high performance and memory safety, and all those things are true. But Rust is also a powerful modern language that has a good feature set. And I feel like there&#8217;s a design gap of sorts. One of the things that&#8217;s great about Rust, and I&#8217;ll probably keep returning to this topic, is if you need something to be correct, if you need to be able to count on the code that you write, Rust helps you. Rust is your partner in doing that. You can still write code with bugs in it, but Rust makes it harder to do that and easier to write code that&#8217;s going to be solid. I think that&#8217;s a huge gap that it fills.</p><p><em><strong>Q. What kinds of systems or use cases benefit most from adopting Rust today?</strong></em></p><p><strong>Evan Williams:</strong> Systems that are mission critical in some way or other are really key Rust use cases. Yes, Rust has high efficiency and the memory safety is very important. But all of these features combine into a whole that make Rust a really powerful language for doing things that have to work. Things where failure is monetarily or in human cost even a terrible problem. That&#8217;s one of the powers of the language and I think it&#8217;s great for those situations.</p><p><em><strong>Q. One of the recurring themes in your book is that developers need to think differently in Rust. What does that mindset shift actually involve?</strong></em></p><p><strong>Evan Williams:</strong> There are a few things associated with that. One is that Rust is not an object-oriented language. It kind of looks like an object-oriented language in some ways, but it&#8217;s not. And if you carry with you an object-oriented language mindset, then you&#8217;re going to have nothing but trouble. It&#8217;s also a language that requires you to think carefully about the design of your code before you start writing it. It&#8217;s very easy to get yourself into trouble in Rust if you don&#8217;t plan what you&#8217;re doing. You have to start thinking about data and how it&#8217;s handled in a different way. You have to think ahead of time about where the data is flowing through your program, what it is, where it is going, how long is it going to live, who is responsible for it. These are things that you don&#8217;t really have to think about when you&#8217;re writing a Python program or a Java program. Those are good design principles in all of those languages, but Rust requires it of you.</p><p><em><strong>Q. Many developers struggle with the borrow checker early on. How should they reframe it as a design tool rather than a limitation?</strong></em></p><p><strong>Evan Williams:</strong> The thing about the borrow checker is it&#8217;s there to help you, and it is very easy to get into a mode where you&#8217;re fighting with it and you feel like it&#8217;s your enemy. But in fact, what it is doing is encouraging you to build your code in a solid manner. It&#8217;s encouraging you to think about not just what data you have, but how it&#8217;s going to be used. In something like Java or Python, with some amount of plumbing you can get anything from anywhere. You don&#8217;t really have to design your program in a highly organized way where you&#8217;ve thought about the data flows. But you&#8217;re much better off if you do. I think the principles that the borrow checker forces you to adhere to in Rust are the exact principles that you should be using in every programming language. But you don&#8217;t have to. So it&#8217;s very easy to not think about those things. The borrow checker is your friend because it prevents you from making a messy design. It prevents you from making a broken design. It prevents you from writing whole classes of bugs that you will then spend many hours trying to find. I have found it to be an incredible partner in writing code that allows me to sleep at night.</p><p><em><strong>Q. What are the most common mistakes developers make when they try to apply patterns from other languages directly in Rust?</strong></em></p><p><strong>Evan Williams:</strong> The principal thing is, number one, trying to use Rust as if it&#8217;s an object-oriented language. It&#8217;s not going to work. Viewing the compiler errors as things that you need to figure out how to work around is something that virtually everybody who starts with Rust does. And I can&#8217;t emphasize this enough: one of the things that&#8217;s sort of interesting about Rust is the more experienced you are, the more years you have doing something in some other language, the more trouble you&#8217;re likely to have, because you have patterns of thought that come from those languages that you don&#8217;t even realize are there. That&#8217;s one of the things that can really get you into trouble. Treating the compiler errors and the problems with the borrow checker as things to work around as opposed to signals that your program needs some redesign, and thinking about things from an object-oriented perspective, those are the main traps.</p><p><em><strong>Q. Traditional design patterns were created with object-oriented languages in mind. How do they evolve when applied to Rust?</strong></em></p><p><strong>Evan Williams:</strong> In a number of different ways. First, some of them evolve into entirely new forms or almost out of existence, because being a modern language, Rust has things like enums and all sorts of very advanced use of generics. These are things that make a lot of these design patterns either less necessary or unnecessary. Another way that they evolve is that since there is no inheritance in the language, you have to rethink a lot of the design patterns where, say, you would have an abstract base class. You can&#8217;t have that because there&#8217;s no such thing. So you would lean into traits. And every single design pattern that you use is affected by the borrow checker and by memory discipline in a way that is not normal in something like Java or Python.</p><p><em><strong>Q. Are there any patterns that become unnecessary or even counterproductive in Rust?</strong></em></p><p><strong>Evan Williams:</strong> The one that immediately springs to mind is Singleton, which is so useful that I was using it before it had a name. But it&#8217;s useful in Python or Java. In Rust, it tends to be either completely unnecessary because other features of the language make it unneeded, or it tends to encourage designs that are really not necessary and where a much better approach could be used. There are a few occasions where the singleton pattern, as it stands, is actually useful, but more often than not, it&#8217;s getting you into trouble.</p><p><em><strong>Q. What Rust-specific patterns do you think are the most powerful and still underutilized?</strong></em></p><p><strong>Evan Williams:</strong> The one that I get so excited about that I have to limit myself so that I don&#8217;t spend the rest of this conversation talking about it is the typestate pattern. This is something that was not invented for Rust, but you would think that it had been because it works for Rust so perfectly. It&#8217;s a way of developing state machines and systems that have state that evolves where invalid state transitions aren&#8217;t just errors, they&#8217;re impossible to write. The compiler won&#8217;t compile them. It represents a huge advance in the way that such systems are written because now instead of runtime errors, you have a state machine that is guaranteed to work because every transition either is a valid transition or it won&#8217;t even compile. That&#8217;s an amazing thing and I love that feature. Not invented for Rust, but it fits Rust so perfectly, it&#8217;s hard to believe it.</p><p><em><strong>Q. Your book emphasizes clear data flow and system architecture. Why is unidirectional data flow so important in Rust systems?</strong></em></p><p><strong>Evan Williams:</strong> Thinking about the way data flows through your system in Rust is crucial because Rust makes it much more difficult to thread things back. As a general rule, because you can&#8217;t have many different clients holding on to different things in different places, especially not being able to write to different things in different places, having a clear direction of data flow makes it much clearer and much easier to create a consistent system that&#8217;s going to compile and work. By saying, I have a chain of ownership that moves down but never moves back up, you are now much more likely to have a system that is going to work in an environment where the number of references that can be held is very limited and you have to be very careful about memory safety. Data flowing down is something that feels natural and smooth and just works. Data trying to fight the stream back up is going to end up giving you problems because the borrow checker is not going to like you.</p><p><em><strong>Q. How does Rust&#8217;s ownership model influence architectural decisions at the system level?</strong></em></p><p><strong>Evan Williams:</strong> I think one of the crucial things about it is that because you have to be so thoughtful about how your data moves and who owns it, you have to also think about what that means for your system. You need to think about who controls what, how it is controlled, and you need to start from the very beginning thinking about the boundaries of your program and the system architecture, dividing things up into areas of responsibility. Because unlike Python or Java, you can&#8217;t have links going all over the place. The borrow checker is never going to accept that. Rust gives you lots of great tools for creating boundaries of abstraction that make it a lot simpler to write code that doesn&#8217;t have hidden or difficult-to-find connections between things. You know who owns things, you know who is able to write things. In order to do that, you have to be able to think about things ahead of time and think about what parts of your program own what, and create clear boundaries and areas of responsibility for each piece of the system that you build.</p><p><em><strong>Q. Can you share an example of how Rust leads to better system design compared to other languages?</strong></em></p><p><strong>Evan Williams:</strong> One of the examples in the book, one of the projects that we build in the book, is a miniature publish-and-subscribe system similar to Kafka, but very, very much smaller. It is amazing how easy it becomes to make something that is solid and clean in that circumstance. Because Rust has move semantics, you know that if something leaves here and goes here, it&#8217;s now not here anymore. It&#8217;s there. There&#8217;s no question about things like having references dangling or anything like that. The clarity of things moving through the system, the clarity of being able to have immutable data in a lot of places and knowing who can and can&#8217;t modify any piece of data, it just makes the design of the system so clear and it makes it so much harder to make a system that doesn&#8217;t work. By doing things that way, you have something where you can have a potentially very complicated system and yet have complete confidence that every piece of it individually is going to work right and that they&#8217;re going to work together as a system.</p><p><em><strong>Q. What are the real-world challenges that engineering teams face when adopting Rust in production?</strong></em></p><p><strong>Evan Williams:</strong>  One thing that often happens is that when a team adopts Rust, because of the challenges of learning to work in the language, velocity can go down at first. The team can find itself actually moving slower. Once the team becomes very well acquainted with Rust, velocity can increase dramatically, but there is a period of time where it seems like things have gotten worse. Another problem that often happens is that, as I said earlier, more experienced developers often have a harder time adapting to it. And the last thing I&#8217;ll mention, although it&#8217;s a lot better than it used to be, is Rust is still not 100% in terms of the kind of rich libraries that you&#8217;d find in, say, Java or C, which are just older languages. There&#8217;s more out there that supports those languages, although Rust is certainly catching up and it&#8217;s remarkable how far it&#8217;s come.</p><p><em><strong>Q. When would you advise against using Rust for a project?</strong></em></p><p><strong>Evan Williams:</strong> There are a few things. If you&#8217;re doing some kind of prototyping, Rust is harder to prototype in and you really want something more like Python. If you&#8217;re working in certain niche environments where the tooling is not there, that&#8217;s a place where you don&#8217;t want to try to fight with the tooling to try to get Rust to work. It&#8217;s good to just work with the native things that are there. There are places where a dynamic language like Python is just a much easier thing to use and will more perfectly fit what you&#8217;re trying to do. And I also think there are a few areas where Rust has the potential to do a lot but is still catching up. User interfaces, for example. There are certainly user interface frameworks and libraries, but doing a website in Rust is still kind of a feat. And it&#8217;s an awful lot easier to use the tools that everybody else is using to accomplish that goal.</p><p><em><strong>Q. What is the best way to introduce Rust to a team without overwhelming your developers?</strong></em></p><p><strong>Evan Williams:</strong> The thing that you need to do to start is find a piece of work that you can work on that has a limited scope and which is not in the critical path, because there are going to be roadblocks and bumps as people learn. What you don&#8217;t want to do is jump into saying, we&#8217;re just going to rewrite our project in Rust now. Pick a small piece, focus on that, gain confidence and mastery of the language, and then use that to build upon it and start bringing in more things.</p><p><em><strong>Q. How should developers balance performance, safety, and complexity when designing systems in Rust?</strong></em></p><p><strong>Evan Williams:</strong> One of the nice things about it is that all of those things sort of come with the language. The thing that developers need to do is focus on letting the language help you. Rust will give you all of those things if you focus on thinking in the language and using patterns and features that are natural to the language, as opposed to trying to retool something from another language that doesn&#8217;t really fit. There are so many things that people try to do to get around problems that they have where if they just use the features of the language as they exist and did things in a way that&#8217;s more natural to the language, all of those things just fall out.</p><p><em><strong>Q. How do you see Rust evolving over the next couple of years in terms of adoption and ecosystem?</strong></em></p><p><strong>Evan Williams:</strong> There are a couple of different ways you can answer that. The ecosystem is going to get richer and people are going to be branching out in the set of use cases, hitting areas that right now Rust has relatively weak support for, but where I think people are building all the time. As larger and larger projects are built, there is going to be more refinement of the language itself, but more importantly, more refinement of the use of the language. And I think this is important. This book that I wrote is not just about the language itself, but the way to use the language. And I think the Rust community and the people who are building in Rust are going to be defining and creating new ways to use it that are unique and leverage its power to do things that right now we haven&#8217;t even thought of.</p><p><em><strong>Q. Did any of your own assumptions about Rust change while you were writing the book?</strong></em></p><p><strong>Evan Williams:</strong> It&#8217;s interesting because one of the things that changed is I came to recognize, through working on the early chapters about what not to do, that in Rust it&#8217;s actually more work to do things wrong. And I think that&#8217;s one of the things that surprised me. I knew that you had to think in a new way and do things differently, but when I went back and tried to write bad code in Rust, it was much harder than writing the good code. That&#8217;s an interesting perspective that just didn&#8217;t even occur to me.</p><p><em><strong>Q. For developers picking up your book, what key takeaway do you hope they walk away with?</strong></em></p><p><strong>Evan Williams:</strong> The key takeaway is that the thing you want is not to learn a particular set of patterns. My book is full of what the title says, how to deal with design patterns in Rust. But the much more important thing is changing your mindset. If I can help people to recognize that there is a new mindset that they need, that&#8217;s the key thing. And I see so many people who become frustrated with Rust because Rust has such an unusual learning curve. In most languages, it&#8217;s sort of a steady progress. Maybe you plateau a little bit, but you&#8217;re always going up. With Rust, very often it seems like you&#8217;re learning and learning and learning and getting better and better. And when you reach a certain level of complexity in your programs, it feels like things are getting worse. And that&#8217;s because of the mindset. Helping people understand that will save them so much pain. That&#8217;s what I want people to take away from the book.</p><p><em><strong>Q. Is there something most developers underestimate about Rust?</strong></em></p><p><strong>Evan Williams:</strong> I think the thing that people perhaps underestimate about Rust is it&#8217;s not just about memory safety and all these other things. It&#8217;s a really powerful modern programming language. It brings so much to the table that has nothing to do with memory safety or thread safety or any of those other things or high performance. It&#8217;s just a very clean, beautiful language to write in because it brings so many modern innovations that other languages are sort of stuck having to drag along historic pieces of syntax alongside. Rust is a pleasure to write in. It&#8217;s just sometimes the borrow checker can be a little annoying.</p><div><hr></div><p><em><a href="https://www.linkedin.com/in/evan-williams-1512092">Evan Williams</a> is the author of <a href="https://www.packtpub.com/en-nz/product/design-patterns-and-best-practices-in-rust-9781836209461">Design Patterns and Best Practices in Rust</a>, published by Packt. This interview was conducted by <a href="https://www.linkedin.com/in/s-jan">Saqib Jan</a>, Editor-in-Chief of Deep Engineering.</em></p>]]></content:encoded></item><item><title><![CDATA[Computer Architecture in an AI-accelerated World with Jim Ledin]]></title><description><![CDATA[On memory hierarchies, GPU mechanics, hardware abstractions, and what engineers get wrong by ignoring the hardware layer]]></description><link>https://deepengineering.net/p/computer-architecture-in-an-ai-accelerated-world</link><guid isPermaLink="false">https://deepengineering.net/p/computer-architecture-in-an-ai-accelerated-world</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 06 May 2026 18:15:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9f371179-665a-4e1b-939b-ad7b5b50839d_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.linkedin.com/in/jimledin">Jim Ledin</a> has been thinking about what happens between the instruction and the silicon for over thirty years. He is the CEO of <a href="https://ledin.com/">Ledin Engineering</a>, an expert in embedded software and hardware design, and the author of <a href="https://www.packtpub.com/en-us/product/modern-computer-architecture-and-organization-9781806028023">Modern Computer Architecture and Organization</a>, now in its third edition, published by Packt. His career spans embedded systems development, battery management software for electric vehicles, and cybersecurity assessment and penetration testing for safety-critical systems including self-driving vehicles.</p><p>The <a href="https://www.packtpub.com/en-us/product/modern-computer-architecture-and-organization-9781806028023">third edition</a> comes out at a moment when the architecture conversation in software engineering has narrowed almost entirely to one question: what hardware should run AI workloads. Ledin&#8217;s answer is more nuanced than the GPU consensus suggests, and it is grounded in the kind of bottom-up reasoning that most application developers have never had to apply. </p><p>And this conversation covers where that consensus is incomplete, what engineers building AI systems are getting wrong about memory and parallelism, why abstraction layers become dangerous when they hide hardware costs, and what the architecture of a self-driving vehicle teaches you that distributed backend experience does not.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A.</p><div id="youtube2-Q21CJXfQLOk" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;Q21CJXfQLOk&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/Q21CJXfQLOk?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>Q. You have been working with embedded systems and hardware design for over thirty years. What first pulled you toward understanding what was happening at the hardware level rather than just writing code?</strong></em></p><p><strong>Jim Ledin:</strong> My first real exposure to computer architecture was in the 1980s when I had a Commodore 64 with its 6502 CPU. I wrote a simple basic program to do some screen drawing, basically moving a dot around the screen with the joystick and pushing the button to draw the lines. And it was slow. It was so slow you could watch it moving one pixel at a time. That was painful to try to do anything with.</p><p>As time went on I learned a little bit about 6502 assembly language. I found out there were ways you could implement that through the basic interpreter. What you had to do was write out your assembly code by hand, convert it to the opcodes and data bytes, and then poke those bytes into memory. Poke is the basic command. Then you could transfer control and execute them. After I took the inner loops of the drawing program and implemented them in that way, the speedup was amazing. It shot the line across the screen faster than you could see. That episode really cemented for me how important it is to understand what is going on in the hardware of a system, and not just write what you want to do in your favourite language.</p><p>My current work is focused on embedded systems development and testing, as well as implementing cybersecurity for those systems and doing cyber testing on them. I have done quite a bit of work with electric vehicles, battery-powered systems, the battery management software, as well as the powertrain control systems. Also implementing cyber testing to evaluate what kind of vulnerabilities may be present in systems and trying to exploit those to demonstrate whether or not they actually exist. I have been doing that for over thirty years, embedded initially and then later adding in the cybersecurity aspect.</p><p>The architecture of computer systems is at the boundary where performance, system security, and behaviour in real-world situations all meet. You really need to understand, across all those domains, that everything works as expected and intended. You need an understanding top to bottom, not just what your high-level software does, but also at the hardware level. Not necessarily saying you need to understand what your compiler is doing, but know how the hardware operates, what kinds of things cause you to run into limits, and what you can do differently to improve performance, reliability, and security.</p><div><hr></div><p><em><strong>Q. Your book Modern Computer Architecture and Organization is now in its third edition. What had changed enough in the field to make a new edition necessary?</strong></em></p><p><strong>Jim Ledin:</strong> The book is intended to start at the beginning. I do not assume that readers have any background or experience with computer architecture, assembly language instructions, memory cache, pipelines, or anything like that. We start with history, where did the first computing devices come from, how were they developed. It even starts back in the 1800s when Charles Babbage designed a mechanical computer intended to be a general purpose digital computing system. It never actually got built, but many of the principles he developed, including pipelining and distributed processing, were implemented in that design. I thought it was remarkable that those concepts were being worked out that far back in history.</p><p>Then the book goes through the vacuum tube era in the 1930s and 1940s, the Intel 4004, which was really the first microprocessor, and then on to the 8086, 8088, the PC, the 386, which is basically the same base architecture that modern Intel and AMD processors in your PC and server-based systems use today. The code running on these modern systems is highly compatible with those systems from decades ago. It has gone from 16 bits to 32 bits to 64 bits, adding capabilities without removing previous ones.</p><p>The book walks through that history and then goes into detail on how processors work, starting with the 6502. That processor is simple enough that you can understand what is going on with its registers. It only has three. Nothing about it is overwhelming. Once you understand it, you can build upon it to get to the modern processors, which are far more complex.</p><p>What changed substantially since the last version of the book was the rise of AI workloads, particularly the shift from the fastest CPU available to very highly parallelised systems optimised to perform matrix computations. The new version, which came out in March, has a chapter that goes into detail on how GPUs operate, from the top-level modular structure down to the granular details of processor cores. There is another new chapter on transformer-based models, looking at them not as someone who designs them but more like a mechanic who wants to take them apart. We work through what calculations actually occur in GPT-2, which was one of the earliest large language models to break through as something genuinely new and important. The current frontier models have obviously evolved quite a bit since then, but they share many of the same fundamental characteristics. If you can go through GPT-2 and understand how it works, you are a very long way toward understanding the latest models.</p><p>We are also seeing real diversification of architecture. There were many years where computers for most applications were based on the same Intel-type architecture, but now across different application areas you are seeing GPUs, TPUs, domain-specific accelerators for things like Bitcoin mining, local AI in cell phones and cars, and the open source RISC-V processor which is available to everybody. You can design your own chip based on it, implement it in an FPGA, do whatever you want. It is a rapidly growing line of processor development and the book covers all of it.</p><div><hr></div><p><em><strong>Q. The argument that GPUs are the right architecture for AI and LLM workloads is often treated as settled. Where is that consensus incomplete?</strong></em></p><p><strong>Jim Ledin: </strong>GPUs are probably the ideal architecture for people and small companies that want to run language models locally. I have recently gotten the Gemma 27 billion parameter model running on an Nvidia RTX 4090, which is about the top end of consumer GPUs available today. For local and personal use, GPUs are the way to go.</p><p>But for larger scale deployments running much larger models, the trend is toward dedicated TPUs. A tensor is basically a multi-dimensional array. A matrix is a two-dimensional array, and tensors have more dimensions. Tensors are used widely across AI models, and the work going on inside the processing of those models is largely matrix multiplications operating on broken-down portions of higher-dimensional tensors. A TPU is a processor similar in concept to a GPU, but very specifically focused on the work of large language model tensor processing. GPUs, as the first letter implies, also have silicon dedicated for generating real-time video and handling things like gaming and video creation. TPUs do not use silicon for that purpose. They focus everything on the tensor work.</p><p>That is why systems like the Nvidia Blackwell architecture, designed for large-scale data centre applications, are built to have many components interconnected with extremely high-speed data links, working together as a supercomputer. For larger models, consumer GPUs are not really used. It is more the dedicated hardware that focuses on that work.</p><p>Another factor is that AI workloads are becoming increasingly memory bandwidth limited. That means it is taking more time to bring data into the GPU or TPU memory than it is taking for the computation itself to complete. These very high-end systems are implemented using what is called high bandwidth memory, or HBM. An HBM module is basically a cube made of a stack of RAM chips, so they hold a lot of memory and have very high bandwidth. On a TPU card you typically have several of these HBM modules, and they have a far higher data rate for transferring data in and out of the processing components than on a typical consumer GPU. This is also part of why it is becoming hard to find DDR5 RAM chips. A lot of production capacity for memory is going into high bandwidth memory modules, which cost more for the purchaser and make more money for the vendor.</p><div><hr></div><p><em><strong>Q. Software engineers working in the cloud often treat hardware as someone else&#8217;s problem. What does your book argue they are getting wrong, and what does that cost them?</strong></em></p><p><strong>Jim Ledin:</strong> If you write software and just ignore the hardware limits, that can lead to a lot of hidden costs. If your code is accessing memory in inefficient patterns, not using the cache memory within the processor effectively, and moving data around more than necessary, that can have significant performance impacts.</p><p>If developers understand how the memory access and caching processes work at the hardware level, they can often tailor code to work more effectively within those constraints and minimise latency. When the CPU requests data from memory and it is not available in its cache, it has to wait. You are giving the processor downtime when you want it to be processing data. A lot of that is unavoidable, but the amount of latency can be minimised by different approaches to optimising algorithms.</p><p>As an example, in a modern PC, each time you read something from DRAM, even if it is just a single byte, 64 bytes are transferred into the CPU cache. That is what is available at that point for the processor to work with. For best efficiency, assuming you have options, you would want your code to be working with data from that block before it moves on to something else, rather than bouncing around to other memory locations. If you access several other locations that cause them to be loaded into cache, and then that first block gets evicted, and then you go back and read it again, now you have to reread it. That is inefficient. When possible, you want to work through memory in a linear way.</p><p>And if you are working in a cloud environment, this not only has those performance issues but also results in higher costs, because you are paying for the usage of the system whether the CPU is actually crunching instructions or sitting idle waiting for a data item to come in from memory.</p><div><hr></div><p><em><strong>Q. If you are building AI systems today, what are the hardware concepts that would most change how you designed them, and what do most engineers not understand well enough?</strong></em></p><p><strong>Jim Ledin:</strong> Data movement can often be more expensive than the actual computation steps. The latency of moving large data structures across different levels of the memory hierarchy can dominate and leave a lot of compute bandwidth idle. This is a concern even with the very highest performance AI-focused systems. Getting the memory access right relative to the processing is a genuine challenge. You definitely do not want to be iterating across large data structures multiple times in an algorithm if there is a way to avoid it. Going through data linearly is probably going to give the best performance.</p><p>As you increase parallelisation of algorithms across cores and processors and across GPUs and other devices, other constraints appear. Synchronisation, where tasks on different processors need to sync up, is a real constraint. The communication bandwidth between processors, whether they are inside the same device or communicating board to board or rack to rack, all of these affect the efficiency and speed of processing, not just the number of cores you can throw at a parallel algorithm. It is important to understand the cost associated with all of these interactions among parallel activities and optimise around them to get the best overall performance.</p><p>And then optimising compilers do a great job of scheduling instruction execution and keeping pipelines full, but there are things you can do in code that make it harder for them to do that, and things you can do that make it easier. In performance-critical inner loops, minimising branching can help avoid pipeline stalls. Part of what goes on in a modern processor is trying to predict what will happen at a branch in your code, an if-else type block. The processor may guess right, which means it is very efficient, or it may guess wrong and have to back up and start down the other path. If you can minimise or eliminate branching within the most performance-critical loops, that makes it easier for the optimiser and the rest of the system to run as efficiently as possible.</p><div><hr></div><p><em><strong>Q. What is actually happening under the hood in a GPU that makes it effective for AI workloads, at a level that goes beyond the standard explanation about parallelism?</strong></em></p><p><strong>Jim Ledin:</strong> Most of the processing in a transformer-based AI model, at least 80% of the execution time, is these tensor operations, which are implemented in hardware as matrix multiplications. GPUs and TPUs have very specialised multiply-and-accumulate hardware specifically designed to perform these operations.</p><p>The current generation of Nvidia GPUs implements what is called single instruction multiple thread, or SIMT, execution. A group of 32 threads runs in lockstep, meaning they are all executing the same instruction but on different data streams. SIMT also supports branching, so you can have if-else logic in the code. But this has a performance cost. If you are executing through a stream of data on SIMT code and you come to a conditional instruction where some threads take the if part and some threads take the else part, the hardware executes one side, the if part, only on the threads where that condition applies, then goes back and executes the else part for the other threads. At the end of the block, they sync up and resume in lockstep. Your code can have conditional logic in these lowest-level operational sequences, but there is the drawback that you effectively have a pipeline stall where it has to go back and execute a different thread. You have the flexibility, but there is a cost.</p><p>GPU and TPU performance comes as much from high memory bandwidth, getting data in and out as fast as possible, and latency minimisation, as it does from effective thread scheduling across the many thousands of cores within a GPU. All of these things, memory bandwidth, minimising latency, thread scheduling, and using SIMT effectively, all affect GPU performance in addition to the raw ability to parallelise across cores. You really need to manage all of these aspects to get the best performance, not just maximise core count.</p><div><hr></div><p><em><strong>Q. The memory hierarchy from cache to RAM to storage is often discussed in theory but rarely in practice. Can you give a concrete example of where a misunderstanding of memory hierarchy caused a real performance problem, and what the fix actually looked like?</strong></em></p><p><strong>Jim Ledin:</strong> There was a web server in some Linux distributions in the early 2000s called Tux, which ran in kernel space. It avoided a lot of the transfers from user space to kernel space that a web server normally has to perform. It only served static pages, because since it was running in kernel they did not give the pages dynamic generation capability.</p><p>One issue with this server was poor cache locality. The amount of data it kept active on each request seemed to be excessive. Under high load, with lots of users hitting it at once, the state information grew to exceed the size of the level 2 cache in the CPU. Performance dropped off sharply.</p><p>Some engineers examined that and determined that by evaluating the cache limitation against how the code was structured, they could reorganise it so the amount of data per request would be smaller and therefore remain within the cache limit up to a much larger level of usage. Similarly, instructions also have a cache in the CPU, and by reorganising the processing and batching some things, they were able to increase the degree to which instructions would remain in the instruction cache during web server processing. The fixes they implemented increased application performance by about 40%.</p><p>This was basically examining the behaviour of the application in the context of the limitations of the processor hardware and coming up with solutions that respected those limits. For other applications, similar fixes might involve restructuring data. A large array of structures might be more efficiently processed as a structure of arrays in a way that better aligns with cache limitations. But in these cases, while the design approach is to look at the limits of the system and try to work within them, to really understand what is going to have a big impact you need to implement it and benchmark it in a realistic environment.</p><div><hr></div><p><em><strong>Q. There is a growing tension between the abstraction layers that frameworks provide and the hardware cost those abstractions hide. At what point does that become a serious engineering problem?</strong></em></p><p><strong>Jim Ledin:</strong> Early on in the development cycle, abstractions are great. They can greatly accelerate development and limit mistakes. Where it becomes dangerous is when abstraction obscures what is happening with the data layout in memory and the execution patterns, basically how the processor is interacting with data as the algorithm proceeds. This is especially critical in large-scale real-time systems with demanding performance requirements.</p><p>In addition to using abstraction where it makes sense, engineers need to understand what is happening underneath the abstraction in performance-critical applications. I am not suggesting abandoning abstractions. They are entirely appropriate at the level where they preserve meaning and understanding across a team. But they begin to create a problem where they obscure the costs.</p><p>The most effective approach is a two-layer design. Use the most expressive code at the edges of the system, and in the core, use more performance-aware code. It is not always obvious where to place the boundary between performance-aware code and more expressive code. It may take some benchmarking, trials, and iterations to identify the best location for that boundary. But knowing you need to draw it is the starting point.</p><div><hr></div><p><em><strong>Q. You work on architectures for systems like self-driving vehicles. What makes those architectures fundamentally different from a standard distributed backend system, and what should engineers working in conventional contexts take from that?</strong></em></p><p>Jim Ledin: A self-driving vehicle is both real-time and safety-critical. The software must meet all of its deadlines, its time limits for producing a response, or that is not just a glitch to blow past, that is a system failure, and that cannot be tolerated. There must be fail-safe responses when unexpected situations occur. Only in the most extreme circumstances, like an unrecoverable hardware failure, would the system be able to stop processing.</p><p>A self-driving vehicle tightly couples sensing, computing, and actuating, seeing what is around it, deciding what to do, and steering and controlling vehicle speed. That is pretty different from loosely coupled distributed systems. A distributed system might typically implement retry mechanisms if something fails, and if a system goes down there are online and offline redundant capabilities that can be brought up, basically switching to a backup. Rather than using that approach, safety-critical vehicles provide a level of redundancy where dual processors operate in lockstep. If one experiences a failure, the system continues on the one good one until a repair can be made.</p><p>This can be extended further. The American space shuttles had three computers operating in parallel. One advantage of three over two is that if you have two computers and they give different answers, you have to decide which one is good and which is bad. If you have three and two give one answer and one gives another, you probably know the third is bad.</p><p>The way engineers working with conventional distributed systems can apply these principles is in situations where the design needs to be fault tolerant while minimising or eliminating processing interruptions. Rather than waiting for a failure to occur, you have enough running capability in operation simultaneously that you can detect a failure and keep things going the whole time while bringing up redundant capability. A lot of large systems already operate this way, but systems that do not could potentially deliver a higher availability level using these techniques.</p><div><hr></div><p><em><strong>Q. For engineers who want to build real working knowledge of systems and hardware, what is the most direct path in?</strong></em></p><p>Jim Ledin: Start by understanding how processors operate at the simplest level of a single instruction. There are four steps in an instruction: fetch, decode, execute, and write back. Fetch is when the processor retrieves the opcode bytes from memory. Decode is when the processor assigns work to units within it, like an ALU for an addition instruction. Execute is when it actually does the computation. And write back is where it stores the results in registers, in memory, and in status bits within the processor. Essentially all processors operate at that very low level.</p><p>That mental model then scales upward to more complex processors and their capabilities. That is the reason the book starts with the 6502 processor. It is pretty simple, only three registers, 8-bit, nothing about it is overwhelming. But once you understand it, you can build upon that knowledge to get to the modern processors, which have hundreds, if not more, instructions available and many divergent capabilities. It all builds upon those very simple foundations.</p><div><hr></div><p><em><strong>Q. Looking ahead five years, what skills will matter most for engineers working at the intersection of software, hardware, and AI?</strong></em></p><p><strong>Jim Ledin: </strong>The most important thing is to stay up to date and remain aware of changes as technology advances. Four years ago when the previous version of the book came out, it was not at all clear to me, or I think a lot of people, what was going to happen with AI in the coming years. Pay attention to what is going on around you. Pay less attention to announcements driven by financial considerations or hype from companies focused on their performance in the stock market. Pay more attention to what is actually having an impact in the real world, and learn more about those things.</p><p>The sources matter. There are trustworthy websites with genuinely good information about current ongoing activities in CPU development and other computer-related areas, as well as more in-depth sources like scientific papers if you are willing to dig in at that level. Even pursuing formal education, which does not necessarily mean going back to college, could mean taking online courses to develop depth in areas where you might be behind. Certificate programs can be a real path to updating your skills.</p><p>Today, the thing is AI. Developers do not just learn programming languages anymore. You need to be learning how to interact with AI and use it effectively to develop better software. The way to really understand these systems requires the ability to reason across all of the abstraction layers, from the software framework at the top level all the way down to the hardware that runs the code. You do not need to break out the assembly code generated by your build tools, though that is sometimes valuable and can be very helpful, either for learning purposes or if you are really in a hot inner loop that needs maximum optimisation. More often it is about understanding the constraints, how the processor works best with pipelines and caches, and orienting your code to work within those environments.</p><p>It is also becoming increasingly critical to understand heterogeneous computing environments. It is not just writing code that runs on a CPU. You might have code that interacts with a GPU for a parallelised algorithm, whether it is a language model or something else. And there are specialised accelerators that may be implemented within large-scale systems that speed up specific parts of the operation. There is a lot to learn, and it takes curiosity and sustained attention to stay current.</p><div><hr></div><p><em><strong>Q. How would you explain the CPU versus GPU distinction to a senior software engineer who has never had to care about it before?</strong></em></p><p><strong>Jim Ledin: </strong>A CPU is optimised for low-latency execution of complex branching code. Branches do have an impact on performance, but CPUs are designed to handle that and minimise it. GPUs work best with highly parallelised, high-throughput execution of linear code, operating on massively parallel workloads. GPU cores work best when they are going through parallel streams with minimal branching.</p><p>If you are developing an algorithm and you are not sure whether it should run on a CPU or be split between a CPU and a GPU, the GPU only really becomes attractive when you have enough work for it to do that it can be parallelised, and enough that it will amortise the costs associated with moving data onto the GPU, launching the kernels to execute the code, and doing the management work to transfer data to and from the GPU.</p><p>The GPU is really not a general purpose computer. It is more of a specialised device that needs to be managed by something else. You cannot write a program that just runs on a GPU. It needs to be started and managed from a CPU, and you need to get enough benefit from the work you are doing to make all of that worthwhile. If you cannot keep the GPU busy with this kind of work, the CPU implementation may actually win, because it avoids the data transfer and scheduling overhead entirely.</p>]]></content:encoded></item><item><title><![CDATA[Clean C++ Code, and the Hidden Cost of Complexity with Sándor Dargó]]></title><description><![CDATA[On C++26, cognitive load, and the hidden price of clever code]]></description><link>https://deepengineering.net/p/clean-c-code-and-the-hidden-cost</link><guid isPermaLink="false">https://deepengineering.net/p/clean-c-code-and-the-hidden-cost</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 22 Apr 2026 11:30:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/a04535d6-a8d4-4ff0-bc53-bf31cb699f9a_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://fr.linkedin.com/in/sandor-dargo">S&#225;ndor Darg&#243;</a> has spent years making large C++ systems easier to maintain, safer to change, and cheaper to run. At Spotify he works on codebases where performance, binary size, and clarity have to coexist, and where the cost of getting any of those trade-offs wrong shows up in production. He writes daily about C++ on his blog, speaks at conferences, and sat down with Deep Engineering Live to talk about C++26, what it takes to write code that survives real-world conditions, and what the shift to AI-assisted development is doing to the way engineers work.</p><p><em>Watch the full conversation below.</em></p><div id="youtube2-vsdeOS8snN0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;vsdeOS8snN0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/vsdeOS8snN0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p></p><p></p><blockquote><p><em><strong>A note on format:</strong> the transcript below has been lightly edited for clarity and readability. During the live session on Deep Engineering Live, questions were displayed on screen for Sandor to read, and we also brought audience members on stage to ask their questions directly.</em></p></blockquote><p></p><p><em><strong>Q. There is a thread running through your talks on clean code, binary size, undefined behavior, and now C++26. What problem are you actually trying to solve?</strong></em></p><p>I try to reduce complexity in real-world systems. After all, I think that&#8217;s our main job as software engineers: to turn the complex into the simple. If you think about clean code, it clearly reduces the cognitive load. If you think about binary size, it might reduce operational cost, depending on your situation. It might even lead to more users, though I&#8217;m not sure I should delve into that one. Undefined behavior clearly reduces hidden risk. New standards like C++23 and C++26 reduce boilerplate and enable safe and more readable abstractions.</p><p>I think all of these topics connect. They make large C++ systems more maintainable and more evolvable. And most of my talks start from problems I actually encountered. I try to solve my own problems, but they are not unique. I just try to share what I learn on the go.</p><p><em><strong>Q. From the vantage point of a staff engineer responsible for a large codebase, which two or three features in C++26 do you expect to most change everyday design decisions?</strong></em></p><p>Everyone is talking about contracts and reflection. That&#8217;s going to change everything. I&#8217;m not sure about the time scale though. If you look at C++23 support right now, even that is not complete yet, especially if you look at the differences across compilers. You go on cppreference, check what&#8217;s implemented on which compiler, and we are simply not there yet.</p><p>Given that time scale, I&#8217;m not sure about the answer. But contracts and reflection are the big ones. I don&#8217;t think I&#8217;ll be able to use those in a production environment in the next one or two years. I hope I&#8217;ll be wrong.</p><p><em><strong>Q. If you were reviewing an architectural proposal that leaned heavily on these features, what are the first red flag questions you would ask?</strong></em></p><p>It depends on the environment. If we are in a widely-used production environment and these are very new features, I&#8217;d probably ask if those approaches are actually proven to work, and how maintainable they are. For the time being, we simply lack the experience with these new features. We are still trying to discover how to use them properly.</p><p>Being among the first adopters is sometimes good. Sometimes it&#8217;s better to be in the second line. It really depends on the environment. But I would look for already-proven, maintainable usage. This is pretty much what happens with almost all new major standard versions. There are people coming and saying, can we use modules? And you often end up saying, certainly not yet. There&#8217;s no cross-platform support. I can imagine that will be the case for reflection and contracts in the first few years.</p><p><em><strong>Q. What does a responsible adoption plan look like for a big feature like contracts or reflection?</strong></em></p><p>It really depends on your environment. If you target one platform and you know which compiler you&#8217;re using, and the feature was shipped as ready, then you can go ahead and try. But if you have to support different platforms or compile with different compilers in the same pipeline, you first have to check if all of them are supporting that feature properly.</p><p>In some of my earlier environments, I simply couldn&#8217;t start using even C++20 for a long time because not every feature we needed was supported on all the different compilers. In other teams, we said, okay, we use this compiler, it&#8217;s shipped, let&#8217;s go for it.</p><p>What you have to make sure is that even if for some reason you have to fall back to the previous compiler version, you don&#8217;t have to change your code. It would be quite a pity to move to a new version, start using concepts from C++20, and then in two weeks they say there&#8217;s a problem, we must go back. And then you realize it&#8217;s not just updating the compiler version, you actually have to change the code. So check that you have a safe fallback plan.</p><p><em><strong>Q. Your talk &#8220;Clean Code, Horrible Performance&#8221; is a deliberately provocative title. What is the actual answer?</strong></em></p><p>The title was a question, a provocative one. Someone very active in the community told me I shouldn&#8217;t have said it because it was misleading. Maybe it was. The whole point was to frame it as a question. My answer is no. Clean code does not imply horrible performance.</p><p>We must admit that in some constrained environments and on hot paths, you must optimize for performance and forget about readability. Otherwise your software just won&#8217;t meet its nonfunctional requirements. The most well-known example is probably how the square root function was optimized for Quake III. But in my experience, even in environments with very high throughput, readability and maintainability gained through clean code were always more important than optimized performance. Wherever I&#8217;ve worked, network latency and database read and write times dominated.</p><p>At the same time, I&#8217;ve seen people optimizing for heap allocations, saying we shouldn&#8217;t allocate for a string there, while at the same time they were making network requests in a loop. That just doesn&#8217;t make much sense. Amdahl&#8217;s Law says the overall performance improvement gained by optimizing a single part of a system is limited by the fraction of time that part is actually used. Slowness is also relative. If your code takes a long time to execute due to network latency, then relatively speaking, the heap allocation is not so slow anymore. I&#8217;m not saying you should put everything on the heap. I&#8217;m saying don&#8217;t worry about things that don&#8217;t really matter in your environment.</p><p><em><strong>Q. If you had to write a one-page policy for a large C++ codebase covering the trade-offs between readability, performance, and binary size, what would be on that page?</strong></em></p><p>If it&#8217;s a one-pager and you don&#8217;t know the exact environment or the nonfunctional requirements, it would probably be language-agnostic. The number one point would be: default to readability. You still read code more often than you write it. And not to mention agents, but they also prefer simplicity.</p><p>Second: if you have to optimize, measure first. Don&#8217;t start optimizing for performance before you prove it is actually a problem. You don&#8217;t optimize just because you can. You optimize if you need to. Otherwise, you might just waste time, or worse, you think you&#8217;re optimizing the necessary while you don&#8217;t touch the real problem. Measure first, optimize after.</p><p>Third: optimize only the hot path. You&#8217;ll find that with the measurements. Keep the hot path isolated and well documented. That will help you later.</p><p>And last but definitely not least: make the trade-offs very explicit. In code reviews, but also in the code itself. Leave comments. Because if you sacrifice clarity, document why. Otherwise someone later will come in and think, this doesn&#8217;t make any sense, let me make it cleaner. They are unaware of why certain choices were made. I&#8217;ve been there. I came in thinking something didn&#8217;t make sense, and by the time I realized it slightly changed the binary size in a way that mattered, some pull requests were already merged. Trade-offs there will be. But make them conscious and share the knowledge.</p><p>And this is probably even more important in the new world of agentic coding. Agents will not know the context that teams share with each other. You have to have things written down, and preferably in the codebase, because that&#8217;s what they can read.</p><p><em><strong>Q. What are the silent killers of binary size that creep into C++ systems over months or years?</strong></em></p><p>That&#8217;s a really broad topic. I wrote a series of articles on this and had a workshop at CppCon on the effects of programming styles on binary size. I made it very explicit that those articles and the workshop were not for embedded engineers, because they operate on a different scale and care about different orders of magnitude.</p><p>There are environments where every single byte matters. I never worked in such an environment. But there&#8217;s the other end of the spectrum where you might have to think about, bear with me, hundreds of megabytes. I know many of you might laugh, but I&#8217;m not kidding. When I first heard about binary size as a problem, it was due to a common library that many services shared. We hosted maybe two dozen services on that server. A few changed almost every week, others barely changed in a year. We had to keep ten or twelve different versions of the same library, and that library was over 100 megabytes. Just by making sure all services got the new version every few weeks, and we didn&#8217;t have to store more than two or three versions of that big library, we solved the binary size issue. That was seven or eight years ago.</p><p>In terms of programming patterns, the most overlooked area is unoptimized compiler and linker settings. You might gain the most from there. We tried many different code-level changes and it was satisfying, shaving off a few kilobytes here and there. Then we changed some settings and half a megabyte was gone. That&#8217;s often completely overlooked.</p><p>Template overuse is another one. We had a framework where adding a new object with no logic whatsoever, just the boilerplate, already added around 20 kilobytes because of the heavy templating. We moved away from that. Unnecessary use of std::function can also be problematic. We maintain our own backport of move-only function from C++23 specifically for that reason. And exceptions, while not always a silent killer, can be significant, though there&#8217;s interesting work being done to reduce their footprint considerably.</p><p><em><strong>Q. How do you move code review conversations from taste to shared criteria?</strong></em></p><p>Arguing over taste is never a good investment of time. Not just in engineering. You need to move from taste to agreements. And to do that, you have to communicate, discuss, and eventually decide. And it might not be very popular what I&#8217;m going to say, but your workplace is not a democracy. Certain people have more to say based on their experience and their responsibility.</p><p>But what&#8217;s important is that you introduce some shared decision framework. Track binary size in the CI pipeline so you see the effects at the end of every build. Track performance metrics. Once you have numbers, the conversation is not about taste anymore.</p><p>For the things you cannot quantify, like coding style, coding dojos are genuinely useful. You practice together, explore different approaches without delivery pressure, and over time you move from phrases like &#8220;I like this more&#8221; to &#8220;that&#8217;s actually the style we agreed on.&#8221; Discuss, educate together, share what you learn, and measure what matters to you.</p><p><em><strong>Q. What are the most common mistakes when working with time and clocks in C++?</strong></em></p><p>The most common mistake is choosing the wrong clock. Maybe you don&#8217;t fully understand the different guarantees each clock offers. For example, instead of using steady_clock to measure a short interval for a retry logic, you use system_clock. And then later, due to some bug, you figure out that system_clock is not a monotonic clock. It can jump backwards due to NTP adjustments or manual clock changes.</p><p>Another problem is unsafe conversions and cross-system time. Time is relatively easy when it&#8217;s in one system. But when you have different systems and different platforms, you can end up with clocks using different epochs, different precisions, or different time sources. When you try to compare or convert times from different systems, be very cautious. Test with all the different platforms. Debug and see what&#8217;s going on.</p><p>If you still use C-style APIs, things go wrong easily because they don&#8217;t give you the type safety that chrono durations give you. You might have to use C-style APIs, but try to isolate those parts and do the conversions at the boundaries. Within what&#8217;s within your control, rely on modern C++ time representations. Use chrono wherever you can.</p><p>For APIs specifically: keep your APIs abstract enough so that they are testable. Don&#8217;t rely on the system clock directly. Inject a time provider so you can test different assumptions about your code.</p><p><em><strong>Q. You run a daily C++ quiz and have been blogging for years. What gaps have you noticed consistently, even in experienced C++ developers?</strong></em></p><p>There are two main ones. The first is what I&#8217;d call a depth gap. C++ is a massive language. The standard is about 2,000 pages. Even if you are an expert in one area, it doesn&#8217;t mean you master all the others. You might be a master of template metaprogramming but know nothing about multithreaded programming. Best practices can be quite different across industries and across different kinds of C++ environments.</p><p>We should be humble enough to acknowledge our boundaries and say &#8220;I don&#8217;t know.&#8221; In the beginning of your career, it&#8217;s natural to do that. And with decades of experience, you&#8217;re confident enough to say it again. But in between, it&#8217;s more difficult. The sooner you can make that shift, the better. I once said in an interview that I didn&#8217;t know anything about a particular topic and didn&#8217;t want to guess. They said, well, we don&#8217;t really use that either, let&#8217;s skip it. I got hired at the end.</p><p>The second gap is fundamentals. I&#8217;ve seen many senior-level engineers who are really good at architectural questions, articulate and thoughtful. But they had difficulty writing some very simple algorithms under pressure. Not hard LeetCode problems. Simple ones. I&#8217;m not a fan of LeetCode-style interviews, but you do have to be able to solve problems live with someone watching. That&#8217;s something you won&#8217;t learn on the job. You have to practice on your own.</p><p><em><strong>Q. What has the shift to AI-assisted development changed for senior and staff engineers?</strong></em></p><p>There are two parallel shifts. One is the language itself evolving. With C++23 and C++26 we need a bit less template metaprogramming wizardry now that we have concepts. And safety has become a central topic in a way it wasn&#8217;t before. You can see that in the kinds of proposals the committee is now accepting.</p><p>The other shift is about how we work. As a developer, you&#8217;re expected to be professional in agentic coding. To be an AI-first developer, some would say. It&#8217;s as if you&#8217;ve become a team lead of agents. You keep giving tasks to them, reviewing the code, tuning your instructions.</p><p><em><strong>Q. Viktor Nikolov joined us from the audience to ask a follow-up question on this. He wanted to hear more about the AI shift and what it means for engineers day to day.</strong></em></p><p>I think as a developer in this new world, you have to learn to like your job again. Or still.</p><p>Before, you&#8217;d get your tasks at the beginning of a sprint or a week, and then you&#8217;d go back and start to explore the requirements, explore the code. It took some time. You slowly built up the models in your head and thought about the different kinds of solutions. You might even enter the so-called flow state, which requires focus and a bit longer time. And I think we&#8217;ve kind of lost this over the last few months.</p><p>We became, often, just prompters. Many of us complained even before that we are living in a world of constant context switching. But it just became even worse. Because at the same time, most probably, you will try to prompt different agents with different problems at the same time, and you keep jumping from one window to another. Maybe from one meeting to another, because others are also moving faster. At least they think they move faster.</p><p>Basically, you&#8217;ve lost everything, or almost everything, that you liked about your job. But we have to adapt somehow to this new situation. And mentally, it&#8217;s very difficult.</p><p>I read something very interesting recently on The Pragmatic Engineer, which is a great Substack if you haven&#8217;t come across it. They quoted research saying that in the beginning you ship more code, because it became so much easier. But you don&#8217;t just ship more code. You ship worse code. And that gain in speed is vanishing after a few months because you start accumulating technical debt at the same time. What first seemed faster becomes not faster, but the debt stays.</p><p>I also try different ways of working with agents that keep me happy but also try to speed me up, approaches that don&#8217;t remove what I like in this job but actually help. It&#8217;s difficult. And I&#8217;m happy to continue this conversation with anyone who wants to reach out.</p><p><em><strong>Q. What would you tell engineers starting to build with C++ today, whether in a new codebase or an existing one?</strong></em></p><p>The most common thing I see is defaulting to shared pointer when unique pointer is the right choice. People complain that smart pointers are slow, but they are defaulting to shared pointer instead of unique pointer, which is fast and cheap. Often you don&#8217;t really have to draw a line, you just have to know what to pick and not default to the easy option.</p><p>More broadly: performance is not for the sake of performance. You don&#8217;t write faster code because you can. You write faster code because you need to. If you don&#8217;t need it, default to readability and default to safety. And if you work in an environment where network latency or database latency dominates, you will not care so much about the cost of a heap allocation. Optimize for your actual environment, not your assumptions about it.</p><p>And document why you made the choices you made. Not what the code does, but why it is structured the way it is. That&#8217;s what makes a codebase survivable over time. Especially now that agents are reading it too.</p><p><em><strong>Q. C++ versus Rust. Some engineers in the audience asked about this. Are C++ jobs being taken over by Rust?</strong></em></p><p>I&#8217;m not sure how many jobs are actually being taken over. I had C++ colleagues who fell in love with Rust and moved to other companies just to use it. I don&#8217;t necessarily see that as a huge threat. C++ is not going away anytime soon, simply because it&#8217;s an old and evolving language and we have plenty of systems out there that you just won&#8217;t replace. Even if C++ is not strictly needed for a domain, the cost and risk of replacing it is too high. There will be COBOL jobs for decades for the same reason.</p><p>What I do think is that the overall pie of engineering jobs is growing. Rust is taking a bigger slice, but the pie itself is bigger. And moving between languages is becoming easier because agents can help you understand an unfamiliar codebase quickly. That lowers the switching cost over time.</p><p>C++ is already evolving. We are talking about C++32. The language is not standing still.</p><div><hr></div><p><em><a href="https://fr.linkedin.com/in/sandor-dargo">Sandor Dargo</a> is a senior software engineer at Spotify, the author of a daily C++ quiz and blog at sandordargo.com, and a regular speaker at C++ conferences. This conversation was recorded live on Deep Engineering.</em></p>]]></content:encoded></item><item><title><![CDATA[Knowledge Graphs, GraphRAG, and Real-Time AI in Production with David Knickerbocker]]></title><description><![CDATA[Intentional engineering, living knowledge graphs, and why similarity is not the same as truth]]></description><link>https://deepengineering.net/p/knowledge-graphs-graphrag-and-real</link><guid isPermaLink="false">https://deepengineering.net/p/knowledge-graphs-graphrag-and-real</guid><dc:creator><![CDATA[Saqib Jan]]></dc:creator><pubDate>Wed, 15 Apr 2026 12:30:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/93b09a18-df5c-49af-93ca-6f4d45a26bdc_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This conversation with <a href="https://www.linkedin.com/in/dkjapan">David Knickerbocker</a> keeps returning to a single conviction: the best engineering starts with intentional problem definition, and most AI failures happen when teams rush to use a tool before understanding what they are actually trying to build.</p><p>Knickerbocker has spent his career across cybersecurity, data operations at Intel, McAfee&#8217;s AI research team, and healthcare IT, before founding Bert Intelligence and Grooveseeker. He is the author of <a href="https://www.packtpub.com/en-us/product/network-science-with-python-9781801075213">Network Science with Python</a>, published by Packt, which argued years before GraphRAG became mainstream that graphs and natural language processing belong together as a single discipline. He has been writing code since he was six years old and spent twenty-eight years living in Okinawa, Japan before returning to the United States.</p><p>The conversation covers what it actually takes to build a knowledge graph system with data fresh up to a minute old, why his Verdant Eye system treats knowledge as claims rather than facts, how graph anchoring reduces hallucination space in ways that similarity-based retrieval cannot, and why deliberately forgetting old data is not a failure mode but a design principle. He also walks through his purpose-built testing philosophy, his three production GraphRAG systems, and what working with open source intelligence in adversarial environments teaches you about AI that clean-dataset engineers never have to confront.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-h-tmV8x5Wsc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;h-tmV8x5Wsc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/h-tmV8x5Wsc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><em><strong>1. Most AI systems treat knowledge as a static snapshot. You have built your Verdant Eye system around the idea that knowledge should update continuously. What does it actually take to engineer a knowledge graph that stays fresh, and where does the real difficulty lie?</strong></em></p><p><strong>David Knickerbocker:</strong> For me it is not so much about what breaks. It is about how do I actually do this, and how do I engineer it. Everything in data science and engineering really starts with problem definition. You start with what you are trying to do. If you want to build a world AI and be able to answer questions about things that happened a minute ago, then that is your problem statement. And so then you think about how to get that data into the database so that it is there and it is fresh. But then you also have to get AI to be able to use that data, so there are kind of two sides to this coin.</p><p>It really comes back to intentional engineering. The AI industry feels very shiny and very new, but there is a lot of old school discipline that is still extremely useful to me. I am a very intentional designer, developer, and engineer. You start with the idea, you go through the ideation, from ideation you create your spec, from the spec you do your project management, you assign tasks and do the work. It feels like vanilla old school engineering to me.</p><p>The approaches I use are KISS, keep it simple, and YAGNI, you are not gonna need it. When you are a minimalist engineer and you think in MVPs, you are building the minimally viable product you are aiming for. When you build the minimal thing it is much easier to test and validate that it works than if you throw a whole bunch of spaghetti at the wall and see what happens. Nothing really breaks on my side because I am an old school engineer and I am intentional with everything.</p><div><hr></div><p><em><strong>2. Freshness and accuracy often pull in opposite directions. Something that just arrived may not yet be trustworthy, while something stale may still be reliable. How do you design a system that balances recency and trust, and what signals do you use to make that call?</strong></em></p><p><strong>David Knickerbocker:</strong> In the world of open source intelligence, it has less to do with right and wrong. It has less to do with facts. What I am looking for with open source intelligence is really claims of what is going on in the world. You can have two different groups that are in opposition from each other. One group will say this is the truth, another group will say this is the truth, and they will be in direct conflict with each other. I do not make that decision, and I do not allow my AI to make the decision about what is true or false either. I am more interested in what people are claiming is going on in the world.</p><p>Because if you take what is claimed and you cluster it, you can see that this thing is happening over here and this bad thing has happened over there. I think in terms of ribbons. I come from natural language processing, so I think about clusters not as baskets or clumps of stuff but more like ribbons. You have a whole bunch of information and this top ribbon might be this bad thing happened. The next ribbon might be this event is happening at the library. The next ribbon might be a punk rock show is happening at this nightclub.</p><p>The trueness and the falseness is a much later thing than the awareness of what is being said. That is how I think about it.</p><div><hr></div><p><em><strong>3. What does real awareness mean in practice at the data ingestion layer? And how is your system different from just running an agent with a search tool?</strong></em></p><p><strong>David Knickerbocker:</strong> If I use my GraphRAG and I say what has happened in Portland in the last hour, or the last five minutes, or the last minute, it will be able to answer that question. And if nothing has been reported in the last minute then there is just nothing to report. An empty dataset is better than a hallucination.</p><p>My systems are constantly getting data. When I was building my GraphRAG system, one of the questions I use for calibration is just what is the latest information, because I just want to see that the latest information is coming through. That prompt is very reliable. The answer that comes back is anywhere from a few seconds old to maybe a minute and a half old. The Internet moves at the speed the Internet moves.</p><p>I liken it to the difference between a snapshot and a movie. If you use a tool to do a search and find out something, you are getting a snapshot of time. My systems capture the heartbeat of the Internet themselves and they are always listening. It is much more like a movie compared to a photograph. When you are talking to companies that need urgent information and you can run a query and it comes back thirty seconds old with something that was just seen on the Internet, that looks really different from spinning up agents and using tools to hit a search engine. A search engine will give you a few answers. My systems are always listening and always capturing. I can rewind the Internet itself and play it back forward again.</p><div><hr></div><p><em><strong>4. Where do you see most engineering teams underestimate the cost involved in building graph systems? And what is the failure mode you keep seeing repeated?</strong></em></p><p><strong>David Knickerbocker:</strong> I remember research I did back in 2012 and there was a famous finding that most tech problems are actually people problems. They are not tech problems. That comes down to communication, interpersonal skills, things like that. But getting to the technical side of things, one thing that used to drive me nuts was the rush to use graph databases before they were even understood.</p><p>This bothered me so much in 2020 and 2021 that I actually wrote a book called Network Science with Python. I wrote it because I was annoyed watching teams spend months building graph databases and then not really getting further than populating the graph database. Things are supposed to start when you populate the graph database. That is not the end.</p><p>At that time I was using graphs at Intel for data flow mapping, source code analysis of legacy code, mapping how legacy code would create outputs across thousands of scripts and hundreds of servers. I got well known for this at Intel and McAfee. But I was never invited to the cool kid graph database parties. I was always just doing stuff with graphs and using it to map out data flows and using them to fix production outages. Dead serious stuff. And it was really frustrating watching teams get stuck because the graph skill was not there.</p><p>I think the failure is probably a common one with what is going on today too. There is this rush to use agents before even understanding AI. And if the understanding is not there, then it is just wishful. You are saying please work, please work, please work. And if you do not know how it works, you can mistake whether it ran correctly or just ran. There is a huge difference between it ran and it ran correctly.</p><div><hr></div><p><em><strong>5. You have argued for years that graph and NLP belong together as a single discipline. GraphRAG is now proving that in mainstream AI. What did teams building with NLP alone consistently get wrong that a graph layer would have fixed?</strong></em></p><p><strong>David Knickerbocker:</strong> Language and graphs go together. Similarity in language is not equal to same. I will say that one more time. Similarity is not equal to same. Similar sounding things can be very, very different from each other. A graph kind of anchors things into a piece of context.</p><p>This was really clear to me even when I worked in data operations, because there is a lot of language that goes on in servers. It is not just look at the file, look at the blah blah blah. There is a lot that goes into those log files. If you have a hundred servers then multiple people created the different log files. There is quite a lot of natural language in log files and source code and all kinds of production things. Even working in data operations at Intel, not even as a data scientist, I was seeing language everywhere and already mapping out how production systems were working.</p><p>Graphs show you where things go. But all of the context about what that node even is is often carried by language itself. It was just crystal clear to me a long, long time ago that graphs and language go together. When I was writing this book I even felt afraid that people were going to hate it. You know, it is three years later and it is 4.9 out of five or whatever. But it was so unusual when I was writing it because nobody was really talking about how graphs and language go together the way I was. At the time I was doing a series called a hundred days of NLP, natural language processing. Even back then, using Twitter data, I was realizing that you cannot do natural language processing and leave off graph. It is ridiculous to even do that. If you are working with social media data, you see person A talk to person B about this thing happening. What do you have if you throw away the language? You do not have anything. All you have got is a graph. All of the context is gone. It was crystal clear to me in 2017, and it frustrated me for several years.</p><div><hr></div><p><em><strong>6. Your first NLP and graph experiment was eight years ago. How has entity extraction and relationship linking changed since then, and what has stayed the same?</strong></em></p><p><strong>David Knickerbocker:</strong> The very first one I actually used was the book of Genesis from the Bible. I am not religious, but it is ancient text. It blew my mind that I could pull families out of ancient text and actually map it as a graph. I did this in 2018 and it is still on my GitHub. I can actually go back to my first code and see what I did.</p><p>I am sure it was part of speech tagging because that was before my book and I had no idea what I was doing. I just kind of made it work. Builders build. You just figure out how to do it the first time and then figure out how to do it better after that. There is my small little screen window, just adding color and trying to add size to nodes. Very manual. But then you scroll down to cell 25 and you get to page rank, where I am mapping out who the main entities are. That is where the notebook gets important. Network science is more important to me than visualization, because when you are doing network science you get to do things programmatically. If I want to know whether the punk rock scene in Portland is growing or shrinking, I do not want to visualize that. I want to do that programmatically, turn it into a graph, do time series analysis, and know if the graph is actually increasing or decreasing in density.</p><p>What has changed is really how you create the graph and how you visualize it. Back then it was part of speech tagging with a ton of cleanup. That evolved to using spaCy models. And then LLMs have changed the game because it is painful to download twelve different spaCy models when you can just use an LLM these days. Entity extraction has improved a lot since 2015. I mostly have to throw away less. Less cleaning to do.</p><p>But there is a dangerous side to this. With older NLP, people were critical because there was something messy in there. When you are using LLMs, everything just looks perfect. And that is kind of a dangerous downside. People are a little too trusting of LLMs compared to how they treated older NLP. The cleanliness is real but it creates false confidence.</p><p>What has stayed the same is the network science and the mathematics. Page rank is still very important. Betweenness centrality is still very important. Community detection is still very important. My book is not going to go out of date because of that. The things that change are really how you create the graph and how you visualize it.</p><div><hr></div><p><em><strong>7. GraphRAG is often sold on the promise of reducing hallucinations. What does it actually take to get from fewer hallucinations to genuinely accountable output where you can trace a claim back to a source?</strong></em></p><p><strong>David Knickerbocker:</strong> My system is about claims. The node is attached to the claim that it makes, so there is no hallucination there. The hallucination space is smaller with nodes because you are starting with a node and you are traversing it. You are starting with your anchor space and going from there.</p><p>If you are wondering what jazz events happened in Portland, Oregon, you are connected to the Oregon node, connected to the Portland node, connected to the jazz node. There is very little chance for hallucination. But if you are just using a RAG system, it is just going to look for similarity. And in a GraphRAG system, if there is no match then the output is that there is no match. There is no hallucination opportunity. Whereas with a similarity-based system, there could be similarity even if it is only a single word in a paragraph. That is not a zero type thing. That is a really frustrating thing to me as an NLP person.</p><p>I like to have the discipline of a graph. It is the same discipline I felt from data operations, because you cannot mess up when you work in data operations. When the database is down, you have to fix it. If you come up with some similarity-based bull for your manager, he is going to be mad at you. You fix the problem when the database is down. That discipline of a graph is what I feel GraphRAG gives AI, rooting its answers in physical spaces, and that really reduces the opportunity for hallucination. There is less for it to bulk up around.</p><div><hr></div><p><em><strong>8. Temporal drift is a real problem in knowledge graphs. Facts become outdated, relationships change, and the graph can silently become wrong. How do you detect and handle contradiction and drift at scale without requiring an engineer to review everything?</strong></em></p><p><strong>David Knickerbocker:</strong> My system does not judge, and my system is about awareness. I think about a living system. You are a living system. I am a living system. And you do not remember everything you have ever been told. I cannot remember what I had for breakfast. Our brains are naturally throwing away old information and naturally learning new information, making room for that new information. When I build systems, I like to think about how life does it, and then I try to build that kind of thing into it.</p><p>My system is called the Verdant Eye. Not the Verdant Brain. The Verdant Eye sees, and it does not contain eternal memory, because that is not what an eye does. An eye sees. When the scene changes, the scene changes. What is in front of your eyeballs changes all the time. Your eyes do not need to be recalibrated. The thing has just changed.</p><p>Operationally, if you give a system infinite memory, your database bills are going to skyrocket for the rest of your life. It is never going to be possible. Think about data operations, think about transactional databases. These living systems have been with us for a long time. Anybody who has worked in data operations knows how living systems work because they have worked on living systems, they just do not call them that. In a transactional database you operate off of what you need, and data that is not needed eventually gets archived. In a human body, memories eventually fade away. If I stop thinking about a thing, it will eventually go away.</p><p>When I am building artificial intelligence I am never tempted to build something with infinite memory forever, like the machine from the Hitchhiker&#8217;s Guide to the Galaxy. I do not want to build a super AI. I want to build AI that actually serves us human beings. I want to build AI that does not boil the ocean, that can be bootstrapped by individuals, that does not cost a trillion dollars.</p><div><hr></div><p><em><strong>9. You have built your own testing frameworks for GraphRAG rather than relying on standard benchmarks. What outcomes are you testing for, and how do you know when a system is actually working?</strong></em></p><p><strong>David Knickerbocker:</strong> Everything I do is intentional. There is a really cool intelligence report I read a couple of years ago that said even datasets need to be designed for the use they are going to be used for. Down to the dataset, you should be able to visualize how somebody is actually going to use that data. There is no testing framework anybody else can give me that is going to be fit for purpose for what I am trying to build, because I am not trying to build general intelligence. I am trying to build intelligence that serves a specific purpose.</p><p>There is a scene in Rick and Morty that is one of my favourite scenes. Rick makes a little robot and this robot wakes up and asks what is my purpose. Rick says you pass butter. The robot asks again thirty seconds later. Rick says you pass butter. And the robot says oh my god. But that is the entire purpose of that robot. Its whole purpose in life is to pass butter.</p><p>I have three GraphRAG systems right now and each one is independent. The Verdant Intelligence system is for high level situational awareness, looking down on the world, what is going on in Michigan, what is going on in Oregon, what is going on in California. My second system is called Grooveseeker, and that is street level intelligence. Not what is going on in Oregon but where is the punk rock event happening tonight on what street in Oregon. That graph system has a very different set of rules than the Verdant Intelligence one. My third system has thirty years of artificial intelligence research. When I am building these systems and I want to understand what people did twenty years ago I can just talk to that graph and find out. Each one of these goes through its own testing.</p><p>For the Grooveseeker system, I set up a couple hundred questions and go through multiple rounds of the same question to make sure queries are coming up correct and reliable. If it is not hallucinating, it is doing good. If it is getting me to the right location, it is good. If it is getting me there at the right date and time, it is good. The final test of my world AI was I stopped proving it in articles and just used it to go to a punk rock show. I downloaded my data, asked what is going on in Portland from March 10 to March 13, figured out five events I wanted to go to, narrowed it down to one, bought the ticket online, went to the show, saw all the bands, and hung out with one of them. My AI did not take me to a nonexistent venue. It made a real memory for my family. That is how I know it works.</p><div><hr></div><p><em><strong>10. You are working with open source intelligence, which means dealing with adversarial sources, deception, and deliberately misleading data at scale. What does designing for that environment teach you about AI that engineers working with clean datasets never have to confront?</strong></em></p><p><strong>David Knickerbocker:</strong> I really encourage AI people to learn a little bit about open source intelligence. If you are going to build artificial intelligence to understand the world, the open source intelligence community has been using natural language processing and graphs to understand the world for quite a while. There is a lot to learn from them.</p><p>The real world is a messy space. It is not just that websites can disagree with each other. Websites also have malware. If you point your servers at websites and just download everything on them, you need to be prepared for the consequences of downloading malware. There are all kinds of things when you are dealing with the Internet.</p><p>My systems do not care who is right or wrong. They are observers. My systems will see three sides to the same story. There will be the left side and there will be the right side, and then sometimes there is something really extreme. And it does not mean that any one of them is wrong. I wrote an article about open source intelligence recently and I mentioned that bigger clusters are not more important than smaller clusters. In open source intelligence, everything matters top to bottom. If you are using an agent to do an Internet search you are going to get back what the search engine gave you, maybe ten things. If I use my API and say what happened in Oregon in February 2026, I am going to come back with ten thousand things. My APIs do not return ten. They return full context. That is a difference in completeness. It gets back to the snapshot versus movie idea. I can rewind the Internet itself and play it back forward.</p><p>The judgment part needs to be downstream. My system is a fast layer to AI, and it does not do the judgment thing. But there are certain things that are just still good to be a human being about. If something from an extreme source sounded like something dangerous was heading in the direction of your community, that would be an actionable insight, and you would go to the mayor or the police or someone like that. I am not going to build that kind of automation into the system. There are certain parts of being a human being that I like keeping.</p><div><hr></div><p><em><strong>11. What advice would you give to engineers who are starting to build knowledge graph and GraphRAG systems today? And what should they not do?</strong></em></p><p><strong>David Knickerbocker:</strong> First of all, ask why you are doing anything before you do anything. Do not follow crowds just to follow crowds. You should have a good reason. I do believe that GraphRAG is something you should probably just start with because it is more reliable in my opinion than vanilla RAG. But that is my opinion.</p><p>I am not a crowd follower type. I am a bit of a rebellious type. But I think there is a lot of creativity in being like that. The AI space is a very creative space. If you follow the crowd you are going to do what everybody else is doing. If you sit outside and you look at plants and you think about nature, you can hit insights you will never get from following the crowd. If you are just looking at LinkedIn and seeing what everybody else is doing and reading the same books as everybody else, it is very important to actually be grounded in the world and to think about life itself. If you are going to do anything with intelligence you might as well think about real intelligence. These language models are nothing compared to what is in my backyard. They are not passing tokens. They are not complaining about maxing out their tokens. They are trying to collapse everything to the bare minimum.</p><p>My own philosophy of AI I call absolute zero. Collapse everything to zero. My world AI has zero storage, zero AWS cost, and my AI bills are extremely low, because I just collapse everything down to the bare minimum. And that is also the reason why I have real-time AI, because I was able to collapse everything down to the minimum.</p><p>I encourage people to read the old stuff too. Some of the best insights come from old papers. My graph partner and I were talking about how he got an insight from something thirty years old. A couple of years ago I created something off of Claude Shannon&#8217;s information theory, and it was a different implementation than anything else. You can do these kinds of things if you are an original thinker. If you only follow crowds you are not going to do anything except follow the crowd. If you try to create a product and you are no different from any of your competitors, then what are you doing? I just encourage independence. Get back to the science. AI has to be rooted in science and engineering. When it gets really loud, that is not always the best time to pay attention to the loudness.</p>]]></content:encoded></item><item><title><![CDATA[Small Language Models and the Future of Production AI with Karun Thankachan]]></title><description><![CDATA[When general-purpose LLMs are overkill, small language models trained on specific tasks may be the smarter bet]]></description><link>https://deepengineering.net/p/small-language-models-and-the-future</link><guid isPermaLink="false">https://deepengineering.net/p/small-language-models-and-the-future</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 26 Mar 2026 07:55:56 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/9159d430-667c-43e3-ac30-1a5827aab86a_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This conversation with <a href="https://www.linkedin.com/in/karunt">Karun Thankachan</a> is a practical tour through small language models in production, starting from the limitations of general-purpose LLMs and repeatedly returning to a single constraint. Cost-effective reasoning for specific tasks is a different engineering problem than general-purpose reasoning, and good engineers choose their tools accordingly.</p><p>Thankachan is a Senior Scientist at <a href="https://tech.walmart.com/content/walmart-global-tech/en_us.html">Walmart</a>, where he works on language model systems for retail AI applications. He has a background in machine learning research from Carnegie Mellon University and has spent time at Amazon before his current role, building production AI systems at scale.</p><p>In our conversation, we also talked about ReasonLite, an open-source library that brings chain-of-thought distillation, program-aided reasoning, self-consistency, and trace-budget control under one unified interface, making SLM training feel more like hyperparameter tuning than a collection of disconnected scripts.</p><p>He also covers SLM-Fusion, a multi-model orchestration framework that handles routing, merging, and serving across multiple specialized SLMs, including an OpenAI-compatible FastAPI gateway that abstracts the entire reasoning layer as a microservice. Finally, the conversation turns to where the industry is headed and why RAG and context engineering are winning over fine-tuning right now, and what to watch as diffusion models become more mainstream.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-cICtADWoVP4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;cICtADWoVP4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/cICtADWoVP4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1. Currently, your career spans both academia and industry&#8212;and perhaps things in between&#8212;starting from advanced research, for example, at CMU, to applying AI at Walmart. Can you perhaps share with us how this journey led you to eventually focus on SLMs?</strong></em></p><p><strong>Karun Thankachan:</strong> Yeah. So I started my career as a software development engineer back in India. There, I had the opportunity to work under a director who was starting the data science and machine learning team there.</p><p>I had the opportunity to work on a bit of data analytics, big data science, and eventually machine learning. That sort of sparked my curiosity in machine learning, leading me to do my master&#8217;s in machine learning from Carnegie Mellon.</p><p>And that&#8217;s where I got a bit more interested in the research side of things. I had the opportunity to work under a few professors there. Professor John Stanford, in particular&#8212;I was able to publish a pretty good paper in AAA, and got into the weeds of deep learning. Eventually, that led me to land a role at Amazon and now currently a senior scientist at Walmart.</p><p>It was, however, at Walmart, with the ChatGPT/LLM wave, that I got involved a little bit more in the field of language modeling and NLP.</p><p>Our current director had a vision for agents that could solve specific business problems, and we started trying to develop toward that mission. During that time, I realized that a lot of building agents is a little bit more software engineering than machine learning. It was a lot more about designing evals that could give you feedback on how your LLMs are performing, building guardrails that could make sure that your LLMs are behaving the way that you want them to behave, and optimizing for cost and latency. A lot more engineering focus than, let&#8217;s say, machine learning focus.</p><p>And I missed a little bit of that machine learning flair. And that&#8217;s when I started investigating on my own a little bit more how I could be involved in the machine learning side of things within the AI wave.</p><p>And I stumbled upon small language models&#8212;language models that you could actually fine-tune and optimize to the specific task that you want. It felt a bit more in the domain of machine learning. It felt more like you were understanding how a model was working and helping the model learn patterns in your data, which was sort of what got me interested in the field. That&#8217;s what got me interested in SLMs.</p><p>And it&#8217;s sort of my opinion that right now, we are in a race to see who can define this new customer experience that would be based on LLMs. And that&#8217;s why we are relying a lot more on foundational models, and we&#8217;re hitting them via APIs, plugging and playing them into our experience to figure out what kind of new, reliable experience we can provide to the consumer. And whoever provides that new, better experience to the consumer will take over a huge amount of the market.</p><p>But afterwards, once this new experience is well-defined, then we will go back to that age of cost optimization. And that&#8217;s where SLMs are going to come back into the picture, because they are able to reason more cost-effectively on specific tasks, as opposed to LLMs, which are more general-purpose reasoning. So that&#8217;s sort of why I still keep very invested in this domain of SLMs, and I&#8217;m hoping that converts in the next five or six years. Yeah.</p><p><em><strong>2. Let&#8217;s talk about ReasonLite which was introduced as a way to perhaps tackle the fragmented, unclear evaluation practices and high token costs that hinder reasoning in small models. What gaps did you see in existing SLM distillation toolchains that made you create ReasonLite?</strong></em></p><p><strong>Karun Thankachan:</strong> Sure. So maybe to take a step back and explain what ReasonLite is: LLM models are models that have a tremendous reasoning capacity&#8212;general-purpose reasoning capacity. And since they are models with billions of parameters, they&#8217;re also able to, in very layman&#8217;s terms, remember a lot more details to be able to reason out a solution for a question that you ask.</p><p>For instance, if you ask an LLM how do you bake a cake, an LLM might be able to remember all the steps that are required to bake a cake&#8212;preheat the oven, mix flour and sugar, add X, bake for X amount of minutes. So it&#8217;s able to remember a great amount of detail.</p><p>An SLM, in comparison, doesn&#8217;t have as many parameters, so it won&#8217;t be able to remember as much. It&#8217;ll be able to maybe remember things like flour, eggs, cake.</p><p>And how these language models work&#8212;they&#8217;re essentially what we call autoregressive models. So what it has generated thus far will influence what it will generate next. So if you can&#8217;t remember a lot of the prior steps to baking a cake, like preheat the oven, flour, egg, cake mix, stuff like that, you might not continue generating the correct answer. An SLM might say flour, eggs, brownie instead of flour, eggs, cake, because it didn&#8217;t have the correct prior before it.</p><p>But even though LLMs have billions of parameters and have a lot of memory, it may be overkill when you want reasoning for a specific task that you have in mind. It&#8217;s really great for general-purpose reasoning. But for specific reasoning for a particular task that you have in mind, an SLM might be able to get you there. And the only thing that you have to do is update its parameters, which are currently now for subpar general-purpose reasoning. Update it to become good for specific-task reasoning.</p><p>So how do you teach these SLMs how to build out that reasoning? There are a lot of techniques in the market, like CoT distillation. To provide an example, here you ask the LLM, &#8220;Hey, I&#8217;m going to ask you a question. Let&#8217;s say a person went to a store where they&#8217;re selling apples for $3. He bought four apples. He gave the shopkeeper $20. How much does he get back in return? Tell me the answer, but show me the steps also.&#8221;</p><p>So the LLM will write out: the cost for an apple is $3, four into $3 is $12, he gave 20, so 20 minus 12, eight back. Your answer is 8. It will show you each of the steps and then give you the answer, the 8. So like we mentioned earlier, these are autoregressive models. So what it generated earlier will help it generate more accurate answers in the future. So if I&#8217;m able to get the SLM to output responses in this similar manner, or at least think in this similar manner, then it&#8217;s more likely to get at the final answer.</p><p>So what we do is we ask the LLM to generate its entire chain of thought, and we feed this chain of thought into SLMs and ask it to generate a similar chain of thought. We don&#8217;t do this for general purpose. Rather, we do it for our specific tasks. If you try to do it for general purpose, again, the parameters will get updated in every which way, and it won&#8217;t be able to generate good answers again. But if you do it for a specific task, the parameters will get updated to reflect that specific task. It will start to be able to solve that specific task.</p><p>Similar to CoT distillation, there are other techniques, like contrastive rational training, which is essentially you tell it, &#8220;This is the answer that I want, like four into three is equal to $12. That&#8217;s what I want. Four into three is equal to $11. That&#8217;s not what I want.&#8221; So you push it toward what you want and push it away from what you don&#8217;t want. So there are a lot of these techniques out there for helping train SLMs to perform or provide reasoning on a specific task.</p><p>But when I was building out SLMs to reason on specific tasks, I realized a lot of these techniques were written in notebooks. They were written in scripts. And what I wanted was something similar to what ML practitioners are familiar with today, which is a kind of hyperparameter tuning, where you have all these knobs and you can turn them on and off. You can adjust the parameters, and you can figure out what set or what combination of techniques helps the model learn the pattern the best so that it can generalize in the future.</p><p>So I wanted all these techniques under one roof, like CoT distillation, self-consistency, program-aided distillation, contrastive rational training, curriculum scheduling. I wanted all these techniques in one package, which I could control similarly to hyperparameter tuning. And that&#8217;s why I developed ReasonLite. Everything was split out in files, and I wanted to bring it into one package. And with this now, hopefully, practitioners can call the package, tune it just like they would with HP tuning, and that sort of, I feel, solves a pain point in current SLM training.</p><p><em><strong>3. ReasonLite integrates program-aided distillation, using external symbolic tools or code to verify intermediate reasoning steps. How does this approach work in a real-world training pipeline? Can you give an example of using a tool, say a calculator or knowledge base, during distillation, and how it improves a student model&#8217;s reasoning accuracy without overly complicating the production workflow?</strong></em></p><p><strong>Karun Thankachan:</strong> o maybe to explain what program-aided distillation is, maybe taking our previous example of a person going to a store, buying four apples, which are worth $3 each. They pay the shopkeeper $20. How much do they get back in change?</p><p>If you give that question to an LLM and ask it to give you an answer with a sort of chain of thought, then what happens is it does the calculation. It says that, hey, four into three is $12, and 20 minus 12 is $8.</p><p>So here, the LLM doesn&#8217;t actually have a calculator doing the calculation. What it&#8217;s doing is it&#8217;s looking at four into three, and it&#8217;s saying that 12 is probably the most likely answer. But at times, an LLM could generate a chain of thought that says that four into three is $11, just because it&#8217;s not actually doing the calculation. It&#8217;s just predicting what&#8217;s the most probable answer.</p><p>Same thing with 20 minus eight. It might not give you $12. It might give you 20 minus eight as $9, because it&#8217;s just predicting what&#8217;s the most likely number. It&#8217;s not doing the actual calculation.</p><p>So this is a little bit harmful when you are trying to do chain-of-thought distillation at scale. Just to refresh everyone&#8217;s memory, what is chain-of-thought distillation? You ask the LLM to show the steps that got it to the final answer, like four into three is 12, 20 minus 12 is 8. Those steps&#8212;show it. So that&#8217;s the chain of thought.</p><p>Along with the answer, that&#8217;s the entire chain of thought. That chain of thought, you feed it to the SLM. And then the SLM tries to generate that same chain of thought using its much smaller parameters. But it&#8217;s only being trained on chain of thought for a specific task, so it will be able to capture that limited amount of chain of thought.</p><p>Now the problem is, if these chains of thought that we are generating from the LLM itself are incorrect&#8212;like four into three is 11, or 20 minus eight is 9&#8212;if it&#8217;s from the LLM itself and it&#8217;s incorrect, then the SLM obviously can&#8217;t be expected to learn the correct answer. And you can&#8217;t sit and verify all your chains of thought, especially when you&#8217;re training at scale.</p><p>So how do you make sure that the intermediate, especially math-oriented, steps are correct? You ask the LLM to generate it in a way that is like Python language. The code would be related as four into three, and c is equal to four into three. Answer is equal to 20 minus c. So your answer is 20 minus four into three. It&#8217;s eight.</p><p>So instead of the LLM actually doing the calculation, it just writes the code with the inputs that you provided. The code is taken and run in something like Python or a calculator. The answers are then attached back to the chain of thought, and then you feed it into the SLM.</p><p>So this way, with this kind of external program that&#8217;s embedded into your distillation&#8212;i.e., program-aided distillation&#8212;you can make your chain of thought a little bit more accurate, and you can get your SLM to learn only on the correct answers instead of any incorrect answers.</p><p><em><strong>4. One feature of ReasonLite is a trace-budget controller to constrain the token usage of chain-of-thought traces during training and inference. In a production deployment, why is controlling the length of reasoning traces important for cost and latency? </strong></em></p><p><strong>Karun Thankachan:</strong> When you&#8217;re actually serving answers to users, you run into actual engineering concerns. One is obviously latency. When a user types in a question, you want to give them an answer in a fairly short amount of time so that the user doesn&#8217;t drop off on the site. And you maybe don&#8217;t want to provide a very verbose answer unless the user is explicitly asking for it. If they&#8217;re asking for something simple, you want to give them an accurate answer, a comprehensive answer, but it doesn&#8217;t need to necessarily be verbose.</p><p>So during that time, if your model is trained to think in this chain-of-thought manner, where it&#8217;s trying to explain breakdown steps and then get the answer&#8212;which is generally good practice&#8212;but if it&#8217;s trained on fairly long chains of thought, that might kick up your latency and increase your cost, because each token costs a little something to produce, even from the SLM.</p><p>So you might want to have some kind of guardrails around it, so that the latency doesn&#8217;t increase to an unbearable amount, and the cost doesn&#8217;t become very cumbersome or essentially very expensive. The compute doesn&#8217;t run up, essentially.</p><p>So it&#8217;s to prevent that that you have these trace budget controllers. And how it sort of works is, you can enforce it in different manners. You can enforce it by saying that, hey, for any particular inference call, you shouldn&#8217;t take more than X amount of tokens. If you&#8217;re starting to hit your X amount of tokens, cut your chain of thought short with whatever you have and provide an answer. It might not be an accurate answer, but it helps you make sure that your token cost won&#8217;t grow beyond a particular point, and your latency also won&#8217;t exceed a particular value.</p><p>Now, an obvious question that people might have is, hey, if I limit my token usage and if my chain of thought isn&#8217;t allowed to grow, won&#8217;t I get bad answers? Which is a very reasonable question. So typically, yes. If your model is trained to produce very verbose chains of thought, you will run into that token-limit issue again and again with the token budget controller. You&#8217;ll run into the issue again and again, and your model won&#8217;t be allowed to express all its thoughts and therefore give a good answer.</p><p>So typically what we do is we have eval metrics that track things like whether the model is being useful to the user or not, like a thumbs-up, thumbs-down feature in ChatGPT. So if you get a lot of thumbs-down features, and if you&#8217;re seeing that for all those requests your token budget controller was cutting off your chain of thought, then you can understand from your evals and from your logs that your model is actually being too verbose.</p><p>We need to retrain the model so that the chain of thought is shorter, it&#8217;s less verbose, and it&#8217;s able to get to the answer quicker. So that&#8217;s why this kind of trace-budget controlling is important in production settings, and how it helps you limit token usage.</p><p><em><strong>5. Techniques like chain-of-thought prompting and self-consistent decoding&#8212;generating multiple reasoning paths and aggregating answers&#8212;can significantly improve reasoning accuracy. However, they also increase compute cost and latency by running the model multiple times. How do you balance these trade-offs for production systems?</strong></em></p><p><strong>Karun Thankachan:</strong> So, maybe I can take a step back. What do we mean by self-consistency? Essentially, like we mentioned, LLMs are not actually doing the specific calculations or specific reasoning. They&#8217;re not actually understanding, or they&#8217;re not able to derive meaning. They are autoregressive models. So based on whatever they&#8217;ve seen, whatever they&#8217;re generating right now, they&#8217;re going to generate the most likely answer next. So sometimes it may be generating incorrect things.</p><p>But if you ask the model to generate an answer to a specific question 10 times, then the majority of the time, it might actually give you the right answer. So what you can do, or what is a decent practice, is: with your LLM, you generate maybe not just one chain of thought. You ask it to generate 20 chains of thought. Then you check the final answer for these chains of thought, and if the final answer across those chains of thought is similar in a majority of cases, that is your final answer.</p><p>For instance, let&#8217;s go back to our apples example. Four into three, 12. Twenty minus 12, eight. Let&#8217;s say 12 chains of thought generate eight, the other five generate nine, and the remaining three generate something like 10. So the majority vote is eight. Now you know that this is probably the right answer. Let&#8217;s pick up all these chains of thought and use that to train our SLM.</p><p>So self-consistency is a fancy way of saying majority voting. You&#8217;re essentially asking the same model to generate the response to a question again and again and again until, in the majority of cases, it starts giving you a specific answer instead of different answers all the time.</p><p>Now, if you try to use self-consistency during inference, you&#8217;re essentially asking the SLM to answer the same question multiple times&#8212;let&#8217;s say 10 times. You&#8217;re picking the majority answer, and then you&#8217;re giving that majority answer to the user. The problem is, if you do it at inference time, instead of answering one question once, you&#8217;re answering it 10 times. So the cost becomes 10x. The latency becomes 10x as well during inference.</p><p>So typically, you don&#8217;t want to use self-consistency at inference time, so that you can control the latency. You only typically use self-consistency during your training loops, so that you can figure out what chains of thought to actually feed into your SLM. So the simple rule of thumb is: self-consistency is better for training time, where latency isn&#8217;t the concern&#8212;and cost also, to a certain extent, because you&#8217;re doing a lot of these things in batch, and you can append other techniques on top. You can actually manage the cost and manage latency. So use it at training time. It&#8217;s not something you need to use at inference time.</p><p><em><strong>6. In ReasonLite, you emphasize not just final answers but also intermediate reasoning quality&#8212;providing targeted reasoning probes and symbolic verifiers to assess a model&#8217;s thought process. In a practical setting, how do you evaluate whether a distilled small model is truly reasoning well versus just guessing the right answer?</strong></em></p><p><strong>Karun Thankachan:</strong> Got it. So essentially, when you are trying to train an SLM, and like we discussed thus far, what it generates is based on what it has learned so far&#8212;what it is currently generating in its chains of thought, or in its thinking, essentially. So when you are evaluating an SLM, it might not be enough to evaluate it on whether it&#8217;s generating the final answer correctly or not.</p><p>Again, going back to our apples example: four into three, 12; 20 minus 12, eight. Eight is the final answer. Does that mean you evaluate it only on eight? You could. But let&#8217;s say it did four into three, 11; 20 minus 11, eight. It still got to the final answer, but it&#8217;s because it&#8217;s not really doing the correct things. It&#8217;s just, again, making guesses about what&#8217;s the most probable answer. And it somehow stumbled on the correct answer.</p><p>So your model might actually deviate from the behavior that you desire, but your answer is still correct. We don&#8217;t want those kinds of things to spread into production. So we want to not just evaluate the final answer. We want to also make sure that we evaluate the steps in between.</p><p>So how do we actually do that? We can check what we call stepwise behavior. There are a few things that you can inject into the model&#8212;symbolic verifiers or reasoning probes. These are, I think, two things that we have implemented in ReasonLite.</p><p>So maybe to give an example of how these things function: a reasoning probe is trying to figure out if the SLM is able to do a specific substep well. For instance, we have mathematical reasoning probes that help you test if your SLM is learning math very well. Take 17 plus 8 is equal to 25. When you do that addition, there is this behavior called carry behavior, where seven plus eight is 15, so you need to put the five and carry over the one. Then one plus one is two&#8212;25. That&#8217;s how you do the math. So this carry behavior is something you want to see specifically if your SLM is learning. Within ReasonLite, there are functions that help you test specifically for carry behavior within your SLM. So that&#8217;s a way of evaluating if your SLM is behaving well on substeps.</p><p>Similarly, step verifiers are another way of evaluating if your SLM is behaving the way you want it to. For instance, the apples example again: four into three is equal to 12. That is a substep. You want to verify if that substep is accurate or not. So you take the output of the substep. The step verifier takes whatever it is doing, runs that code, generates an actual answer, and matches it up. So it&#8217;s able to see, at the substep level, if it&#8217;s giving you an answer correctly.</p><p>So these kinds of reasoning probes and step verifiers are things that you can maybe add on to the final-answer evaluation, and they&#8217;ll give you a little bit more information about how your model is actually behaving.</p><p><em><strong>7. Let&#8217;s shift into talking about SLM-Fusion and multi-model orchestration. Modern AI deployments often involve multiple models, from small domain-specific SLMs to large general LLMs. But traditional serving frameworks usually assume a single static model, which leads to inefficiencies under dynamic workloads. SLM-Fusion, from what I understand, is an open-source library proposed to bridge this very gap by unifying model merging, routing, and serving in one system. Can you explain the impetus behind SLM-Fusion and also talk to us about how it works in a real scenario?</strong></em></p><p><strong>Karun Thankachan:</strong> Got it. So SLM-Fusion&#8212;just to give a little bit of context&#8212;this paper was written somewhat a while ago, before multi-agents became a little bit more popular and that kind of architecture became a little bit more popular. I would say maybe it&#8217;s a little bit outdated at this point.</p><p>But SLM-Fusion, the idea essentially is: typically, with LLMs, with their general-purpose reasoning capability, you can, with enough RAG and context engineering, get them to answer questions within multiple domains, even with a single LLM, as long as you have your RAG engine built well. You have a retrieval layer that gets you specific context related to this new domain, and you do context engineering well enough that only the relevant info stays inside the context of the LLM.</p><p>But if you&#8217;re working with SLMs, since you are fine-tuning your SLMs on a specific task, that fine-tuned SLM is only able to reason very well for that specific task at hand. You can&#8217;t just use one SLM and be hopeful that it will be able to pick up or be able to reason in a new domain as well, because you are training it&#8212;its parameters are limited. You&#8217;re only able to reason for one specific task.</p><p>So in this case, what you typically do is train multiple SLMs, and then you figure out a way, based on the questions that are coming in, how to route the question to the appropriate SLM. So that&#8217;s essentially the idea behind SLM-Fusion. The key thing is: how do you route it to the correct SLM? How do you evaluate when the routing was inappropriate? And how do you not base the routing on just hard-coded rules, but learn the routing from user behaviors?</p><p>Sort of like: hey, it routed to SLM A, the user didn&#8217;t particularly like that response, but based on whatever rules we have, that was the SLM to route it to. So now, how do you reconcile the fact that that was the SLM to route it to versus the user behavior? Was it one question and then a follow-up question that switched it to another domain?</p><p>So those kinds of things&#8212;how do you actually evaluate that, how do you learn it from telemetry, and try and update this routing over time&#8212;that was the core of SLM-Fusion. Now with multi-agent architectures, it&#8217;s becoming a little bit easier, but if you&#8217;re working in SLMs, some kind of routing is good. There have been multiple papers, both within ICLR, ICML, and AAAI, that have come around this routing concept as well. But it&#8217;s a lot more updated at this point.</p><p><em><strong>8. In production, when would we prefer merging models over just choosing one? Can you perhaps discuss an example use case where merging two specialized models could yield better results than using a single model alone?</strong></em></p><p><strong>Karun Thankachan:</strong> Sure. So again, it comes to how different the reasoning is that these two different SLM models would have to learn.</p><p>For instance, let&#8217;s say within a retail scenario, you have a reasoning model that&#8212;let&#8217;s say it&#8217;s an anomaly detection model&#8212;that sort of needs to decide, looking at sales of an item, why the sales dropped anomalously. So if sales of an item drop anomalously, there could be multiple reasons that drove it. So if I were building an SLM model, if I saw sales dropping, then the next thing I would have the SLM model do is be able to generate multiple hypotheses and then figure out what is the appropriate one to chase down and try to answer.</p><p>Within that same context, if, let&#8217;s say, I wanted to fix the anomaly, I would want an SLM model where I would give it some context on, &#8220;I want you to go and hit this system, change the value to something like this, and hit the system, change the value to something like this.&#8221; Here, the SLM model doesn&#8217;t need to have that kind of broad thinking in terms of hypothesis generation. It needs a little bit more specific tool understanding, a little bit more integrated with API calls.</p><p>So the reasoning between both these models would be very different. One would be a bit more broad&#8212;generate hypotheses, figure out which one is the right one, and then tackle it, I mean run those hypotheses, figure out what is an appropriate answer. And this one is a little bit more tool-oriented, a bit more in-depth, a bit more specific. It can&#8217;t afford inaccuracies because it&#8217;s interacting with the tool.</p><p>So the reasoning would be very different. In these cases, rather than trying to build one SLM that could maybe do both, it might be a better idea to separate it out, and it might be a good idea to bring these two into a routed format where you generate the hypothesis, you tell the user that, &#8220;Hey, I evaluated X hypotheses. These two seem to be the most likely root reasons.&#8221; Then the user sort of tries to understand, okay, maybe I also think that, okay, out of all the ones I&#8217;ve evaluated, this is probably the reason. Let&#8217;s try and fix this. Let&#8217;s adjust these metrics or adjust these settings here, and then you route it to the second SLM.</p><p>And that SLM sort of makes all the necessary tool calls, all the necessary adjustments, and it has more in-depth, specific reasoning built into it. So that might be a good scenario for routing.</p><p><em><strong>9. One of the core features of SLM-Fusion is an adaptive routing layer that can be rule-based, learned, domain-specific, or cost-aware in deciding which model or ensemble handles a request. How do these routing policies work under the hood? For instance, what would a cost-aware router consider&#8212;latency SLA, API throughput costs, query complexity, etc.?</strong></em></p><p><strong>Karun Thankachan:</strong> Sure thing. So within the router, we have a few ways you can decide what SLM to route it to. The simplest way, and the easiest way to get started, is just rule-based routing. You see certain domain keywords, and you can route it to a specific domain SLM. The slightly more advanced manner is getting an embedding out of the user query and figuring out which sort of base embedding it matches the most. So each SLM would have a domain-encapsulated embedding associated with it. So it&#8217;s everything related to that domain in an embedding. So if the user query matches this domain-specific embedding, route it to this SLM.</p><p>Now, the advantage with this sort of embedding-based matching is that, if the user asks a specific question that is maybe multi-domain, and you routed it to the wrong SLM, or it might be the case that you need to split that question&#8212;route the first portion of the question to this SLM, get a response, route the second portion of the question to the next SLM, get a response.</p><p>So instead of this embedding being static, what SLM-Fusion does is provide you the opportunity to adjust those embeddings based on how well you have done on the questions users have asked in the past. So using your logs, you can pull in your logs. The ones that you didn&#8217;t do well on, those ones you can narrow down on. You can figure out how to update your embeddings for those specific ones that you didn&#8217;t do well on.</p><p>And for a particular question, if you feel like it&#8217;s a multi-domain question, within the router itself you have a tinier SLM that can split multi-domain questions into separate questions. So with these sorts of knobs, you are not just hard-coding how to route it, but you are able to learn over time how the routing should evolve. And you are also able to address multiple-domain questions by using the routing module to split them into different questions and orchestrate it in a manner where you can still use your SLMs, and you don&#8217;t need to try to condense everything or try to get SLMs to interact across domains. So that would be the core way to use this sort of routing more.</p><p><em><strong>10. Let&#8217;s talk a little bit about telemetry-driven feedback loops. What signals, according to you, are most valuable for such a loop in a production setting? And how do you feed this feedback back into the system?</strong></em></p><p><strong>Karun Thankachan:</strong> Got it. So it really depends, but the most critical one, I would say, is how the user is responding to the queries. So just like ChatGPT&#8217;s thumbs-up, thumbs-down&#8212;some kind of user satisfaction score. That would be the best way to assess any sort of generative system, because the responses being generated are evaluated by the user. And if it&#8217;s not a helpful one, there&#8217;s no point in any of these generative systems.</p><p>So being able to track user satisfaction scores and attach them to your logs&#8212;your chain of thought, your final answer, your user satisfaction scores&#8212;that sort of logging system is what we call telemetry. And once your logs are all stored and generated, being able to search through your logs and figure out which ones you didn&#8217;t do well on, and having enough logging to figure out which SLM it routed to, why it routed to that SLM, why it tried to split the question into separate portions&#8212;having all of those logs in one place is what is going to help you build that feedback loop and improve your routing over time.</p><p>Apart from user satisfaction, you could also use things like token usage. Is the compute cost actually building up? Is maybe a question that was designed for one domain being unnecessarily split into multiple questions and maybe just sent to the same SLM again and again? I&#8217;ve seen that happen also. So checking if your token cost for any of the responses you are giving is spiking. Similarly, if your latency is spiking.</p><p>So these three, I think, would be the top metrics to attach to your telemetry, or have tracked along with your telemetry, with timestamps and request IDs, so that you can map it properly. And then you can improve your routing layer over time.</p><p><em><strong>11. Thank you for that. So now, quantization is a common way to reduce inference cost, but mixing models of different precision&#8212;or even merging quantized weights&#8212;can be tricky. So what did you build in SLM-Fusion, again, to use it as a case study, to handle quantized models effectively?</strong></em></p><p><strong>Karun Thankachan:</strong> Got it. So, I guess, just to explain why quantization is tricky: quantization is nothing but using different integer formats. So with quantization, what you&#8217;re doing is you can represent things as 32 bytes, 16 bytes, 8 bytes, or 4 bytes. The lower you go, the smaller your models become, the faster the multiplications become, and therefore the faster your SLMs become. So you can make your models smaller and faster the more quantized they become. But again, as you make them more quantized, you lose a little bit of information, so they won&#8217;t be as accurate.</p><p>So how it helps you use different SLMs that might be in different quantization modes&#8212;it gets a little bit tricky here&#8212;but we have these things called tensors. When these calculations are taking place, we do them in these large-scale 3D matrices called tensors. And how the calculation within an SLM works is, you sort of align these tensors, or align these channels, pad them as necessary to get them to the same quantized integer formats, and then sort of carry forward the calculation.</p><p>So, a little bit more on the math side, but the key thing is aligning the tensors so that you&#8217;re not assuming that all the models are at the same level of quantization. You try to identify whatever quantization it is at, then sort of work through packages that we already have. It&#8217;s not something new that SLM-Fusion is providing, but most of the popular deep learning packages already provide this. But aligning per tensor, per channel, so that the calculations actually flow through.</p><p>And in terms of, apart from just the quantization, building adapters into your models is another way to perhaps mitigate this. Adapters are still, I would say, a little bit unproven in terms of the value they add for the number of parameters they introduce. But in some very few scenarios, where the domains are similar enough, but you need a slight change in parameters so that it adapts&#8212;not to a completely new domain, but maybe a complementary domain&#8212;in those cases, I think adapters work. But for quantized models, if they&#8217;re in different quantized states, having adapters can help you maybe bridge that gap as well.</p><p>So, a little bit on the math and technical side: alignment of tensors. A little bit less mathy, but more on the modular side: adapters to help bridge the gap. So those are the two things that I think SLM-Fusion had that help you work with different quantized SLMs.</p><p><em><strong>12. All right. Now, SLM-Fusion also introduced a FastAPI-based Fusion Gateway that is even OpenAI-compatible for inference requests. So how do you see a system like this being deployed in a production microservice architecture? Could it sit alongside existing serving frameworks, perhaps?</strong></em></p><p><strong>Karun Thankachan:</strong> Yep. Yeah, definitely. So the FastAPI backend is essentially there to support that same thing. The idea being that, within microservice architectures&#8212;again, maybe taking a step back&#8212;the core idea is that anything that has to do with one specific function is split out, modularized, and kept separate. So your reasoning engine, if it is like this multi-SLM model, you can keep it separate from everything else. You can update it as required without impacting any of the other microservices in that environment.</p><p>And with the FastAPI backend, the key idea is that you can hit it just like you would any other kind of service that you can abstract away. So what we typically call, I guess, reasoning as a service&#8212;RaaS, if you want to call it a new domain. So whenever you need a little bit of, &#8220;Hey, I think I need a little bit of human reasoning at this particular stage to make a decision on what to do next,&#8221; then just hit the API endpoint like you would in any kind of microservice architecture.</p><p>It abstracts away all the reasoning. It will do the routing within, it will pick the SLM, it&#8217;ll generate an answer, and it will send you back a specific API that follows the contract. And that API isn&#8217;t just something that&#8217;s generated by the SLM&#8212;it&#8217;s filled in so that the contract is always maintained between whatever service is calling the reasoning-as-a-service microservice.</p><p>So yeah, that way, you can just abstract the whole thing away, and you can put it in any kind of production environment, with the typical guardrails that you have&#8212;like trace-budget controllers, latency holders, and everything. It will actually stick to the SLAs that you typically expect in a multiple-microservice architecture system.</p><p><em><strong>13.  Now, quantization is a common way to reduce inference cost, but mixing models of different precision&#8212;or even merging quantized weights&#8212;can be tricky. So what did you build in SLM-Fusion, again, to use it as a case study, to handle quantized models effectively?</strong></em></p><p><strong>Karun Thankachan:</strong> Got it. So, I guess, just to explain why quantization is tricky: quantization is nothing but using different integer formats. So with quantization, what you&#8217;re doing is you can represent things as 32 bytes, 16 bytes, 8 bytes, or 4 bytes. The lower you go, the smaller your models become, the faster the multiplications become, and therefore the faster your SLMs become. So you can make your models smaller and faster the more quantized they become. But again, as you make them more quantized, you lose a little bit of information, so they won&#8217;t be as accurate.</p><p>So how it helps you use different SLMs that might be in different quantization modes&#8212;it gets a little bit tricky here&#8212;but we have these things called tensors. When these calculations are taking place, we do them in these large-scale 3D matrices called tensors. And how the calculation within an SLM works is, you sort of align these tensors, or align these channels, pad them as necessary to get them to the same quantized integer formats, and then sort of carry forward the calculation.</p><p>So, a little bit more on the math side, but the key thing is aligning the tensors so that you&#8217;re not assuming that all the models are at the same level of quantization. You try to identify whatever quantization it is at, then sort of work through packages that we already have. It&#8217;s not something new that SLM-Fusion is providing, but most of the popular deep learning packages already provide this. But aligning per tensor, per channel, so that the calculations actually flow through.</p><p>And in terms of, apart from just the quantization, building adapters into your models is another way to perhaps mitigate this. Adapters are still, I would say, a little bit unproven in terms of the value they add for the number of parameters they introduce. But in some very few scenarios, where the domains are similar enough, but you need a slight change in parameters so that it adapts&#8212;not to a completely new domain, but maybe a complementary domain&#8212;in those cases, I think adapters work. But for quantized models, if they&#8217;re in different quantized states, having adapters can help you maybe bridge that gap as well.</p><p>So, a little bit on the math and technical side: alignment of tensors. A little bit less mathy, but more on the modular side: adapters to help bridge the gap. So those are the two things that I think SLM-Fusion had that help you work with different quantized SLMs.</p><p><strong>14. SLM-Fusion also introduced a FastAPI-based Fusion Gateway that is even OpenAI-compatible for inference requests. So how do you see a system like this being deployed in a production microservice architecture? Could it sit alongside existing serving frameworks, perhaps?</strong></p><p><strong>Karun Thankachan:</strong> Yep. Yeah, definitely. So the FastAPI backend is essentially there to support that same thing. The idea being that, within microservice architectures&#8212;again, maybe taking a step back&#8212;the core idea is that anything that has to do with one specific function is split out, modularized, and kept separate. So your reasoning engine, if it is like this multi-SLM model, you can keep it separate from everything else. You can update it as required without impacting any of the other microservices in that environment.</p><p>And with the FastAPI backend, the key idea is that you can hit it just like you would any other kind of service that you can abstract away. So what we typically call, I guess, reasoning as a service&#8212;RaaS, if you want to call it a new domain. So whenever you need a little bit of, &#8220;Hey, I think I need a little bit of human reasoning at this particular stage to make a decision on what to do next,&#8221; then just hit the API endpoint like you would in any kind of microservice architecture.</p><p>It abstracts away all the reasoning. It will do the routing within, it will pick the SLM, it&#8217;ll generate an answer, and it will send you back a specific API that follows the contract. And that API isn&#8217;t just something that&#8217;s generated by the SLM&#8212;it&#8217;s filled in so that the contract is always maintained between whatever service is calling the reasoning-as-a-service microservice.</p><p>So yeah, that way, you can just abstract the whole thing away, and you can put it in any kind of production environment, with the typical guardrails that you have&#8212;like trace-budget controllers, latency holders, and everything. It will actually stick to the SLAs that you typically expect in a multiple-microservice architecture system.</p><p><em><strong>15. Finally, Karun: any emerging trends, perhaps in governance or tool integration, that you believe will significantly impact how we deploy language models in production?</strong></em></p><p><strong>Karun Thankachan:</strong> I think, right now, diffusion models are becoming a little bit more commonplace, and that might be a trend worth checking out. Apart from that, I guess the main thing to focus on is that, within LLMs, maybe six months ago, there was a split between: is investing in parameter-efficient fine-tuning&#8212;LoRA, QLoRA&#8212;along with alignment techniques like DPO and PPO, a good investment of time versus just focusing on RAG and prompt engineering? It looks like the industry is shifting a lot toward RAG and context engineering. One, because maybe it&#8217;s cheaper. And for the other things, you need specific hardware, and you need to hire people who know how to do it. But it also seems like you can actually get fairly accurate answers and fairly good reasoning from your LLM models if you actually set up a good RAG pipeline, and if you bolster it with good retrieval&#8212;a way to improve or rerank the retrieved documents and again select the best ones on top of it. So don&#8217;t just have a simple RAG pipeline. Fit a model on top, maybe improve the accuracy of your retrieved documents with the reranking model, and also focus a lot more on context engineering. So don&#8217;t bloat your context with a lot of information. Look into context compression. Look into eliminating things from your context if they are irrelevant. Just having irrelevant things increases hallucinations. So a lot of investment in good engineering, I would say, combined with good retrieval, seems to be giving a lot more accurate answers, a lot less hallucination, and a lot better reasoning as well. So that seems to be where the industry is focused right now. It would be interesting to see if it switches back to fine-tuning, or if it switches back depending on how this diffusion trend plays out and how the cost-versus-LLM trend plays out. I think those are some trends to keep an eye on to see where we need to switch next.</p>]]></content:encoded></item><item><title><![CDATA[Trade-offs in Modern System Design: A Conversation with Archit Agarwal]]></title><description><![CDATA[A pragmatic guide to architecture choices, cost discipline, resilience, and interview-ready thinking.]]></description><link>https://deepengineering.net/p/trade-offs-in-modern-system-design</link><guid isPermaLink="false">https://deepengineering.net/p/trade-offs-in-modern-system-design</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 26 Feb 2026 05:13:34 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/e4a14374-2767-4450-9c5b-ca04d9e4a48a_1536x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This conversation with <strong><a href="https://www.linkedin.com/in/architagarwal984/">Archit Agarwal</a></strong> is a practical tour through modern system design&#8212;starting from first principles and repeatedly returning to a single constraint: real systems live under trade-offs, and good engineers choose those trade-offs deliberately. Agarwal is a <strong>Principal Member of Technical Staff </strong>at <strong>Oracle</strong>, where he works on <strong>ultra-low-latency authorization services in Go</strong>. He has <strong>11+ years</strong> in backend engineering across <strong>.NET and Go</strong>, and he writes <strong><a href="https://www.linkedin.com/newsletters/the-weekly-golang-journal-7261403856079597568/">The Weekly Golang Journal</a></strong>, focused on turning system design into usable, operational guidance&#8212;especially around performance and efficiency.</p><p>He lays out the inflection points that justify splitting&#8212;deployment friction, widening blast radius, and the need for truly independent scaling&#8212;while emphasizing that flexibility comes with a real operational tax. On cost and resilience, Agarwal makes the same argument from a different angle: engineering decisions should be evaluated as <em>performance per dollar</em>, not performance in isolation. He describes building cost awareness into the design process via observability, explicit cost discussions, and being disciplined about scaling only when needed. </p><p>Finally, the conversation shifts from production architecture to <strong>interview performance</strong>. Agarwal recommends that candidates stand out by aligning on requirements first, surfacing trade-offs explicitly, and communicating clearly enough that the interviewer can follow the &#8220;commit history&#8221; of their reasoning. He also explains how he expects candidates to handle changing constraints midstream&#8212;by absorbing the change, restating it, and selectively updating only the affected parts of the design&#8212;while building breadth through fundamentals, real-world problem practice, and a few deep specialties.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-rOLA2NpKPfM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;rOLA2NpKPfM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/rOLA2NpKPfM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h1>Emerging Trends and Challenges in System Design</h1><p><em><strong>1. We&#8217;re seeing this pendulum swing in architecture, with many teams rethinking a pure microservices approach and embracing modular monoliths to reduce complexity and cost. How do you decide when a microservices architecture is truly warranted versus keeping a system design simpler?</strong></em></p><p><strong>Archit Agarwal:</strong> To be very honest, this is the first question that I ask myself when I start designing a new system or a new module. And the rule that I follow is very simple: If the problem isn&#8217;t complex yet, don&#8217;t overengineer it. Start with just a monolith.</p><p>New engineers that come into the industry come up with a lot of these buzzwords&#8212;event-driven architecture, microservices, serverless. They&#8217;re great, but you cannot apply everything in just one go until your application really needs it, right? So that is a key difference between any interview-ready engineer and a genuinely good engineer: a genuinely good engineer would not want to implement everything up front. He would engineer things around the problems that we are facing.</p><p>In any early project that you see when you start with a project, the requirements are always changing. You have very little understanding of the domain, right? And the scope is very small. So you should not go into implementing every new buzzword that you see in the industry. You start small, start with a monolith, and design in a way that, in the future, if you want to break that down, you can easily do that, right?</p><p>And if your application requires a low latency&#8212;for example, if you&#8217;re working on a financial kind of system&#8212;you cannot live with only microservices. You will have to evaluate if microservices are good for you. Ideally, if you use microservices, there is always going to be additional network hops, and it will be slowing down the system, right? So I would always say that microservices aren&#8217;t the magical fix that fixes bad architecture, right? They just distribute that over the network.</p><p>So when you start writing your application, start with a monolith and then start understanding if you have the pains where the pain of having the monolith is greater than the pain of splitting it. Ideally, we would have a lot of signals when we can identify whether we should move out of a monolith or not. A few of those signals are: your deployments are getting bigger and slower, you have a larger blast radius on the bugs that you will see, or you need a lot of independent scaling.</p><p>For example, if you have a sale for an e-commerce platform, if there is a sale coming up, you would always want your payment-related system to scale larger than your login system, right? So if those are the requirements, you definitely start moving out of a monolith and move into microservices.</p><p>And there are a lot of other things. For example, if you need different tech for different problems. If you want to have analytics, you would want to use different technology for that, right? So in a monolith, you cannot have your project written in multiple languages.</p><p>So microservices definitely give you flexibility. They also give you headaches, so you should always choose wisely.</p><p><em><strong>2. With cloud spending at an all-time high, there&#8217;s sustained CFO scrutiny on engineering decisions. How do you incorporate cost considerations into system design? </strong></em></p><p><strong>Archit Agarwal:</strong> Ideally, I would say this is a point where every engineer becomes a philosopher. I remember one quote from&#8212;I don&#8217;t know where I read it, but it stuck to my mind&#8212;and it said that a good engineer would design for performance, but a great engineer would design for performance per dollar.</p><p>So any engineer who is thinking about the cost with respect to the performance gain is a great engineer. I didn&#8217;t truly understand this quote until one of my family members started one of his startups and I was involved with him in all the tech-related discussions. That was the first time when I realized, OK, when I&#8217;m fighting with my manager or my senior manager over using a particular tech, why do they always say no if they don&#8217;t need it? And I&#8217;m always saying that it will help us scale, right?</p><p>That was the first time when I started realizing the importance of why I was denied a lot of requests&#8212;because those were not the real pain that I was solving for, right? Trust me, every system will definitely cost something, and you need to understand that no business can keep spending money on something that is not needed at that particular moment.</p><p>And to be honest, we had one client&#8212;I&#8217;ll give you one more instance&#8212;where, as a team, we saw great advantage. There was one client who was pushing to reduce the infrastructure cost, and we as engineers, again, we were not doing that. So what he did is he introduced a dashboard where we were seeing per-engineer cost of the infrastructure for the development process. And those numbers were huge per month. And to be very honest, seeing those numbers listed against each person&#8217;s name, everyone started evaluating whether to use a particular tech or not.</p><p>Like, whether it is really needed, or when you log off from the system, should you shut down your EC2 instances or not, right? That is a huge difference, and in six months, we saw a 20% month-on-month decrease in the infrastructure cost.</p><p>So I would say I follow a few principles with that. I don&#8217;t prematurely optimize, but I stay observant on the infrastructure. I keep my observability to the extreme so that I can have a dashboard and see where my system is lacking, what part to scale, where I should have improvement. So observability is very important in this perspective.</p><p>Then I always design my system for horizontal scaling, but I don&#8217;t horizontal scale unless it is needed. Because if you have infrastructure which is of no use, there&#8217;s no point spending that money. But you should have that in your infrastructure requirements and your lifecycle.</p><p>For example, if you are using an S3 bucket and now you have 100 GB of data there which is ideally not being used for months&#8212;or will never be used&#8212;why do you want to spend money on live data there? You should push it out to cold storage and spend less on that data which is practically not being used.</p><p>Then, into the technical conversation: for every story that we start designing, we have design discussions. In the design discussion, we would try and include the costing. At times we see that engineers come up and say that they&#8217;ll reduce the latency by 10%, but to reduce the latency, they&#8217;re increasing the cost of the infrastructure by two times or three times.</p><p>So then the question is again on the engineer: Do we really want to improve the latency with the high cost? If it is really needed, we are OK to spend, right? But if it is not really giving any advantage to the user&#8212;of that 10% decrease in your response time&#8212;by spending that great amount of infrastructure cost, this makes the team aware that performance without cost awareness is just expensive engineering. So you should not just keep adding to infrastructure cost every now and then.</p><p><em><strong>3. Modern systems are facing record-breaking DDoS attacks and increasingly complex supply-chain threats. For instance, 2025 saw hypervolumetric DDoS attacks peaking at multi-terabit levels and a 188% year-over-year spike in malicious packages in open-source registries. How do you design systems to be resilient against such attacks and vulnerabilities that are increasing exponentially?</strong></em></p><p><strong>Archit Agarwal:</strong> In today&#8217;s world, I don&#8217;t design things thinking that I&#8217;ll not get attacked. I always design thinking that I&#8217;ll always be attacked, and how would I react when I&#8217;m attacked?</p><p>Modern systems are operating in very hostile environments. So you should always assume two things: the system will fail&#8212;that is for sure, that is inevitable&#8212;and then you&#8217;ll definitely get attacked now or in the near future. So if you plan your infrastructure and your architecture based on these two assumptions, you&#8217;re making good decisions to protect your system against these two things.</p><p>Once you accept these things, you can reduce the blast radius of these two things because now you are aware. So how do you do that? There are a couple of things that we start with.</p><p>First part is always a layered defense, where you start with your network layer. In your network layer is the first thing&#8212;your first defense layer&#8212;to protect yourself against any attack or anything. So you can use services that are given by the cloud provider. For example, AWS has a service that is called AWS Shielded Advanced. You can use that. Azure has a service. Google Cloud has a service. Every major cloud provider will have some service to protect with the network layers&#8212;you start using that.</p><p>Then in your application layer, you start adding code for limiting the request. For example, you start implementing rate limiters based on the geolocation, or IP, or user. Maybe you say that if a user is making more than 100 requests per minute, he&#8217;s probably trying to attack my system, because that&#8217;s not an ideal flow of a user to call my system 100 times in a minute. So we&#8217;ll block that user.</p><p>And maybe some bot-type of protection. For example, Google has a bot which crawls to every web page and collects the data for optimizing the search results. But Google&#8217;s bot makes sure that it is not overloading the server with a lot of requests to crawl the data. But there are bots that people write&#8212;bots that are made to overcrowd your server and keep collecting the data so that they can do some added advantage to themselves with the data that they collect from you. So you should write your application layer to protect yourself against such bugs.</p><p>Then your architecture has to have an upper limit on your auto scaling. So you cannot keep auto scaling to 100 servers for one service, right? Because if you&#8217;re scaling to that extent, that means there is some malicious activity going on your server suddenly. So you should always have an upper limit. Auto scaling is great until you realize that you&#8217;re auto scaling your DoS attacks.</p><p>Then the second thing on your defense would be having resiliency principles. For example, if you have a bigger application, you would always deploy it into multiple availability zones. Why? So that if one data center is under attack, you can completely shut down your service deployed on that data center, but still have your application up and running for users because your services are again in different data center&#8212;or maybe go multi-region.</p><p>Or these days, you can even go multi-cloud, but multi-cloud is not easy. You will have to consider a lot of things around multi-cloud.</p><p>Then is your supply-chain security. These days, modern applications are dependent on a lot of external services, so you need to make sure that whatever service version that you are using, you have already validated the service for the security risk&#8212;and you are not auto-upgrading until you validate it&#8212;because those dependent services are the actual surface area that you are exposing to the attacker. That is the service area&#8212;now you can start attacking on the service area. So that is the next thing that you look at.</p><p>Then you apply security by authorization, and by authorization you would always do a deny-by-default. You don&#8217;t say that I will allow everyone unless he has this role. No&#8212;you say that everyone is denied unless they have this particular access. So then you protect yourself.</p><p>Then your token should be short-lived. You don&#8217;t ideally create tokens that are living forever, right? So that even if the attacker has access to the token, he is only having access for a particular duration. He loses the access after the token has expired.</p><p>Then observability is the key. You should always have observability on your systems. You should never miss out observability and logging so that you don&#8217;t have visibility on things.</p><p><em><strong>4. Today&#8217;s architectures often depend on numerous third-party services and cloud providers, even if not by explicit choice. How do you design a system that remains portable and robust when you&#8217;re relying on external SaaS APIs, cloud services, or even multiple cloud environments?</strong></em></p><p><strong>Archit Agarwal:</strong> I was expecting that question with all the recent AWS, Azure, Cloudflare outages that have been going on in recent months. And to be honest, every system depends on a lot of different external services&#8212;for example, your database, all your messaging queue, your SaaS APIs&#8212;all of these are external dependencies. And you cannot create an application in these modern days without having dependencies on at least one of them.</p><p>So I would say multi-cloud is not always feasible because it has its own challenges. There are business challenges, there would be some data-related privacy challenges, and you have cost challenges definitely&#8212;because if you have multi-cloud, you will have a lot of huge costs that you will have to invest.</p><p>So ideally, we don&#8217;t design to avoid dependency. We design so that if one dependency creates a failure, the whole system is not down. That is the core intention of designing things. There are a few principles that we usually follow, and I think most engineers would agree.</p><p>We have an abstract layer for each external service. For example, if you are talking to a storage service, we have an interface through which our application will talk. Now this interface can any day go ahead and update the dependent service and say that today I&#8217;m talking to AWS, tomorrow I&#8217;ll go ahead and talk to Azure. So it would be easier for us to keep switching the external dependencies without impacting our actual application. So this is decoupling the application from the external dependency.</p><p>Then we can use open standards and some cloud-neutral tools. Standard as in containerization, Kubernetes, telemetry; use some databases that are open-ended&#8212;for example, Postgres, MongoDB. And for cloud-neutral tools, you can go ahead with using Terraform, where you can deploy to different cloud providers any day&#8212;you can choose between any.</p><p>Single region is a single point of failure, and single cloud can also be there, but you will have to be cost-smart on using multi-cloud. You need to make sure that your disaster recovery model is in place. You don&#8217;t replicate all the services to different cloud. Only replicate the mission-critical services to different clouds so that your users don&#8217;t have impact on their daily very important critical task&#8212;but some tasks can still be offline for some time and it&#8217;s still OK for them.</p><p>You&#8217;ll have to plan that, and then unified observability. You cannot have observability divided over different cloud or different region. You should have one single place to look at logs, traces, and everything so that you don&#8217;t do the guesswork. You have a curated list of everything at one place.</p><h1>Practical Architecture Insights from Experience</h1><p><em><strong>5. You personally have experience building ultra-low-latency services, such as global authentication systems. What design principles and techniques are crucial for achieving sub-millisecond latency at scale?</strong></em></p><p><strong>Archit Agarwal:</strong> Ultra-low-latency systems look very simple from outside, but they&#8217;re a totally different type of structure that we are building. So I treat latency as the monthly budget that you have. Now, every network hop or any memory allocation that you do will take something out from that budget, so you will have to be very smart in choosing where to spend.</p><p>So you don&#8217;t ideally optimize for speed&#8212;you eliminate whatever is slow. Start eliminating whatever is slow. So there are a few key principles that I usually follow, and I try pushing my team to follow those.</p><p>One is: move the computation closer to the user. So your computation layer should be closer, or deployed into the edge location where the user is trying to access from. So let&#8217;s say I&#8217;m living in Bangalore and I&#8217;m trying to connect to a server sitting in the USA&#8212;I will have a lot of latency, right? So do that: fix the compute layer closer to the user.</p><p>Then avoid network hops completely in those hot parts where you want ultra-low latency. You cannot have network hops to different microservices. You always use in-memory everything. You don&#8217;t go to a distributed cache, you don&#8217;t rely on some other network server&#8212;because, again, you&#8217;re reducing the network hops.</p><p>Then you keep your service lean. You don&#8217;t use a lot of wrappers. For example, if you are using wrappers, those wrappers&#8212;finally&#8212;convert that into the native code only, right? So I would always recommend: remove those wrappers and directly communicate in the native language to the machine. That will improve the performance and reduce the latency time on your server.</p><p>Then improving your network layers&#8212;for example, reusing the HTTP connections will help. So you don&#8217;t really initialize HTTP connections again and again on your system. Then using the right protocols&#8212;so if your service-to-service communication you&#8217;re using maybe HTTP, it&#8217;s not good. You can use gRPC. gRPC is way faster than HTTP in service-to-service communication, so you choose that.</p><p>And then the last part is always the right hardware and the runtime that you&#8217;re running on. If your hardware is too old, too laggy, there is nothing that can solve the problem. You will have to fix the hardware also.</p><p><em><strong>6: If I asked you to summarize briefly, how do you ensure that pushing for extreme performance doesn&#8217;t compromise reliability or maintainability?</strong></em></p><p><strong>Archit Agarwal:</strong> Ideally, what I&#8217;ve observed in my experience till now is that, in an application, not more than 5% of the application actually requires that ultra-low latency. The 95% of the application is still OK with having a little more latency on that side.</p><p>So you only should optimize on that 5% which actually requires ultra-low latency. You cannot develop an application where everything is designed for ultra-low latency. So that 95%&#8212;I would always say&#8212;design it for readability and maintainability. But for the 5% which requires low latency, there we can still compromise on the readability and improve the latency there.</p><h1>Cracking the System Design Interview</h1><p><em><strong>7. System design questions are broad and open-ended, and probably that&#8217;s why they&#8217;re challenging. Do you recommend using any kind of structured approach or framework to tackle these interviews?</strong></em></p><p><strong>Archit Agarwal:</strong> System design interviews are not about memorizing a particular framework. It&#8217;s about thinking in a framework. Having a framework will never have a bad impact&#8212;it will only help you because now you are more calm, and you&#8217;re approaching the problem in a structured way without using buzzwords very initially in the conversation.</p><p>I&#8217;ve seen a lot of engineers come in to a system design interview and, as soon as I give a problem&#8212;let&#8217;s say, &#8220;design this system&#8221;&#8212;they start with, &#8220;let&#8217;s use microservices,&#8221; and start using distributed cache. But they didn&#8217;t understand what scale I want the system to be in. And when I asked, &#8220;How many users are you planning on this system?&#8221; they would ideally say 1,000 users or 10,000 users in a minute. But is that really needed? Is that really what I wanted? That&#8217;s not in alignment.</p><p>So I would always say: start with one to two minutes of quick alignment with the interviewer. Try and gather the functional requirement, where you basically get answers to two main questions: What are we actually building, and what does the user actually need? By this, you will understand what the database model is&#8212;whether the system is read-heavy, write-heavy, what type of system it is. Then you go into nonfunctional requirements. Now, nonfunctional requirements are the ones that actually drive the architecture.</p><p>So in nonfunctional requirements, you ideally collect data around the number of requests that you are planning on, the scale at which you are operating, the consistency that you are looking for, or is there any latency requirement there. Nonfunctional requirements are the ones that decide the architecture&#8212;not the other way around.</p><p>So yeah, I would say: consider the system design interview as two engineers discussing a problem. It should not be like you are getting interrogated by the other person. If you are asking the right questions in the initial one to two minutes, you have already impressed the interviewer. He&#8217;s already giving all the ears to you now&#8212;he&#8217;s listening to the conversation, and he&#8217;s also interested in giving his thoughts on that. After doing all this, now you can move to high-level design and get into the different parts of it.</p><p><em><strong>8. So according to you, how should candidates break down a complex design problem during an interview to ensure they cover all important aspects? I know part of it is asking those questions, but what else?</strong></em></p><p><strong>Archit Agarwal:</strong> Basically, when it comes to a system design, you should try and break that complex system into smaller pieces and then go to the high-level design.</p><p>So once you have got those questions answered&#8212;basically functional and nonfunctional requirements&#8212;then you start by introducing a very high-level design diagram, and then you start zooming into one piece at a time. For example, you have given the high-level architecture where you say that there is a user who is making a request to the API server. Which request goes to the service, and then the service makes the call to the database or maybe the caching layer, and the response is sent back. That&#8217;s a very high-level architecture that you have.</p><p>Now you start zooming in: What type of API gateway? Do you need a load balancer? Do you need multi-region deployment? And all these are answers that you have already collected from the nonfunctional and functional requirements&#8212;and this is how you start introducing your thought process.</p><p>And in this process, when you are trying to zoom into each piece, what you do is, ideally, you start discussing the trade-offs. For example, when you talk about database, you say, &#8220;I&#8217;m using a relational database.&#8221; Why are you using a relational database? Why not NoSQL? That is a trade-off that you should introduce in your conversation. Then why are you using EC2, not a Lambda service, right? So all these trade-offs are something that you start discussing, because system design, ideally, is about discussing the trade-offs.</p><p>So if you know the trade-offs&#8212;why you&#8217;re using a particular thing over the other&#8212;you have already made progress where the interviewer knows that this person knows things well. He knows his choices. He understands why to and when to make a choice.</p><p>So by this time, he will be very confident that this guy will be able to design an application which is operating at a Google scale. Maybe the application is as simple as a to-do application, but he will be able to take it to the scale level that we want.</p><p><em><strong>9. And if you turn the lens inwards a bit from your perspective as a system design interviewer, what is your process for evaluating a candidate&#8217;s depth versus breadth?</strong></em></p><p><strong>Archit Agarwal:</strong> So honestly, a system design interview is not about the diagram and memorized architecture. It&#8217;s about building a thinking muscle more, right? Most people try to study system design like a subject, but I would say: think of system design as a skill that you are adding to your bucket, right? It&#8217;s a skill you need to improve with structured and deliberate practice. Start with strong fundamentals&#8212;that&#8217;s what we just discussed, right? You should have strong fundamentals.</p><p>Then start practicing mock interviews. Take help of some person&#8212;maybe a mentor or a friend&#8212;who can sit down with you. You start designing one system design problem. For example, start with a URL shortener. Start discussing it with your friend or a mentor. And try to form a complete framework where you say that first, in any system design, I&#8217;ll get these things answered; then I&#8217;ll go to this part; and then I&#8217;ll go to this part. Try and do your system design practice in that particular framework so that you are very comfortable.</p><p>Be comfortable with the framework itself. You should not memorize the questions that you have to put in, because the questions will keep changing based on the system. But the framework should be good enough so that you have easy traversal through the problem, and it is easy for you to travel there.</p><p>Then work backward in a real-time system. So what I usually do is, I question myself on a few systems. For example, if we are using WhatsApp&#8212;everyone uses WhatsApp mostly, right?&#8212;so I would think about how WhatsApp is able to scale the messaging server. And now I will start exploring articles, blogs, engineering blogs around it, and start understanding how we can do that, right? Or maybe how Netflix is able to scale the streaming globally. That&#8217;s a complete different engineering challenge. How is Netflix able to do it? So start backward, think about the system, and then start researching about it.</p><p>Then start building things. So then you start building things&#8212;and maybe you don&#8217;t do it at a global scale, but at least when you start building, you will understand the challenges around latency, or maybe race conditions, or all those constraints that you think about, right? You start feeling that, and you start solving that.</p><p>And then the last part is definitely: learn to communicate. Because if you don&#8217;t learn to communicate system design interviews, you&#8217;ll not be able to excel there.</p><p><em><strong>10. But do you recommend any specific resources, books, or specific real-world exercises for mastering system design concepts and being interview-ready&#8212;especially for senior engineers aiming to showcase their expertise?</strong></em></p><p><strong>Archit Agarwal:</strong> See, for someone who is aiming for a senior role, I would definitely suggest a mix of a few things&#8212;starting from a book, real-world blogs, and then real-world exercises.</p><p>So for books, I would recommend you should definitely read <em>Designing a Data Intensive Application</em> by Martin. That is a must-read book for any senior engineer who is aiming to excel in system design. Then there are books like <em>System Design Interview, Volume One</em> and <em>Volume 2</em> by Alex Liu, right? Those two are very good books. Then <em>Building a Microservice</em> by Sam Newman.</p><p>So those are a few very good books that have been written. And if you read those books, you&#8217;ll get a lot of understanding on system design. Then you can refer to some engineering blogs by big tech giants. For example, Netflix has an engineering blog. Uber has an engineering blog&#8212;and all those big tech giants who are into technical space, and they have a big tech infrastructure that they maintain, they always have engineering blogs. Go refer and read those blogs. Go to high-quality YouTube channels where they&#8217;re not just discussing the diagram&#8212;they&#8217;re discussing the concept, more depth into the concept. So refer those channels, in case you want.</p><p>And then finally is designing a system which is time-tested, scale-ready, and you have done that. So system design interviews isn&#8217;t cracking by memorizing some answers. They&#8217;re cracked by building strong foundation, real practicing problems, and then thinking like an engineer, not an exam candidate.</p><p><em><strong>11. Even experienced engineers can stumble in design interviews. What are some of the most common mistakes or pitfalls you see candidates make&#8212;especially when they&#8217;re quite experienced and perhaps more confident than some others&#8212;and how can engineers avoid these mistakes?</strong></em></p><p><strong>Archit Agarwal:</strong> System design interviews are funny because people don&#8217;t fail because they don&#8217;t know what Kafka is, or maybe DynamoDB. They fail because of the way they communicate with the interviewer.</p><p>So I would say that if you&#8217;re having good communication&#8212;and you&#8217;re establishing that communication and having a two-way communication with the interviewer&#8212;that&#8217;s half of the job that is already done. I&#8217;ve seen engineers who jump directly into solutions as soon as they listen to a problem where&#8212;let&#8217;s say I say, &#8220;design this system&#8221;&#8212;and they would start saying, &#8220;I&#8217;ll use Redis, I&#8217;ll use Kafka.&#8221; I would say, slow down. First, understand the scale constraints. For example, how many requests per second are we operating at, or how much data are we expecting per day flowing in the system? Or is there a security requirement?</p><p>For example, if you&#8217;re operating in a European country, you have different compliance on the personal identifiable information than in other countries, right? So you should start asking those constraints first and then start coming to a conclusion and architecting things, right?</p><p>And you probably don&#8217;t need to design at Google scale everything. It doesn&#8217;t have to scale to Google, right? There are things that are defined for small scale only. For example, let&#8217;s say there is an application that I want to design that is only to be used by my company&#8217;s engineers&#8212;it doesn&#8217;t have to go outside that. So why do I need multi-region deployment? I can do a local area network deployment and live with it, right? I don&#8217;t even need cloud there.</p><p>So those problems you need to understand. Then if you understand how many requests, how many servers would you need, or how big a database do you need, right? So if you start addressing those basic questions, I think you are already sorted and you are on the right track on that.</p><p><em><strong>12. Have you ever seen a case where the interviewee has asked too many questions? Has that ever happened?</strong></em></p><p><strong>Archit Agarwal:</strong> Yeah, I have once seen one interviewee who was asking too many questions, and that particularly gave me an idea that the question that I have probably asked him is something that he&#8217;s not aware of.</p><p>For example, I gave him a system. He didn&#8217;t have any idea about the system. He&#8217;s never thought about that. He might be using that every now and then, but he has not given it a thought. But it is OK. Let&#8217;s say if I&#8217;m interviewing a very junior engineer, he might not have thought about a lot of things by then, and if he&#8217;s asking too many questions, it is still OK.</p><p>But if he&#8217;s asking questions that are very small, and I think those are very basic for that particular level of engineer, then it raises a red flag. But asking clarification questions is perfectly OK.</p><p><em><strong>13. Now, as you&#8217;ve also said, a system design interview isn&#8217;t just about the final answer, right? It&#8217;s about how you communicate, how you adapt to the constraints you&#8217;ve sort of discovered during the conversation. Interviewers often value a candidate&#8217;s ability to clearly explain their thinking and reasoning&#8212;and the ability to adjust to constraints that are put in front of them mid-discussion, even. So in this context, how important are communication skills in these interviews, and what does good communication look like for a system design question?</strong></em></p><p><strong>Archit Agarwal:</strong> OK&#8212;so, honestly, communication is half of your system design interview. Or maybe it can be more. Let&#8217;s say if I am capable of designing a beautiful architecture in my head and I&#8217;m not able to communicate or explain it to the other person, the interviewer will see that architecture doesn&#8217;t even exist for them, right? Because you were not able to explain it to them.</p><p>So I have seen candidates who design very solid system design architecture, but they were either too quiet, or used too many jargons, or were too scattered in explaining the information. And in a system design interview, it is about how you communicate and explain to the other person the architecture that you are thinking about, because that gives insight into whether this person will be able to work with a team of architects, product managers, and junior engineers&#8212;whether they&#8217;ll be able to explain what they&#8217;re thinking. The system design interview is also intended to understand your communication skill as well.</p><p>On the technical side, there are a few things that I always suggest to everyone. Think out loud. You should not be silent for, let&#8217;s say, five minutes and you&#8217;re just thinking about the system. Start speaking whatever you are thinking. People need to know your brain&#8217;s commit history, basically&#8212;whatever you are thinking.</p><p>So maybe you are saying that, &#8220;I&#8217;m choosing this approach because of this thing,&#8221; or &#8220;given that this is the scale at which we are operating, this option makes more sense.&#8221; Start communicating your ideas. Maybe you are not communicating the right thing, which is good for the system&#8212;but once you communicate, when you read out loud your idea, you will automatically make more sense and you&#8217;ll auto-correct yourself, and it is perfectly OK if you&#8217;re auto-correcting yourself.</p><p>The interview should not feel like a monologue where you&#8217;re just speaking and the other person is listening. Because trust me, if that is happening, you should get the indication that you have already lost the session. So to do that, you will have to start structuring your answers. Basically, what you say is important, but how you say it is more important than that, right? So a good candidate would break the answer into multiple steps. Summarize things. Occasionally, start transitions&#8212;like, &#8220;Now I would go into, I would start discussing the data flow,&#8221; &#8220;Let&#8217;s start discussing the caching strategies,&#8221; these kinds of things.</p><p>Check if the interviewer is aligned to your communication or the approach that you are trying to follow, and make that interviewer feel that they are sitting with another engineer who is trying to collaborate and bring up a good system. That&#8217;s the intent that they want to see.</p><p>Your things that you say should not be meant to impress them. You are not there to impress them with a large amount of jargons that you say, or big words. You should be very clear, concise, and make sure that your communication is so clear that even if the other person is very junior to you, they can still understand. That&#8217;s the core of communication, right? Your communication should not only travel up the ladder; it should also travel down the ladder when you&#8217;re communicating.</p><p>Then listening is another advantage that you&#8217;ll have. If you&#8217;re not listening to the interviewer, you&#8217;ll not be able to respond to the feedbacks that they want to implement&#8212;or maybe you&#8217;ll not be able to adopt whatever they&#8217;re giving as feedback. So you should always try listening more to the feedbacks that the interviewer has.</p><p><em><strong>14. Some really excellent tips there, Archit. But what happens if an interviewer throws a curveball&#8212;say, suddenly the constraints change? You&#8217;ve sort of thought it through really well. You&#8217;re in the flow, you know you&#8217;re doing really well, the goal is almost in sight&#8212;but this new constraint or change in scope is just thrown at you. So what&#8217;s the best way to handle this kind of situation?</strong></em></p><p><strong>Archit Agarwal:</strong> To be honest, I love when an interviewer throws these curveballs. Now, why? Definitely they&#8217;re not easy. When you are into the system design, you are halfway through and you&#8217;re almost there, and something changes&#8212;it&#8217;s really frustrating.</p><p>But, to be honest, that&#8217;s the real-world scenario, right? You&#8217;re always designing things, and suddenly things will always change. Your actual world is also in that same sense. So if you are not able to adopt, then there&#8217;s no point designing architecture, right? So if an interviewer is giving you a curveball, think about it as a chance for you to showcase your adaptability according to the changing scenarios.</p><p>So here is how I would ideally approach it. I would not panic, and I would not go ahead and start defending my original diagram, right? I would first absorb what they&#8217;ve mentioned and then say, &#8220;OK, this changes these things. Now let me think about how we can adopt to this.&#8221; Now this gives the other person a hint that Archit is flexible and he&#8217;s not egoistic on his design approach, which is one good sign.</p><p>Then I would restate whatever they have mentioned to make sure that we are aligned on the same requirement change that we have seen. I&#8217;ll always reiterate in my own words, right?</p><p>Then the third thing that I&#8217;ll do is start highlighting what part of the system will have to undergo changes and what part will remain intact. This also gives a very clear understanding whether I&#8217;m able to structure the redesign approach&#8212;understand what part of the system still can be the same and doesn&#8217;t have to.</p><p>The curveballs that the interviewer gives you&#8212;the changes that the interviewer gives you&#8212;will never be in a way that you will have to scrap the complete diagram, the complete architecture, unless you were already off the track, right? They want to understand: how do you plan what part of the system can remain as it is and what part of the system can change, and how flexible is your system to changes.</p><p>And if there is something that is complex, be honest. No one expects you to have knowledge on everything. So if there is something that is complex, think that you are in a two-way communication with an engineer. You can start speaking about it. If this is a complex thing, you can say that this is a bit complex and these are the trade-offs that we&#8217;ll have to make&#8212;and try and include the interviewer in your communication in those things.</p><p>So this is how you will succeed. System design interviews are not about being right all the time. They&#8217;re about how clearly you can think, how well you can explain, and how gracefully you can handle the changes.</p><p><em><strong>15. Candidates are expected to know advanced concepts that used to be considered niche, and this continues on very well from what you were just saying just now. So, for example, in a scenario like designing a location-based service, it may be assumed that you have knowledge of geohashing or spatial indexes. So how should candidates prepare for this breadth-of-knowledge challenge that has sort of become more and more expected?</strong></em></p><p><strong>Archit Agarwal:</strong> To be honest, the bar is definitely raised. Now, once the things that were termed as &#8220;nice to know&#8221; are something that are considered that you should know with the same experience level. So I would not deny that fact, but here is the thing: I don&#8217;t think a candidate needs to be an encyclopedia on that side. If they are an intentional learner, it&#8217;s good enough&#8212;because no one can ideally learn everything. Tech space is too big for all that right now. There are a lot of things in tech space. No one can learn everything.</p><p>But having said that, in an interview, if you&#8217;re getting some question that is out of your league, you definitely will panic. So how I approach my learning and catering to those things nowadays is having four layers in your preparation module.</p><p>First thing: build extremely strong fundamentals. Your fundamentals are extremely important because any advanced topic you can term right now has always been starting from a basic system. There was a basic system which had some issues&#8212;that&#8217;s why this advanced system was innovated, right? So if you know the basics well&#8212;for example, you know how a database works, or how indexing in the database works&#8212;how can a distributed system fail, or what are the different consistency models, right? If you know these basics, it is more than enough for you to start establishing your knowledge in those advanced topics. So make sure that your fundamentals are very clear.</p><p>Then learn the advanced topics through real problems. I would not just go ahead and keep reading articles or books around those advanced topics. I would just say: let&#8217;s say I want to start understanding geohashing&#8212;so I would not just read about it; I would design a food delivery app to understand geohashing. If someone says that I want to understand Kafka semantics, just don&#8217;t read about it. Start defining or designing a real-time analytics system where you include this topic, and that&#8217;s how you will deepen your knowledge in these areas.</p><p>Now after all this, pick up two to three areas where you will go deep. Because personally, I believe you should have deep knowledge in one or two areas at least, because when you go into an interview, the depth of the knowledge is directly reflected&#8212;because that topic you will be speaking more, right? And trust me, any engineer who is interviewing you, if you go deep into one particular topic, they understand that this is some area that you are more interested in. And if you&#8217;ve gone to that depth, that means you are already an engineer who understands the gravity of things. So you can maybe think about systems that you can go deep into&#8212;like, for example, a distributed system, or a storage system, authentication system, or maybe go deep into performance engineering.</p><p>Then practice is important. Practice articulating how you can discuss the trade-offs. Maybe ask a friend to sit with you and talk to them on the trade-offs. So once you start communicating and your friend gives you feedback, you will start improving your communication skills on the discussion of those trade-offs. So that is the fourth thing that is very important.</p><p><em><strong>16. If you turn the lens inwards a bit from your perspective as a system design interviewer, what is your process for evaluating a candidate&#8217;s depth versus breadth?</strong></em></p><p><strong>Archit Agarwal:</strong> Honestly, a system design interview is not about the diagram and memorized architecture. It&#8217;s about building a thinking muscle more, right? Most people try to study system design like a subject, but I would say: think of system design as a skill that you are adding to your bucket. It&#8217;s a skill you need to improve with structured and deliberate practice. Start with strong fundamentals&#8212;that&#8217;s what we just discussed. You should have strong fundamentals.</p><p>Then start practicing mock interviews. Take help of some person&#8212;maybe a mentor or a friend&#8212;who can sit down with you. You start designing one system design problem. For example, start with a URL shortener. Start discussing it with your friend or a mentor. And try to form a complete framework where you say that first, in any system design, I&#8217;ll get these things answered; then I&#8217;ll go to this part; and then I&#8217;ll go to this part. Try and do your system design practice in that particular framework so that you are very comfortable.</p><p>Be comfortable with the framework itself. You should not memorize the questions that you have to put in, because the questions will keep changing based on the system. But the framework should be good enough so that you have easy traversal through the problem, and it is easy for you to travel there.</p><p>Then work backward in a real-time system. What I usually do is I question myself on a few systems. For example, if we are using WhatsApp&#8212;everyone uses WhatsApp mostly&#8212;so I would think about how WhatsApp is able to scale the messaging server. And now I will start exploring articles, blogs, engineering blogs around it, and start understanding how we can do that. Or maybe how Netflix is able to scale the streaming globally&#8212;that&#8217;s a completely different engineering challenge. How is Netflix able to do it? So start backward, think about the system, and then start researching about it.</p><p>Then start building things. Maybe you don&#8217;t do it at a global scale, but at least when you start building, you will understand the challenges around latency, or maybe race conditions, or all those constraints that you think about. You start feeling that, and you start solving that.</p><p>And then the last part is definitely: learn to communicate. Because if you don&#8217;t learn to communicate system design interviews, you&#8217;ll not be able to excel there.</p><p><strong>17. Do you recommend any specific resources, books, or specific real-world exercises for mastering system design concepts and being interview-ready&#8212;especially for senior engineers aiming to showcase their expertise?</strong></p><p><strong>Archit Agarwal:</strong> See, for someone who is aiming for a senior role, I would definitely suggest a mix of a few things&#8212;starting from a book, real-world blogs, and then real-world exercises. For books, I would recommend you should definitely read <em>Designing Data-Intensive Applications</em> by Martin. That is a must-read book for any senior engineer who is aiming to excel in system design. Then there are books like <em>System Design Interview, Volume One</em> and <em>Volume 2</em> by Alex Liu. Those two are very good books. Then <em>Building a Microservice</em> by Sam Newman.</p><p>So those are a few very good books that have been written, and if you read those books, you&#8217;ll get a lot of understanding on system design. Then you can refer to some engineering blogs by big tech giants. For example, Netflix has an engineering blog. Uber has an engineering blog&#8212;and all those big tech giants who are in the technical space and have big tech infrastructure that they maintain, they always have engineering blogs. Go refer and read those blogs. Go to high-quality YouTube channels where they&#8217;re not just discussing the diagram&#8212;they&#8217;re discussing the concept, more depth into the concept. So refer to those channels, in case you want.</p><p>And then finally is designing a system which is time-tested, scale-ready, and you have done that. So system design interviews isn&#8217;t cracked by memorizing some answers. They&#8217;re cracked by building strong foundations, really practicing problems, and then thinking like an engineer, not an exam candidate.</p><p></p>]]></content:encoded></item><item><title><![CDATA[Coroutines vs Virtual Threads and the Kotlin Java Decision in Practice: A Conversation with José Dimas Luján Castillo and Ron Veen]]></title><description><![CDATA[How to avoid &#8220;Java-style&#8221; Kotlin, modernize enterprise stacks with Jakarta EE, and evaluate modular monoliths, microservices, and Kotlin Multiplatform in real teams]]></description><link>https://deepengineering.net/p/coroutines-vs-virtual-threads-and</link><guid isPermaLink="false">https://deepengineering.net/p/coroutines-vs-virtual-threads-and</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 12 Feb 2026 07:58:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/5pfsenEI-bc" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Kotlin has moved from &#8220;Android-first&#8221; to a practical option for Java teams that want safer, more concise JVM code without abandoning their existing Java investments. </p><p>In this conversation we speak with <strong>Jos&#233; Dimas Luj&#225;n Castillo</strong> and <strong>Ron Veen</strong>, co-authors of <em><strong><a href="https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836">Kotlin for Java Developers</a></strong></em> (Packt). </p><p>Jos&#233; is a mobile-focused technologist with <strong>15 years</strong> of experience building <strong>Android, iOS, and Flutter</strong> applications and leading teams globally; he has worked on <strong>500+ mobile apps</strong>, written <strong>7+ development books</strong>, and taught at <strong>25+ universities</strong> across Latin America.</p><p>Ron is a seasoned <strong>JVM engineer</strong> with <strong>20+ years</strong> in the Java ecosystem, spanning <strong>mainframes to microservices</strong>; he&#8217;s an <strong>Oracle Certified Java Programmer</strong> and <strong>Sun Business Component Developer</strong>, serves as a <strong>special agent and lead developer at Team Rockstars IT</strong>, speaks at international conferences, and has authored books on <strong>Jakarta EE cloud-native migrations</strong> and <strong>modern concurrency</strong> (including virtual threads and structured concurrency).</p><p>Together, they discuss what it takes to make Kotlin a first-class citizen alongside Java in production: writing idiomatic Kotlin, choosing between coroutines and virtual threads, modernizing enterprise systems with Jakarta EE, navigating microservices versus modular monoliths, and adopting modern Android and cross-platform approaches such as Jetpack Compose and Kotlin Multiplatform.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-5pfsenEI-bc" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;5pfsenEI-bc&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/5pfsenEI-bc?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h1>Kotlin and Language Evolution</h1><p><em><strong>1. From your perspective, what are the biggest benefits Kotlin offers to today&#8217;s senior developers compared to Java? And in what areas do you still find Java holding its ground?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> When we started using Kotlin on Android, the difference was very obvious because Java was too verbose. Android is doing some things in mobile development with the Java characteristics. So, for example, it was very easy to see the real benefits&#8212;for example, null safety. It&#8217;s automatic. So in this case it was very, very fast. The interoperability with Java&#8212;because at the end, if you have legacy code in another language, it&#8217;s where you want to try a different language just because it&#8217;s modern, even if you have details with the language, because you will have a lot of problems if you change to a new technology, framework, or language. So interoperability at the beginning&#8212;obviously, we didn&#8217;t believe we had good interoperability. But when we tried it, we saw, OK, maybe I can do the next steps in my applications with Kotlin, but I don&#8217;t need to fight with the legacy code even if it&#8217;s in another language. So I think interoperability was a good point to start with Kotlin for mobile development, and obviously we have other things as pushing the programming in a very easy way to add it&#8212;or the synchrony. But I think those two points to start for any mobile development, when we try to start with Kotlin, it&#8217;s a good point. Well, at the end we need to remember the first step for Kotlin was mobile development, and it was very easy to be clear if I would start with Kotlin&#8212;but if Kotlin doesn&#8217;t use this null safety and interoperability, probably the adoption was slow or more complex for the people. Because, as I mentioned two minutes ago, the main problems are still there even if you change the language. So I think that&#8217;s my point for this comment.</p><p><em><strong>2. How should engineers decide when to use Kotlin versus Java when it comes to new projects?</strong></em></p><p><strong>Ron Veen:</strong> Yeah, I think my experience comes more from enterprise and not so much mobile development. What I&#8217;ve seen there is, well, there&#8217;s a natural eagerness from developers to learn new things. And like Jos&#233; said, when you switch from Java to Kotlin, it really feels&#8212;I don&#8217;t know&#8212;it makes programming a bit more fun again. That&#8217;s something I really found. I could see developers getting enthusiastic. I think one of the benefits is you actually have more concise code, so you write less code. And also, like mentioned, the code tends to be less error-prone. You know, null safety is really a big thing. You can actually see that a lot of frameworks are working towards that now, also with regard to new Java versions, but nullability will always be a thing in the JVM, and Kotlin takes care of that for us.</p><p>Now, <strong>why would you adopt this technology?</strong> Well, I think there could be a number of attractive points. Again, like I said, there will be less code&#8212;and less code is good: less code to maintain. There are less errors in the code. It might make your development team actually more attractive to new hires because you&#8217;re using a very modern language. And we shouldn&#8217;t forget Java really evolved. Java had Project Amber, where it added a lot to the core language&#8212;like records and sealed classes and pattern matching&#8212;things that already were in Kotlin. Java gets them added slowly&#8212;they&#8217;re getting added&#8212;but I think Kotlin is just always quicker with adding those new features that developers really crave.</p><p><em><strong>3. Many Java veterans fall into the trap of writing Kotlin in a Java style. What are some common mistakes or mindset shifts that Java developers need to overcome to write idiomatic Kotlin?</strong></em></p><p><strong>Ron Veen:</strong> I think that&#8217;s the thing we all get to at some point. We start with Kotlin and then we write it in this Java style, right? And this is not what we should do. It is a natural reflex&#8212;let&#8217;s be honest&#8212;because first: OK, I want to do a new language, but I also have a project to finish, or a task to finish, or a sprint to complete. But also, we&#8217;re still trying to write code in the way we know it and just use the little things. So that can kind of give you some problems there.</p><p>But sometimes, as a developer, you have to be willing to relearn the language. You know Java, and now you&#8217;re going to do Kotlin. You can do everything in Java style in Kotlin, but you really should try to relearn the language. It&#8217;s not a drop-in replacement. You really have to be willing to learn.</p><p>I can remember I once pitched it at a project and developers really got, &#8220;OK, this looks good, this looks fine.&#8221; And then, just to force myself to really do it in a Kotlin way, I tried to shrink the number of lines that were in there. The original from 250 went down to under 100 or something, and that was not technically needed, but it was: &#8220;OK, how can I leverage Kotlin&#8217;s native way of doing things and make things faster?&#8221; So sometimes you just have to pick up a piece of existing code and decide to rewrite it completely&#8212;and forget about everything you know about Java.</p><p><em><strong>4. Could you share best practices to avoid &#8220;Java-esque&#8221; Kotlin and fully leverage Kotlin&#8217;s language features?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> As Ron mentioned, that&#8217;s the thing. When we start with Kotlin, I think 99% of developers start by just translating the code. That&#8217;s the problem, but it&#8217;s part of the beginning&#8212;because we have one line of code in Java and we want to see how we need to write this line of code in Kotlin.</p><p>But later you hear about the benefits in Kotlin, and if you are translating each line, you will see you don&#8217;t have less code&#8212;you have the same code in a different way. Maybe it&#8217;s easier to read, but it&#8217;s the same code. But when you start asking that kind of questions, you will see the benefits because you will start trying to look inside how Kotlin is doing that. And you will see five lines or 10 lines in Java&#8212;maybe it&#8217;s two lines in Kotlin, or three. So you will see the real perspective from Kotlin, and this is the first step, I think, to look at good practices.</p><p>For example, you will see not only fewer lines or differences, but the achievement in these cases. For example, immutability first&#8212;you will see, <em>&#8220;OK, I need to think in immutability first,&#8221;</em> because it&#8217;s a different way to start the problem. So, for example, you are automatically using good practices in the general programming work without doing anything, because you don&#8217;t know sometimes, or you don&#8217;t have it clear. We are creating in Java, but we are not thinking in immutability first. So after a couple projects, or some examples you are trying to execute, you will start seeing you are using good practices sometimes&#8212;because it&#8217;s natural here in Kotlin as immutability.</p><p>But now you can do the same thing in other languages, even Java, OK? But you need to do something to have this in your code. In Kotlin, sometimes you do good practices by default. You don&#8217;t have to change the code to have it because, by default, you have these good practices. And you will see a lot of those cases&#8212;not just immutability. For example: the collections and streams, the lambdas. Lambdas is the same base. Lambdas is not something advanced when you are learning other languages; it&#8217;s not the first subject, the lambdas, for example. And here in Kotlin, when you are starting the code, you will see lambdas since the beginning sometimes. Even if you don&#8217;t know it&#8217;s a lambda&#8212;you know it&#8217;s code&#8212;but later you will see it&#8217;s a good point to start doing something. So I think that&#8217;s a good way to start using good practices, and sometimes you don&#8217;t even know you are using these good practices.</p><p><strong>5. I&#8217;d like to get your perspective on concurrency. Java&#8217;s recent releases have introduced virtual threads and structured concurrency to simplify multithreading. According to you, how should engineers approach concurrency now? For instance, when building a high-throughput service, how do you choose between Kotlin&#8217;s coroutine model and Java&#8217;s Loom-based virtual threads?</strong></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> Well, I think the way to&#8212;maybe it&#8217;s not the easy way to understand that&#8212;but I think if you are coming from Java, you can see concurrency as a model. It&#8217;s a&#8212;we have a definition about how we can do some stuff&#8212;and coroutines is a model, a structure, to think about it.</p><p>What are the important or main parts for this? You have cancellation, you have propagation, you have control of the whole cycle. That&#8217;s the thing. You don&#8217;t need to think too much in all the scenarios because you have the structure. So I think that&#8217;s the main concern when we are trying to understand, because sometimes you need to think by yourself in all these scenarios, but not in this case. Coroutines is very clear: you have the ingredients to start doing anything&#8212;OK&#8212;with coroutines.</p><p>Versus, for example, virtual threads: you will have a way&#8212;a very simple model&#8212;for example, it&#8217;s very traditional. We have it very, very clear, because the thread is a&#8212;Way to do the things. We have excellent things, but at the end, they can work together, because maybe you can use one for some specific problems and you have other options for other specific things.</p><p>It&#8217;s not a model about who is better, because that&#8217;s another thing. A lot of people, they want to know the faster way to do that: &#8220;Let&#8217;s go to the way to do that.&#8221; And it&#8217;s not the case about, in this case, coroutines. If you have some specific problems and you don&#8217;t have the model, well, think about: we have coroutines. But if you have a very complex situation, for example, and you need to put less complexity in your system, maybe you can use threads, for example.</p><p>That&#8217;s the thing: it&#8217;s not too complicated to use, for example, the Java virtual threads, because if you have a very complex structure with layers, probably this is the advantage&#8212;you can do that. But if you don&#8217;t have that kind of level of complexity, you can use coroutines, for example, for other things.</p><p>And at the beginning, obviously, the main complexity when you are using the JVM to do that kind of task&#8212;the problem is, you have a monolithic focus or scope for that, and now you don&#8217;t need to think just in the monolithic way to have an answer. I think that&#8217;s my comment for this.</p><p><em><strong>6. How you see these two concurrency paradigms (virtual threads and coroutines) complementing each other in practice.</strong></em></p><p><strong>Ron Veen:</strong> Yeah, first, we should say that I think both virtual threads and coroutines basically try to solve the same problem, which is executing async code, but making it appear sequentially in the code so the developer can really reason about, &#8220;OK, what is the flow of my application?&#8221; So there&#8217;s not this thing called callback hell, where we have a million places where there are callbacks all through the code.</p><p>Both systems, I think&#8212;really, even though in the end they&#8217;re running on native operating systems, that&#8217;s both virtual threads and coroutines&#8212;they&#8217;re actually managed by the runtime itself, and it just switches the threads to execute things. So you can really see that throughput is a lot higher with both of them, where, on the side of virtual threads, it is really good for blocking&#8212;when there&#8217;s blocking things in between. You shouldn&#8217;t&#8212;like, if you have very memory- or CPU-intensive action, I don&#8217;t think virtual threads, in general, is a good solution because it won&#8217;t give away control.</p><p>But in general, like Jos&#233; said, I don&#8217;t think it&#8217;s like a race between, &#8220;Oh, you should use this,&#8221; or &#8220;You should use that.&#8221; I think you should sometimes just look at what is best for your specific situation. Of course, virtual threads, they also have structured concurrency, which they build upon, which gives you a very nice framework&#8212;so that could actually be a good reason to choose.</p><p>Again, if you have these difficult, really difficult use cases in general, I think both virtual threads and coroutines is a paradigm shift for developers in their mindset because, normally, as Java developers, we were always told, like, you know, threads are really expensive. So you should be careful with it. We should pool the threads and stuff like that.</p><p>And I think what we see now is, with both of these things, that we can actually&#8212;calling a coroutine or calling a virtual thread is just as cheap as calling another function in your code. So it&#8217;s also a mindset of where you want to use this in your application.</p><h1>Enterprise Architecture and Team Practices</h1><p><em><strong>7. Enterprise Java is undergoing a renaissance with the release of Jakarta EE 11, which brings a modernized, cloud-optimized platform&#8212;introducing features like the Jakarta Data API and aligning with Java 21&#8217;s virtual threads for scalable concurrency. Ron, given your experience with cloud-native migrations, what do these developments mean for teams looking to modernize legacy Java or Jakarta EE systems?</strong></em></p><p><strong>Ron Veen:</strong> Yeah, I think with Jakarta EE 11, the whole ecosystem made an enormous step because now, for the first time, Java 17 is the baseline language level. So we&#8217;re really optimizing here. That means suddenly we can use things like records. Now we can use the switch expression, we can use non-sealed classes&#8212;all these things which were added by Java via Project Loom&#8212;they&#8217;re now actually available to also use on the enterprise side. So I think that&#8217;s already quite interesting.</p><p>Like you said, there&#8217;s this new API, Jakarta Data, which has this repository-based approach. And for people familiar with, for instance, the Spring Framework, it&#8217;s very close&#8212;not similar, but very close&#8212;to the repository pattern that Spring uses. So I think that is really good.</p><p>They also came&#8212;well, they already came earlier&#8212;with the Core Profile. Jakarta EE has multiple profiles, and the Core Profile is very interesting because it&#8217;s a very slim runtime that you&#8217;re getting then, which means it&#8217;s ideal for microservices situations.</p><p>But yeah, I really think it uses great chances. It promises Java 17 as a minimum standard, but there&#8217;s also this technology compatibility kit that goes to Java 21. And then you&#8217;re right&#8212;suddenly virtual threads also come within reach. And actually, using virtual threads, you have to run Java 21, of course, because that was the first official release where it was a final version. But there&#8217;s this thing called ManagedExecutorDefinition, which has this property &#8220;virtual,&#8221; and if you only set that, you can actually use virtual threads in your Java applications&#8212;or Java EE applications. So I think they&#8217;re making a real big step.</p><p>And about the migration part&#8212;just to get back to the question about the migration part&#8212;I think there&#8217;s many steps that you can actually take. But if it is a simple upgrade, you should first see: Am I already on Jakarta EE at all, or am I still on Java EE?</p><p>Now, if you&#8217;re still on Java EE, then there are multiple ways to migrate your sources, right? There&#8217;s this book&#8212;you&#8217;re right&#8212;from Packt Publishing, where we actually outline a number of these things. There are tools that you can use. There are even dedicated plugins for IntelliJ, for instance, that can help you a lot. It&#8217;s a lot about namespace conversion, but there are a few other tricks as well.</p><p>Now, if you are already on the Jakarta EE part, then I think upgrading is actually quite simple&#8212;basically upgrading your Java version. And this Jakarta Data project is actually quite interesting, and I would just advise architects and developers to say, &#8220;OK, use it if you&#8217;re building a new feature,&#8221; because it&#8217;s completely backwards compatible with JPA. So it has the same&#8212;you know&#8212;so there&#8217;s nothing new you need to change. If you write something new with new database tables, just try to use this Jakarta Data for those specific situations and services.</p><p><em><strong>8. How can architects best leverage these new tools&#8212;like repository-style data access and virtual threads&#8212;when migrating monolithic applications to cloud-native microservices, and what common pitfalls should they watch out for during such transitions?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> Yeah&#8212;for example, about the first part, the modernization: it&#8217;s clear the enterprise is not dying. We are evolving everything. That&#8217;s the first part because, obviously, a lot of people love these phrases to put in a blog. But now, I think the enterprise is very clear&#8212;it&#8217;s evolving.</p><p>And the idea is, obviously, we want to create good tools for everyone, and we want to create good code for legacy, because at the end you will keep using it.</p><p>And about microservices and monoliths&#8212;obviously, you will find a lot of discussions on the internet and Reddit and blogs and YouTube channels, and everything about what is better for your project or your company. The thing is: the problem is when you are just reading without the context, and you need to understand each context is different. I think that is the first part.</p><p>The second is: microservices, in my experience, is not the goal, OK? It&#8217;s the consequence for that. Because when you always see microservices as the goal for each software developer or architect, probably that&#8217;s the issue&#8212;that&#8217;s the problem. Because if you don&#8217;t have developers with experience, and you don&#8217;t have a good architecture definition, you will have microservices with the same problems as monoliths. That&#8217;s the thing.</p><p>The other part is: the monolith&#8212;in these cases, people are looking back sometimes, in some cases, at monoliths as old code. And obviously, that&#8217;s a lie, because you can use monoliths without problems for huge systems, and you can save the complexity of microservices. You can use monoliths to scale. Actually, you don&#8217;t have to always use microservices to scale. It&#8217;s easier to understand and to maintain this because you have less complexity in your code.</p><p>So the question to use&#8212;&#8220;Hey, do I need to use microservices, yes or no?&#8221;&#8212;for me, I always try to answer a question before, or maybe two questions<strong>: Are we prepared for paying the real cost to use microservices&#8212;not just the money?</strong> (and) <strong>are we prepared to use this architecture or not?</strong> Because if you are doing this before you have the knowledge for that, probably you will have a huge problem. Because it&#8217;s not too easy to change your architecture, change your definitions, your business rules, and everything&#8212;and then you need to roll back to monolith because you are not prepared.</p><p>That&#8217;s my concern, obviously, when I work in teams and they are just focusing on changing to microservices because they read it in a blog. OK&#8212;that&#8217;s my recommendation, always.</p><p><em><strong>9. What criteria or signals help you decide if a modular monolith might be a better choice for long-term maintainability, and how do you manage the trade-offs for scalability and team ownership?</strong></em></p><p><strong>Ron Veen:</strong> Well, this is really kind of a subject very close to my heart because I guess we&#8217;ve all been through this wave&#8212;there was this FAANG group: Facebook, Apple, Amazon, Netflix, Google&#8212;and they all came with microservices, right? So we thought, &#8220;This is the way we need to go. This is the wagon we need to jump on.&#8221; And I think a lot of us did. And like Jos&#233; said, I think we also found maybe it&#8217;s not the right choice, because you should ask yourself: if you have 200 employees, are microservices&#8212;for your business&#8212;the right solution? Can you actually afford it?</p><p>Because <strong>microservices, on the DevOps side, require a lot more overhead</strong>. There&#8217;s a lot more monitoring involved. You need to watch these services, you need to see what is coming out, so there are a lot of metrics needed to keep them running in production. You need to make sure they&#8217;re still running in production, because before you had just one monolithic application.</p><p>And another thing is that <strong>monoliths have become like a negative name</strong>, right? Like dinosaurs almost&#8212;which it isn&#8217;t. I mean, it&#8217;s still brilliantly functioning code running on application servers that have been there for a very long time. So there&#8217;s nothing bad about them. They&#8217;re just being opposed to being &#8220;old&#8221; compared to microservices.</p><p>So you have one running, and that&#8217;s easy to monitor, it&#8217;s easy to do logging, it&#8217;s easy to do debugging as a developer&#8212;you know, you just do it remotely. But when you&#8217;re switching to a microservices platform, you&#8217;ve got dozens or hundreds of services running. You need to make sure each of them is still running. And if you trace a problem, you need to somehow combine all the logs so that you can actually go through the logging and try to find out what happened. So there&#8217;s a lot of work there trying to debug a problem with microservices&#8212;they&#8217;ll keep you busy for a bit. So there are a lot of things there that are happening, I think, where you have to be really careful.</p><p>And yes, like Jos&#233; said, it should never be a technology choice, but it should really be a choice based on what we need. From my experience, what I have learned is: I would start with what I would call modular monoliths. The classical monolith is where all the modules are intertwined without very clear boundaries. If you have a modular monolith, basically what it would mean is you still have your modules, but they can&#8217;t interact directly with one another. There&#8217;s a predefined API somewhere, and one module is only allowed to access another module via this predefined API, which really makes the code less cluttered and far less risky to change&#8212;so things will break less.</p><p>Because if I would go find an API, then the API will change and I will see it&#8212;or the API won&#8217;t change, but I can change internal methods without something breaking there. So my suggestion would always be: start with this modular monolith, be very clear on your boundaries, force that we go through APIs, and then just monitor the system&#8212;watch it.</p><p>And if there are typical situations&#8212;like if you find there&#8217;s just one module, let&#8217;s say a customer module, that requires a lot of changes, which means a lot of redeployment&#8212;then that might be a case to say, &#8220;Well, maybe I should factor this out, make it a microservice,&#8221; and combine the two. There&#8217;s nothing against combining microservices with a modular monolith. Or maybe there&#8217;s different resource utilization, different scaling requirements&#8212;well, that could be another case where you say, &#8220;Okay, if this is really the case, maybe I should factor it out.&#8221;</p><p>But even then, I would say: look at how much the costs are, and only when the cost of keeping it in starts to outweigh the cost of switching to microservices&#8212;that would be the moment to make this choice. But again, microservices require a different mindset. You&#8217;re also getting the distributed tax that you&#8217;re paying, and that can be really, really expensive, and you should be really careful. But I guess then we&#8217;ll get into the realm of domain-driven design and bounded context, which might be a bit too much for now.</p><p><em><strong>10. It has been said that introducing Kotlin into an established Java codebase isn&#8217;t just a technical change but a cultural one. Key advice from Kotlin adoption experts is to &#8220;win the hearts and minds&#8221; of skeptical Java developers&#8212;not only through hard facts (letting the improved code quality speak for itself) but also via soft factors like easier onboarding and community support. Showing Kotlin&#8217;s concrete benefits&#8212;for example, its focus on safety and conciseness that addresses fundamental Java shortcomings while staying 100% interoperable&#8212;can help gain buy-in. Drawing from your teaching and leadership experience, what strategies do you recommend to gradually introduce Kotlin in a Java-centric organization?</strong></em></p><p><strong>Ron Veen:</strong> Yeah, I think what you always find if you introduce something like Kotlin into an organization, you should be very aware that there will always be some gurus in the company who are very focused on Java, who know the inner details, and who might actually see bringing in a new language as a threat to their supremacy. So I think the most important thing is to get them on board, because in general I would expect that 60&#8211;70% of developers would be really interested and say, &#8220;Oh yeah, we&#8217;re going along with this.&#8221; So maybe it is not direct.</p><p>What we sometimes did is we had a team with some enthusiasts and some critical people, and we let them develop something new. It could either be&#8212;if we&#8217;re doing microservices&#8212;I mean, microservices are brilliant for this, right? Because you can choose whatever technology you want for your new service, so Kotlin would be a great choice there. So that would be a good thing: have this team of skeptics and enthusiasts and then see how they work together on the problem. Because if you just do the one or the other, we&#8217;re not really getting everyone on board.</p><p>That would be a really good approach, I think. But I sometimes also have done&#8212;you have this thing called Advent of Code, right? This is, right now at this time, the period leading up to Christmas, and there are new coding challenges. You could say with your team, &#8220;You know what? We&#8217;re going to do Advent of Code, and this year we&#8217;re going to do it in Kotlin,&#8221; and try to see how we would work it out there. So that could be a thing.</p><p>Of course, you can do things with hackathons in your own team and say, &#8220;We&#8217;re doing these coding sessions, and let&#8217;s try to explore how we could use Kotlin there.&#8221; And finally, I think if you would do code reviews with the whole team, you could also go through Kotlin and gradually explain: &#8220;OK, what are we doing here, and why are we doing it this way in Kotlin?&#8221; That makes people really see, &#8220;OK, I&#8217;ve done it the Java way,&#8221; and I think that really plays&#8212;for instance with collection classes&#8212;because Kotlin, that&#8217;s such a great collection of, well, collection letters, and then people in the code&#8212;with you, the skeptics&#8212;can see, &#8220;Oh wow, this is actually quite nice,&#8221; and very concise how we&#8217;re writing the code.</p><p>Enforcing it upon someone, I don&#8217;t think it&#8217;s ever going to work. At least they&#8217;ll probably do it, but they won&#8217;t do it by heart. So I think, in general, with these steps you can actually win their hearts.</p><p><em><strong>11. How can engineering leads support their teams&#8217; upskilling and address any resistance, ensuring a smooth transition without disrupting productivity?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> I think maybe the good news or the bad news&#8212;it depends&#8212;but it&#8217;s more about a leadership issue in this case. Because I think we have a beginning that&#8217;s very clear: we need people saying the next steps for that. Because if you don&#8217;t have this follow-up about leadership, probably you will see a mess in some cases.</p><p>That&#8217;s good news because if you have the person in your team, obviously it&#8217;s an easy way to do that. But if you don&#8217;t have it, you need to create it or you need to hire these people, because that&#8217;s the thing. Maybe you have people with good leadership, but without the knowledge for the adoption.</p><p>Because, for example, the most common errors when you are trying to do that: you need to start with small limits. If you&#8217;re trying to migrate everything, or the most complex in your legacy code, probably it&#8217;s not a good idea. So my recommendation, for example, in some companies when they ask for something similar is: start with the small models. Because you need to see&#8212;if you think it is good writing code, that&#8217;s correct because they are working with you&#8212;but they really understand the business. Because maybe you will see they know the new business, not the old business, for example, because you are using old code sometimes.</p><p>The other part is: it&#8217;s a new language, it&#8217;s a new paradigm, it&#8217;s new code. At the end you need to see if you have a problem with the adoption because sometimes people understand the features, and it is not the real benefit. They understand the modern things, or they understand the faster way to do the things, but it&#8217;s not the same as the best way to do that. So don&#8217;t be closed for these situations, because obviously the most important is: OK, we will do the migration. I am not saying this is the best way to do the things because probably the team can find a best way to do the things, because they really put the hands on the code. But if you are very closed with that and you say to the team, &#8220;Just follow what I am doing,&#8221; probably you will miss a good part of the code from the team.</p><p>So that&#8217;s the other part: just go talk. OK, it sounds weird, but when you are using the team for the migration, probably they are the best way to understand the situation. Because you can&#8217;t imagine how the thing is working, but when you are rewriting the code you will see the real things. Maybe you have good code and you don&#8217;t need to rewrite this part. Maybe you have to rewrite another model, not this one, because you will see the benefit&#8212;and maybe the benefit is not too good to change it. Maybe the benefit is in another model. Sometimes that happens.</p><p>Because the problem is: if you think the leadership&#8212;or just one person&#8212;knows everything about the project, probably you are not with the correct answer. Because people need to put their hands on the code for that.</p><p>And just to close that: you need to think in your team. In these cases, you need to trust in that team to take some decisions. Obviously they need to explain to you or explain to the company, but probably they are the best way to understand the problems in those cases.</p><p>And you need to give space to the teams to play with new toys because probably they have the answers, but they don&#8217;t know yet. So if you let them play with some new features or some new code, probably you will find good things in that.</p><p>And obviously don&#8217;t try to read productivity in guidelines&#8212;if you try to read it as: &#8220;If you have more lines of code or less lines of code,&#8221; probably you will miss something. So in this case my recommendation is: we need to avoid productivity as &#8220;more lines of code or less.&#8221; Just keep trusting in the team and decide together if it&#8217;s a good part for the business&#8212;if we need to change something or not. But if you don&#8217;t open the code and read it and try to play with that, you don&#8217;t know if the adoption is a good idea or not.</p><h1>Mobile Development and Cross-Platform</h1><p><em><strong>12. The Android development landscape is in constant flux. As of 2025, we&#8217;ve seen the rise of Jetpack Compose for declarative UI, more sophisticated modularization of apps, and renewed emphasis on clean architecture principles for maintainability. Based on your experience building hundreds of mobile apps, what do you consider the three most important architectural practices or patterns for modern Android development?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> I think the last two years we really see why they think to start using Kotlin. That&#8217;s the thing, because for Jetpack Compose, I think that&#8217;s the main concern.</p><p>And if I need to say three keys for that: I think we need to separate the responsibilities. Even if you are not using it for Android, I think if you can separate your responsibilities in the code, it is always a benefit for everyone&#8212;for testers, for developers, for product owners, for everyone&#8212;because it&#8217;s very easy to understand each part for everyone.</p><p>Trying to use the definition of modules&#8212;not because it&#8217;s a new way to do that. Maybe that sounds like, &#8220;We need to separate everything,&#8221; but no. The thing is: if you modularize everything in your project, it&#8217;s very easy to add these features in this module as a feature, or delete, or change this module.</p><p>For emergencies, for example: maybe you have a critical bug in some part of the code, but if you have modules you can just put it in another part and you can continue with the regular functionality.</p><p>So I think that&#8217;s the second. And the third one: I think the architecture is changing. You need to prepare your architecture ready for change, because when we create code, we think the code will always be the same code&#8212;that&#8217;s the problem.</p><p>It is very complex. People try to create architectures ready for new changes, because we don&#8217;t have the time, we don&#8217;t have the money, we don&#8217;t have the team for that, but I think great developers and great leadership are always preparing the project for changes&#8212;not just for the new framework.</p><p>Because if you prepare your project just for the new framework, when we change the paradigm&#8212;in this case, for Jetpack Compose, because it&#8217;s a really different way to do mobile applications&#8212;probably all your code is not ready for these changes. That&#8217;s the deal, and that&#8217;s the problem. So now you will see a lot of mobile applications where the new features are working with Jetpack Compose, but the old ones probably will never migrate because they are not ready. They don&#8217;t need to keep waiting a miracle to change the modules just for the new Jetpack Compose implementation, for example.</p><p>So that&#8217;s my three keys. And in my context, in my current projects or last projects, I always ask, for example, when I will implement a new technology: what kind of problems will we resolve with that? The cost of the adoption, and the impact for the long term.</p><p>That&#8217;s my three main variables I have in mind, and I always try to put a number: how many problems I will solve, the cost of the adoption&#8212;how many people and how many resources we need&#8212;and the impact we will have: long term, mid term, or short term. I think with these three variables I create a table and share it with the team, and after that I have a very clear situation. We decide if we need to follow these new technologies. That&#8217;s what I did in the past.</p><p><em><strong>13. Jos&#233;, with your background in Android, iOS, and cross-platform frameworks like Flutter, how do you see Kotlin Multiplatform fitting into real-world projects today?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> I think we are in a very interesting situation in mobile development right now because we have a lot of actors&#8212;maybe serious actors&#8212;because in the past we had a lot of actors. But the problem is, I am not saying I am not a fan of JavaScript, but the problem for development in the beginning for multiplatform is they had a lot of JavaScript frameworks, and at the end it&#8217;s not the same case because it&#8217;s not a real multiplatform situation.</p><p>But now we have a lot of multiplatform situations. We have Flutter, we have SwiftUI, we have Android with Kotlin Multiplatform. So you are really creating a codebase for using in the other option of mobile development without having to add some patches for the situations. So it&#8217;s a real competition for who is the best option.</p><p>Now, we don&#8217;t have a clear winner for that because that depends for each team. For example, if you have an iOS team and you have five developers using iOS and Swift, why do they need to learn Kotlin? Maybe it&#8217;s not the best case for that. Maybe you have the Swift situation. That&#8217;s the real thing because you will need to pay&#8212;you will need to use money&#8212;for this migration at the end.</p><p>But now we are in the most easy way to create multiplatform applications, even for mobile developers, even for Android developers or iOS developers. So we have a real situation where we can separate the logic from the UI without problems. With Kotlin Multiplatform, it&#8217;s very clear to do that. You don&#8217;t need to be expert on Android or Java or Kotlin. If you read the code, you will understand in a very easy way&#8212;even because, when we write the code now, you will see a lot of web programming paradigms.</p><p>For example, the reactive way to do websites with Vue or React&#8212;you will see, when you read the code in mobile development, it&#8217;s very, very, very easy to understand. You will see Flutter is OK, React Native is OK, OK, but at the end Kotlin is part of the body of mobile development&#8212;and safe in this case too.</p><p>So we need just to wait&#8212;maybe a couple of months, or maybe a couple of years&#8212;to see the real impact of Kotlin Multiplatform. The way to understand Kotlin Multiplatform is very easy if you understand Kotlin. You don&#8217;t need to understand too much. Probably you will need to take two or three features because maybe these features are not too famous in backend development, for example, or in the Java world. But it&#8217;s two or three, not too much, because at the end you are using your regular Kotlin in backend&#8212;you can just use it in mobile development. The problem is you need to understand how the operation system is working. That&#8217;s the only thing.</p><p>The tooling is very advanced. That&#8217;s the other part&#8212;you will see Kotlin Multiplatform is running too fast for these changes. They are creating and sharing tools each three months, four months for the people, the ecosystems&#8212;and Kotlin too. So it&#8217;s very, very good news for the developers because you will see two or three really good maturity tools each four months.</p><p>So I think that&#8217;s the advantage to use Kotlin Multiplatform for mobile development versus Flutter or React, even if they have more years, because now they are in other stage. They are trying to increase the developers in the ecosystem of Flutter or React, but Kotlin&#8212;they don&#8217;t need to do that because they passed this stage. Because if you are using Java, you can use Kotlin. If you are a Kotlin developer, you can use Kotlin Multiplatform. I don&#8217;t need to convince you to use it because I don&#8217;t have the problem with the knowledge. You have these tools&#8212;just why is not the problem?</p><p>The question is: Why are you not creating mobile applications with Kotlin Multiplatform? That&#8217;s the real thing because you have the knowledge, you have Kotlin, you have Java knowledge. Maybe you just need to take a look at the operation system. I think that&#8217;s the real situation for Kotlin Multiplatform. They are just waiting for more people in the ecosystem because if you know Kotlin, you can do that.</p><p><em><strong>14.  We&#8217;ve been talking about Jetpack Compose, we&#8217;ve been talking about Kotlin Multiplatform, which are, you could say, new technologies or new approaches, and we talked about them in the context of adapting them into large-scale apps. But when it comes to a large-scale enterprise setting, what challenges should teams be aware of when adopting a new technology or approach while they aim to access benefits such as maximizing code reuse without compromising native user experience, and so on? What are some best practices or difficulties that teams should be aware of when trying to bring something into the stack, so to say?</strong></em></p><p><strong>Ron Veen:</strong> Yeah, that is an interesting question because I guess one of the core reasons also for Kotlin Multiplatform to be there is code reuse&#8212;specifically, if you have business logic, that&#8217;s really the code you don&#8217;t want to have distributed over different platforms, but you would like to have centralized.</p><p>So I think everything goes down to that, and that you should really focus on which parts you really think you should share. Because if, again, you look at code reuse, and especially in enterprise environments&#8212;reusing parts&#8212;it depends on your architecture, doesn&#8217;t it?</p><p>Because if you have this classical architecture where you have one large single deployable unit, then code reuse could be quite easy. If you get to the microservices situation, well then code reuse becomes a little more tricky, I think. And then, you know, the whole DRY thing&#8212;&#8220;don&#8217;t repeat yourself&#8221;&#8212;suddenly becomes a bit more fluid, that you could say, well, sometimes we just don&#8217;t want to reuse in order to maintain the independence that microservices should have.</p><p>So, again, here I think it all depends upon what is the architecture you&#8217;re trying to support. So I think there&#8217;s no one-fits-all solution here&#8212;so, no.</p><h1>About the Book: <em>Kotlin for Java Developers</em></h1><p><em><strong>15. Your book, &#8220;Kotlin for Java Developers&#8221; is aimed at software developers proficient in Java who want to learn Kotlin for professional development &#8211; it&#8217;s especially relevant to Android engineers, JVM backend developers, and full-stack Java programmers maintaining legacy systems. What inspired you to write this book together?</strong></em></p><p><strong>Ron Veen:</strong> Well, the good thing is&#8212;Jose actually&#8212;the bulk of the work was already there, right? So I actually came later to the project, and the majority of this book&#8212;and the credits for the book&#8212;should really go to him, because he had already written a large part. I think I only added a few chapters.</p><p>But I think I came to the project for a simple reason: anyone who writes code&#8212;if you have, like, 10 developers and you say, &#8220;Write this piece of code for me,&#8221; then you&#8217;ll get 10 different solutions in the end. It could be typical small things or something, but everyone has their own style. You can always see it with code reviews: &#8220;Shouldn&#8217;t we do it like this? Why not like that?&#8221; And I think it was the same here with the book.</p><p>I think Jose had already written the majority of the book, and&#8212;just like with code&#8212;it&#8217;s always good to have a second opinion about it, and that&#8217;s basically where I came in. We started chatting with one another and talking about, &#8220;OK, should we rewrite it like this? Is this better?&#8221; Just fine-tuning a lot of things and having two perspectives on the book.</p><p>So that&#8217;s how I got to it&#8212;or actually what I did on the book. How I got to it was easy: I was approached by Packt. So I said, &#8220;We have this book about Kotlin.&#8221; I thought, &#8220;Yeah, Kotlin&#8212;that&#8217;s great.&#8221; That really touches me. I think it&#8217;s a great technology and should be used more often. So yeah&#8212;if there&#8217;s a way that we can spread the good news, I would really love to do that.</p><p>Then I got to see all the work that Jose had already done, and I thought, &#8220;Well, this is just brilliant,&#8221; and yes, I&#8217;d love to be a part of this. I just hope that Java developers really see it.</p><p>What I really liked about the approach to this book was it&#8217;s not like I&#8217;m telling you Kotlin from A&#8211;Z. I really love that we have this approach where we say, &#8220;Wait a minute&#8212;you&#8217;re already a Java developer. I don&#8217;t need to teach you what loops are, or iterations, or, in general, what functions are, or lambdas. You already know that. I&#8217;m just going to teach you how you can make the transition to Kotlin.&#8221; That&#8217;s why I think there&#8217;s a lot of information in there for Java developers in a very concise way, so it&#8217;ll save them time if they use the book to switch.</p><p><em><strong>16.</strong> <strong>Jos&#233;,</strong> <strong>What was your inspiration? What specific gaps or common struggles did you observe among Java developers that made you think, &#8220;I need to write this book, and I need to help them&#8221;?</strong></em></p><p><strong>Jos&#233; Dimas Luj&#225;n Castillo:</strong> Well, I need to say something at the beginning, because Ron said I started the book early&#8212;but yeah, that&#8217;s true. But actually, Ron&#8217;s part was very important for the project. I was stuck on a couple things, and he read it and he made the right suggestions. I think that was the point to try to move forward, because obviously we have a lot of complex situations to understand.</p><p>I wrote a lot of books in the past, but this is the second in English, because it&#8217;s not my main language. Obviously, that helps a lot, I think&#8212;maybe not for him because he&#8217;s very good with English, but for me, because I think that helped a lot for that.</p><p>And about the coding: when I start thinking about how I need to solve&#8212;or I want to explain&#8212;the things I know&#8230; I am a teacher too. I was a teacher for 18 years, maybe. And I note, for example, when I try to tell people&#8212;like at the beginning, 18 years ago&#8212;it&#8217;s very easy. It&#8217;s not the same case when you have developers with previous experience.</p><p>So I take that in my mind, because I know a lot of people want to use Kotlin because they are coming from Java&#8212;that&#8217;s the part. It&#8217;s a different way when you are starting from zero or scratch, but when you have developers with experience, it&#8217;s a very different way to do that. So I take that in mind while I was writing the book.</p><p>I was thinking too much that the other part is: I need to take time to explain the syntax. But at the end, the problem is the way we try to focus the situation. For example, am I trying to explain how we can write this line of code in Kotlin, or maybe we need to&#8230; I think we really need this line of code, because maybe in Kotlin we don&#8217;t need it.</p><p>That&#8217;s what I always try to put in the reader&#8217;s mind: if we need it, OK&#8212;this is the way to do that. But the question is: do we really need this line? Because maybe in Kotlin we have another way to do that, and maybe we don&#8217;t need to use it. That&#8217;s my second question always when I try to explain something, because that&#8217;s the real way to create the bridge for Java developers to Kotlin.</p><p>OK, you will need&#8212;maybe you will need that knowledge more; maybe it&#8217;s better if you don&#8217;t need it. But you know why you have the answer. Or maybe you need to find the answer when you are writing the code. That&#8217;s the other part.</p><p>The other question&#8212;or comment&#8212;I always have in my mind is: we need to be respectful with Java. Since the beginning with Kotlin, when we try to sell the idea to use Kotlin, I don&#8217;t like too much, actually. I have a very weird experience in the past when I was a Google Developer Expert and I was the third Kotlin Developer Expert, and the problem is: they removed me because I&#8217;m not always saying the best option is Kotlin for the developer.</p><p>And I really think that, in some cases, maybe you have situations when you don&#8217;t see a very specific answer or a good answer for that&#8212;but it is the real part. We need to be respectful with Java because, at the beginning, without the experience of Java, we can&#8217;t create Kotlin. Not me&#8212;the Kotlin team&#8212;because obviously they use a lot of experience in Java, all of them. They are very experienced people with Java, and they try to see good parts of Java and take it into Kotlin, and they are increasing good parts.</p><p>So if we sell Kotlin as a killer&#8212;I think that&#8217;s disrespectful for the technology, because it&#8217;s not. The way to do that sometimes is actually a compliment for the technologies, the architecture, the projects. But the other part is: more than sometimes it&#8217;s just your preference&#8212;how you prefer to write the code in this way or this one&#8212;because maybe you don&#8217;t have very clear architecture or this paradigm to use it.</p><p>So that&#8217;s the other part. I always, in my way to express it, when you need to compare Java with Kotlin, I always try to be very respectful with that because a lot of the good things in Kotlin&#8212;maybe 90% of them&#8212;they are coming from Java, because Java is the first part of your experience. So I always try to keep that in mind when I write the book and put the examples. Obviously, we have a lot of parts better in this case, or very fast implementation&#8212;I need to say it, obviously&#8212;but with the correct words and the correct approach for the people. For me, that&#8217;s my line to follow to write the book.</p><p>And obviously, I want a couple parts that are really practical. In these cases, I use very simple scenarios and add complexity in the code. I start, maybe, for example, with just one definition, but at the end of the chapter you will see it takes more functions, because that&#8217;s the way to follow that. I don&#8217;t prefer to do anything by default because when I read books in the past&#8212;</p><p>You sometimes see magic code just appear on the next page. So I don&#8217;t like too much to say, &#8220;Hey, we have new code&#8212;just read it and move forward,&#8221; because I don&#8217;t think that&#8217;s the way. Obviously, in other kinds of books, on other kinds of technologies, maybe you need to do that because it&#8217;s a framework and needs to be&#8212;and you can copy the template because it&#8217;s too&#8230; but even in the necessary parts, I try to do that for the book. That&#8217;s my&#8212;maybe five or three points to follow when I write the book.</p><p><em><strong>17.  What do you hope experienced Java professionals will do differently after reading Kotlin for Java Developers, and how do you think this will help them tackle real-world challenges more effectively?</strong></em></p><p><strong>Ron Veen:</strong> Well, I think, again, like we said, this is really trying to explain to Java developers that Kotlin isn&#8217;t that different, because, again, 80% of your logic and things will still be the same. Yes, you will use a bit different syntax, but it&#8217;s still a syntax that&#8217;s quite familiar to you. You might use different collection functions, because there are different ones.</p><p>But I think the really important part here is that you&#8217;re actually starting to see the value, but you&#8217;re also starting to recognize when it doesn&#8217;t really matter if we do Java or if we do Kotlin&#8212;they&#8217;re both running on this brilliant thing, which is the JVM, right? The Java Virtual Machine that, in the end, runs the code.</p><p>So it is not as big a step as you might think it is when you move from Java to Kotlin&#8212;as opposed to moving from Java to Go, or from Java to Rust. The steps are really small, and I think our book just helps you write idiomatic Kotlin, where you can actually see, &#8220;Oh, right&#8212;I&#8217;m actually seeing what I should do.&#8221; And like Jos&#233; said, it&#8217;s not like translating it one to one.</p><p>Actually, I think JetBrains, in the IDE, they have the function where you can select a Java class and say, &#8220;Convert to Kotlin.&#8221; Well, it would technically work, but it still wouldn&#8217;t be idiomatic Kotlin, right? So I really hope that, with the book in hand, you would actually write Kotlin as Kotlin is meant to be&#8212;so I really hope that&#8217;s where we&#8217;re getting.</p><div><hr></div><p>If you want to dig deeper into the mechanics of moving from Java to Kotlin&#8212;writing <strong>idiomatic Kotlin</strong>, handling <strong>null safety</strong>, using <strong>coroutines</strong> for concurrency, and taking advantage of features like <strong>extension functions</strong> and <strong>DSLs</strong>&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836">Kotlin for Java Developers</a></strong></em> by <strong>Jos&#233; Dimas Luj&#225;n Castillo</strong> and <strong>Ron Veen</strong> (Packt, Oct 2025). Written for experienced Java developers, it teaches Kotlin by mapping concepts directly to familiar Java constructs and then goes further into interoperability, generics, data and sealed classes, coroutines and flows, and DSL design&#8212;across backend, Android, and cross-platform development.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BDJC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775" width="336" height="414.46153846153845" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/abe245f2-991b-46bf-905b-bff991dc8e14_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:336,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Kotlin for Java Developers&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/kotlin-for-java-developers-9781835884836&quot;,&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="Kotlin for Java Developers" title="Kotlin for Java Developers" srcset="https://substackcdn.com/image/fetch/$s_!BDJC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!BDJC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fabe245f2-991b-46bf-905b-bff991dc8e14_2250x2775 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></p><p></p>]]></content:encoded></item><item><title><![CDATA[The C++ Programmer’s Mindset on Abstraction Costs, “Future You,” and Thinking with the Machine: A Conversation with Sam Morley]]></title><description><![CDATA[From STL leverage and modular design to cache-aware performance, concurrency pitfalls, and memory-safety habits in modern C++]]></description><link>https://deepengineering.net/p/the-c-programmers-mindset-on-abstraction</link><guid isPermaLink="false">https://deepengineering.net/p/the-c-programmers-mindset-on-abstraction</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 22 Jan 2026 05:13:13 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/TRii5U87yn8" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>C++ rewards engineers who treat problem-solving as a deliberate process rather than an improvisation.</strong> In this conversation, Sam Morley returns repeatedly to that theme: decompose the work until it becomes a set of solvable, &#8220;atomic&#8221; parts, then choose abstractions that fit the real constraints of the system. He argues that abstractions are never free, even when runtime overhead is low, and that good design means balancing competing costs: build time, cognitive load, flexibility, and performance. That same pragmatism shows up in his emphasis on leaning on the standard library, iterating from &#8220;working&#8221; to &#8220;fast&#8221; based on measurement, and understanding when low-level details like cache behavior and memory access patterns should influence how you structure code.</p><p>Morley, author of <em><strong><a href="https://www.packtpub.com/en-us/product/the-c-programmers-mindset-9781835888438">The C++ Programmer&#8217;s Mindset</a></strong></em> and a research engineer with a background in mathematics who maintains a high-performance C++/Python library for data science, also frames maintainability as a problem of empathy for &#8220;future you.&#8221; He discusses writing code that can be understood months later, structuring systems with clear separation of concerns, and treating concurrency and memory safety as design problems rather than afterthoughts. Along the way, he outlines practical guidance on thread-safe architectures, where synchronization mechanisms go wrong, and how ideas from Rust&#8217;s ownership model can sharpen a C++ engineer&#8217;s instincts about lifetimes, pointer safety, and undefined behavior. </p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-TRii5U87yn8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;TRii5U87yn8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/TRii5U87yn8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1: For an experienced engineer, what does adopting the C++ programmer&#8217;s mindset look like in practice? How does it change the way you approach complex software challenges?</strong></em></p><p><strong>Sam Morley:</strong> For experienced engineers&#8212;and probably some less experienced engineers as well&#8212;they&#8217;re probably using this framework of computational thinking already. The framework itself, as I came to discover when I was putting this together, is really a set of common elements that one finds you do when you solve problems. It&#8217;s less about the actual components of the framework and more about how one connects with the different mechanisms and features and facilities within and around the C++ language that make this an interesting discussion topic.</p><p>So, one might be quite experienced at solving software problems, but what we&#8217;re doing here is more about connecting those with a broader thinking about the system and the language&#8212;and all of the facilities around those&#8212;which is hopefully the additional knowledge that I&#8217;m imbuing. As I said, most people are already kind of familiar with this sort of framework, even if they&#8217;re not conscious of that fact.</p><p>One of the things I want people to take away is that it&#8217;s sometimes very helpful to really think about your process. So when you do solve a problem, look back and think: How did I do this? How did I break this problem up? What abstractions did I find? Where did I find them? Where were the common elements&#8212;things that I&#8217;d seen before? What were the things that I&#8217;ve never seen before? Try and make a mental note of those facts, because these are the things that will come up again. From a longevity point of view, it&#8217;s important to not only remember your solution, as it were, but also your process.</p><p>Because if you fall down the same hole every time, then it becomes quite easy to fall down it again if you don&#8217;t remember that there was a hole there. So if you document your process and think about your process&#8212;at least maybe not physically document, but mentally document the process that you go through to solve a problem&#8212;then you can remember these facts much better.</p><p>Moreover, if you&#8217;re experienced, then you should also be mentoring your more junior people, and this is also helpful for them. So it&#8217;s important, for lots of reasons, to think about what you&#8217;re doing and how you&#8217;re doing it.</p><p>And that&#8217;s one aspect of the book. The other is connecting it with the C++ language, the broader system in which it operates, and how you marry those two things together to make an overall, hopefully better, more efficient, and faster solution.</p><p><em><strong>2: In your book you talk about breaking down challenges, and choosing the right abstractions to build the most efficient solutions. Can you walk us through a concrete example where this approach made a big difference?</strong></em> </p><p><strong>Sam Morley:</strong> I want to start by challenging this a little bit. The notion that one can solve a problem without breaking it down into smaller parts is kind of folly. I don&#8217;t think it&#8217;s possible, really. You might not be conscious of the fact that you&#8217;ve broken it down into smaller parts, but you&#8217;re almost surely doing it. Even something as simple as doing some arithmetic&#8212;you might think that you&#8217;re just adding N numbers together&#8212;but really what you&#8217;re doing is you&#8217;re adding two numbers together, and then adding the result of that to the next one, and then adding the result of that to the next one. It&#8217;s an expanding-brackets problem. Whether you are conscious of this fact or not, the point is that you&#8217;re solving several smaller problems that look like the same big problem.</p><p>However, the way that you break down problems obviously matters, and some ways are more efficient than others. For that reason, it is a good thing to get into.</p><p>I want to talk about something that I did a few years ago now, which involved taking frames out of a very large number of video files, sending them to one of the ML services on Azure&#8212;so this was over a REST interface to Azure&#8212;and then we got the results back, and we had to store those on disk in files. This is a very meaty topic, meaty problem. There&#8217;s lots of different elements here: there&#8217;s loading all the files and then decomposing them into individual frames; then there&#8217;s sending all of those frames off to the Azure service; there&#8217;s getting the results back; and then there&#8217;s writing them to disk. So immediately there are four components to think about.</p><p>In trying to process this&#8212;once you&#8217;d expanded this to 100,000 videos or something, each with a few hundred frames&#8212;the numbers here are pretty enormous. In order to get them to and from the Azure service in a meaningful amount of time, we had to multiplex this. So we had multiple threads all sending frames to multiple Azure endpoints, because each of the endpoints is rate-limited, so you can only send, I don&#8217;t know, 10 requests a second or something to each of the things.</p><p>But actually, sending requests was not the bottleneck. Getting the results back was part of the bottleneck. The biggest problem that we actually encountered was writing the results back to the disk, because this was hundreds of gigabytes of results at the end of the day. What we ended up with was that we were getting results back from the service so fast that we had to build in some back pressure into this system to slow down when we had a backlog of things to write to the relatively slow spinning-rust disks that we had.</p><p>So there we have a very interesting structure. We start off with these four big components. Within the first component, we have reading video files from the disk where they were resident, decomposing them into a number of frames that was then passed on to another subsystem, which was responsible for sending these frame things up to the Azure service and waiting for the results. This was quite carefully orchestrated so that we didn&#8217;t hit the rate limit&#8212;so figuring out how to do that was an interesting problem.</p><p>Then there was another component which was taking the results that were being returned from the Azure service and collecting them into a buffer. This was the sub-problem of figuring out that we were thrashing the disks trying to write all these results out. So we installed a buffer in between. We wrote into a buffer and then had another worker process that would take the things from the buffer in big chunks and write them out to the disk.</p><p>So there was this sort of filtering down of problems. You start off with big, challenging, meaty problems at the top, and then each one of those gets decomposed into smaller bits, and then smaller bits still, until you reach a level where you either have an existing algorithm to do it, or it&#8217;s some functionality handled by a library, or it&#8217;s some other kind of interaction with the world. Talking to Azure, talking to a disk&#8212;these are sort of base-level problems that you can solve quickly.</p><p>It&#8217;s all about bringing down the level of the larger problem to these small, atomic things which you can actually solve using facilities that you have. That&#8217;s the real challenge. But I don&#8217;t think that you could just write a singular piece of software that would do all of these things together without breaking it down into these components. I don&#8217;t think that&#8217;s possible.</p><p><em><strong>3: Abstraction in Detail (Chapter 2 of your book) covers when to use different language features&#8212;simple functions, classes, templates, etc.&#8212;for a given task. How do you determine the appropriate level of abstraction in modern C++?</strong></em></p><p><strong>Sam Morley:</strong> Abstraction is tricky. It really depends on the purpose of the code. What am I trying to achieve with my code? I want to upfront say there are no zero-cost abstractions. People will claim up and down that things are zero-cost abstractions. They&#8217;re really not; every abstraction has a cost. Now, this might be a runtime cost, which is what people usually refer to, but that&#8217;s not the only type of cost.</p><p>Templates, for example, have very little runtime cost, but they do have a significant build-time cost. Including lots of templated code might make your runtime faster, but it will surely expand your build time. They also have a pretty heavy cognitive load&#8212;the ability of the programmer to reason about programs which are heavily templated is significantly higher than just ordinary plain C++ code with no templates.</p><p>So getting that balance right&#8212;what am I trying to achieve with this code? Is this supposed to be a set of components of high-performance systems that really need to have the best possible runtime performance, and I don&#8217;t care about build time? Or is this a general-purpose thing that needs to be extremely flexible, and I do care about the cognitive load of people who are going to work with this task? It&#8217;s all about balancing the different competing costs and also competing utilities. How flexible is my system? How fast is my system?</p><p>Now, it&#8217;s not necessarily true that abstractions are always bad. Sometimes you can use an abstraction and it adds very, very little to any of the loads. For example, introducing a very small templated helper function is very useful and it adds basically no overhead, and if that&#8217;s used correctly it can be a big help to the program.</p><p>But sometimes&#8212;and I&#8217;m especially guilty of this&#8212;you can over-abstract. I&#8217;m a mathematician; we like our abstractions. You can over-abstract and make the thing more complicated than it needs to be, and at this point you start to lose something. It might be runtime performance if this is a virtual class hierarchy, or it might be build-time performance if you have heavy template code. Or it could be that you no longer can reason about your software because it&#8217;s now so complicated and filled with all sorts of clever bit-hacking tools and abstraction mechanisms that you no longer understand. It&#8217;s finding a balance.</p><p>Now that I&#8217;m conscious of this fact, I try to keep my abstraction as minimal as possible. I look for the minimal amount of abstraction I need in order to solve the problem without going too far and over-generalizing it. This has come back to bite me recently: I over-specified an interface to the point where it only satisfied the conditions in one very specific instance, and I had to rework the entire interface to make it fit the actual thing that I should have programmed against in the first place. It can come back to bite you. Hopefully that doesn&#8217;t happen very often, but when it does, it is always painful.</p><p>This is why thinking about the abstraction up front is important, and it goes hand in hand with the way that you decompose your problems as well&#8212;the thinking about what the abstractions might be if you decomposed it in a particular way versus a different way. If you pick one or the other and it turns out not to be the right choice, then now you understand that the abstraction was made, maybe the problem&#8212;and the more problems that you solve, the more you get used to this idea.</p><p><em><strong>4: What guidelines help decide when a straightforward function is enough versus when you should introduce a class or a template to solve a problem?</strong></em></p><p><strong>Sam Morley:</strong> Yeah&#8212;if you start with a simple function and you make it a class template or you make it a function template, like I said, that can afford you a lot of flexibility at very low cost. I do this sometimes internally. For instance, I have a function which does something to a pair of integers, and I don&#8217;t know exactly what type of integer I want to use later on, so I just template it. Because the cost of doing this is basically nil, it means I don&#8217;t have to go back and refactor my code later when I change my mind about what integers I use. That kind of thing can be very low cost and high maintainability&#8212;high friendliness when it comes to programming.</p><p>The cost of moving from a simple function to a class is higher, especially if that class has virtual functions&#8212;if it&#8217;s abstract in the other sense of the word. If that&#8217;s the case, then you&#8217;re now incurring a runtime performance penalty, which may be warranted. Runtime performance penalties are not always a bad thing. As long as they&#8217;re away from hot code&#8212;the bits of code that need to run at maximum speed&#8212;you can get away with an awful lot of slop when it comes to runtime cost, especially in instances where the bandwidth and runtime latency is limited by some other factor, like a network connection or a disk or something like that.</p><p>But really there are three reasons&#8212;or at least three reasons&#8212;why you might want to use a class instead.</p><p>The first is that you have some kind of internal state that needs to be managed carefully. For example, a std::vector or std::map manages its internal storage, and if you were to code this by hand in line in a function, you would almost surely get something wrong. These are managing the state very carefully, and you then don&#8217;t need to worry about those details. Your code is much more readable if you&#8217;re using a std::vector than if you have a bunch of goto statements for resizing a buffer when it overflows and things. This is not very nice code to read.</p><p>The second reason to use a class is if you have some kind of behavior that needs to be flexibly abstract. What I mean by that is: you have an interface which reads and writes data from some source, but the source of the data is unknown. You might be reading from a disk or reading from a network socket, and this is a really great place to encapsulate the reading and writing process because it&#8217;s the outward interface. The bit that you&#8217;re really programming against is the same in both instances. You have a read function; you have a write function. It doesn&#8217;t really matter how that is implemented behind the scenes, as long as those two things work. Wrapping this in a nice class doesn&#8217;t have to be a virtual class; it can be a template, or something like that&#8212;some combination of both perhaps. It is a very convenient way of packaging the behaviors that are specific to one mechanism for doing that thing.</p><p>The third reason is that you need some point in which you&#8212;or some other developer later on&#8212;needs a point of customization. This is a slightly nuanced point. C++ templates are very powerful, but function templates are a little bit more tricky to use sometimes than class templates, and the reason for this is the way that class templates can be partially overloaded, partially specialized, whereas function templates can, but not directly in the same way. So it&#8217;s a really powerful technique to use a class template inside a function template that allows you to provide a different specialization that will customize the behavior of the function template without directly interacting with the code within. This is a very nuanced use, I contend, but it is very useful. I&#8217;ve used this pattern a few times in my code, and I&#8217;ve seen it in other code as well. I think the first time I saw this was in NVIDIA&#8217;s CUTLASS library, and I think I&#8217;d used it before that, but without being conscious of the fact that I was using this particular pattern. It is very useful, and I think it&#8217;s somewhat analogous to a sort of bridge or command interface that you might find in the Gang of Four, but with templates instead of virtual classes.</p><p>So those are my guidelines. If you have other uses, I&#8217;d be interested to hear what kind of reasons you would use a class rather than just sticking to a simple function.</p><p><em><strong>5: One key to proficient C++ is knowing the standard library. How important is it for developers to leverage the STL&#8217;s algorithms and containers instead of writing their own from scratch?</strong></em></p><p><strong>Sam Morley:</strong> OK, so there are two things about the STL which are really important to remember. First is that it is a set of very flexible and very generic algorithms and containers for a very wide range of purposes. And secondly&#8212;and probably more importantly&#8212;is that it is there always and you can always use it. &#8220;Always&#8221; being a little bit tricky there&#8212;embedded developers, please don&#8217;t get angry with me&#8212;but for most C++ developers the STL is a sort of thing that you can rely on and use.</p><p>Whenever you need it, and generally speaking, these are very, very good, very high-performance facilities, and they can make your life much easier. So what the STL does, in effect, is make your development window smaller: you spend less time implementing standard things and more time implementing the difficult things. They raise the floor of what is the base-level problem that you can solve without thinking about it.</p><p>If you remember when I was talking through my example earlier, you have these layers of problems. You start with big problems, you make them smaller, you make them smaller, and eventually you get down to a set of problems&#8212;maybe not at the same level everywhere&#8212;but you get down to problems which you know how to solve using standard tools or libraries. So what the STL does is it gives you one level up from having to write those things for yourself. It&#8217;s one less problem to solve, and this means you can move much faster. You can develop much faster.</p><p>Now, they might not give you the performance that you need. You might have to change the way that these work in order to get the performance that you need, but a large amount of the time the STL will probably give you all the performance you need, providing that you&#8217;re using the right algorithms and containers. OK, but that&#8217;s a separate question. The thing that it does do is speed up the development cycle.</p><p>If you implement something from scratch the first time and it doesn&#8217;t perform as well as you need, then fixing that might become problematic. And moreover, you might not know whether that actually is a bug source, or whether there&#8217;s some characteristic that you missed somewhere else in the problem, or whether this new thing that you&#8217;ve implemented is causing the problem. You can get around that with testing and things, but really, if you&#8217;re prototyping something, you might know that you can&#8217;t use those things in the future because they won&#8217;t perform well enough. But building it with the STL things at the beginning is the right way to get started, and it means that you can find a solution. It doesn&#8217;t have to be the best solution.</p><p>Solving problems is an iterative process. You don&#8217;t always find a solution&#8212;let alone the best solution&#8212;the first time round. You probably have to take many bites at the apple. So first you solve the problem, and then you make it fast. And only by measuring do you know which bits are not fast. So starting with the STL will probably get you most of the way, and you&#8217;ll probably find that other parts of your software are the slow parts.</p><p>Now, there are some caveats. First, a lot of libraries provide faster, or slightly more flexible, or things with different properties which are basically drop-in replacements for the STL. For example, Boost containers are a set of more expanded and more flexible container types that are drop-in replacements in most cases for STL equivalents. Abseil has the same set of things, and probably other libraries too. These are really great if you&#8217;re already working in, say, a project that&#8217;s using Abseil&#8212;you already have all of those container types at your fingertips&#8212;and sometimes they do perform better. And things like small inline vectors are extremely useful for a lot of things, and both of those libraries provide such a thing.</p><p>Now, the other side of that is the algorithms. Similarly, there are other libraries that provide standard STL-like algorithms. NVIDIA Thrust is one that comes to mind. This is parallel algorithms. C++&#8212;I think 20 or 23&#8212;introduced these different dispatches for the standard algorithms, which causes it to run multi-threaded or to do it on a particular execution context, I think they&#8217;re called. Thrust was sort of prior to that, and it&#8217;s specifically geared towards running on NVIDIA GPUs and NVIDIA libraries, but it&#8217;s the same set of functionality, actually. It&#8217;s a set of very general-purpose algorithm template functions which dispatch very cleverly through various pathways to give you a fast implementation of whatever that algorithm is doing on whatever device you&#8217;re doing it on. And it&#8217;s a very clean and efficient way of writing very parallelizable, very general-purpose code.</p><p>There is one more caveat that I want to mention, and that is that writing custom containers is a very dangerous game to play. Writing containers is hard. There are so many things you have to keep track of. You have to keep track of the construction and destruction of your elements. If that&#8217;s not a trivial thing, that is something you have to be very careful of. If you&#8217;re doing bulk allocations, you need to be careful that you have properly moved everything, and how you handle the errors. If something goes wrong during the copy, during the allocation, how do you unwind that? What guarantees can you give to the outside world&#8212;the rest of your program&#8212;about how that process happens? And moreover, how do you efficiently move things from an old allocation to a new allocation?</p><p>These are all very complicated and difficult things. I&#8217;m not saying that people aren&#8217;t capable of doing it, but I am saying that it&#8217;s very difficult to get right. If you are reimagining containers, then you should be asking why rather than how. There are genuine reasons to use different containers, but I don&#8217;t think you should be implementing them necessarily yourself. I would reach for a standard container library&#8212;like Boost or Abseil containers&#8212;and rely on the work of a lot of people to maintain those good implementations rather than trying to hack together something yourself.</p><p><em><strong>6: Do you find that mastery of the standard library is a distinguishing factor in how efficiently developers can solve problems in C++?</strong></em></p><p><strong>Sam Morley:</strong> It surely can be. This goes back to the notion of what is the smallest problem that you know how to solve without thinking, and having a very good understanding of what is in the standard library&#8212;what the things in the standard library are capable of delivering, and how you might reasonably do that&#8212;will certainly raise this floor.</p><p>If you know that the standard library contains binary search functions, for instance, then that immediately is taking the place of having to solve a problem of how you binary search through something. Obviously this is a very well-understood thing; it&#8217;s just an example. But knowing how to make use of some of the more tricky and multifaceted std algorithms&#8212;for example, transform, reduce&#8212;knowing how to make use of that efficiently will make the range of problems that you can solve without doing a lot of hard work yourself quite a lot larger.</p><p>However, it&#8217;s not necessarily true that you can&#8217;t be efficient without the STL. You can absolutely be very, very productive&#8212;productive is probably a better word than efficient. The factor is speed, speed and convenience. Like I said, the STL allows you to get going very quickly because it&#8217;s there, it&#8217;s ready to use. You don&#8217;t have to worry about linking or importing or doing anything difficult. And moreover, you don&#8217;t have to worry about licensing and things, which do come up occasionally. It&#8217;s there ready to go, and you can just use it. So it makes a big difference to how quickly you can deliver solutions.</p><p>It also makes a big difference in how quickly you can iterate on solutions. If you build something that works but is slow, then you can make it faster. I don&#8217;t think it&#8217;s necessarily important for you to use the standard library exclusively. If you&#8217;re already working in an ecosystem that provides standard-library-like abstractions, possibly more flexibly, then by all means use those things. If you always have Boost available to you, then use Boost. Boost also provides a great set of many, many more features besides what is in the standard library, and making use of those things will also enhance your productivity.</p><p>Similarly, if you&#8217;re in Abseil, then use Abseil. But you still should keep track of what is in the standard library, because if you move away from a project where you&#8217;re familiar with Boost, or familiar with Abseil, familiar with Folly, or whatever library stack you&#8217;re using now might not be the library stack you&#8217;re using tomorrow. The STL is a constant factor. If you&#8217;re using C++, you more or less always have the STL, so having it in the back of your mind all the time is always a good idea. And it certainly will make you faster&#8212;not necessarily in code execution time, but certainly in the development time.</p><p><em><strong>7: C++ is a multi-paradigm language with many powerful features, some of which can be a double-edged sword for maintainability. Since the goal is to build scalable, maintainable solutions, what best practices do you suggest to keep C++ codebases clean and manageable?</strong></em></p><p><strong>Sam Morley:</strong> Yeah, this is a tricky question. There are, of course, a lot of general-purpose good practices that apply here&#8212;things like documenting your code and leaving lots of comments about how your function operates, what guarantees it expects, and what guarantees it gives, and understanding that.</p><p>Before we jump into this, I want to introduce the notion of &#8220;future you.&#8221; Future you is your future self, and for all intents and purposes, this is a different person. Because when you&#8217;re writing some code, you understand things in the context of what you&#8217;re doing at the moment. Future you will have lost this context. So when you come back to your code in a month, six months, a year&#8217;s time, and you look at it and you think, &#8220;What was I thinking to make this code?&#8221; almost surely the answer is, &#8220;I don&#8217;t know.&#8221;</p><p>So writing comments is not just for other people&#8212;it&#8217;s also for yourself. You don&#8217;t have to go overboard and say, &#8220;I add these two numbers together,&#8221; because that&#8217;s not a useful comment. But I&#8217;ve taken to doing this quite recently where I&#8217;ve been working on some very intricate mathematical expressions and processes: I&#8217;ve taken to writing very big, chunky block comments. It&#8217;s like, &#8220;Right, OK, this is where we are in this process. This is how the next set of things works. This is what it should do. This is broadly how I&#8217;m going to implement the algorithm to do this.&#8221;</p><p>These comments save me so much pain when I jump off the project for a week and then go back and have to remember exactly what I was trying to do. It takes you a few minutes to sit and think about what that thing was, but that&#8217;s time well spent because now you&#8217;re thinking about the problem. This is where you can do some of this work of breaking down the problem&#8212;abstracting, finding common patterns, things that you recognize, things that you know how to implement&#8212;and then you should be able to spot those elements in the thing that follows. Doing this work in the code, in the body of the code, will keep it there so that when you come back to it, you can remember what you were thinking.</p><p>And moreover, this also applies to other people&#8212;not just future you. But that&#8217;s general-purpose advice.</p><p>Specifically for C++ things, and more with scalability in mind: having a very strict separation of concerns is a very good idea. You want to keep code that does numerical computations away from code that talks to users. You want to separate different functionalities as much as possible, and ideally you want to test those in isolation. Having a very modular, very pick-and-choose kind of situation will really help with that.</p><p>Sometimes it&#8217;s not possible to do this easily. Sometimes separating things can be really hard work. But being able to test and benchmark your high-performance components in isolation can really help you understand what they&#8217;re doing, how they&#8217;re doing it, how fast they&#8217;re doing it, and make sure that everything there is correct before you integrate that into the rest of your program.</p><p>It also means that if you&#8217;re doing some work that involves distributing large computations over a large cluster or on the cloud or something, you can write the different distribution mechanisms separately and then just reuse your tight-loop computation routines inside those. So it affords you a great deal of flexibility to modularize your code and separate them into separate libraries, or even just separate namespaces within a library. These kinds of things can make a big difference in the way that you can test and run your code.</p><p>A couple more points: you should always pay attention to thread safety, even if your application is not going to be multi-threaded. You should be thinking, at some point, this might be multi-threaded; I might need to access this class, these class members, from different threads&#8212;so how do I make sure that that&#8217;s a thread-safe thing to do?</p><p>And the third thing is to make sure that you keep your build system clean. I use CMake, typically. Make sure you keep that clean, and keep it in a way that is easy to see what the individual components are. Moreover, if you need to extract bits and put them in their own library, make sure that&#8217;s an easy process, because build systems can get left behind, and having a broken build system is far worse than having broken code. It&#8217;s much harder to figure out what exactly has gone wrong if your build system is broken. So those are my points.</p><p><em><strong>8: When using advanced features like template metaprogramming, clever lambdas, or other C++ &#8220;power tools,&#8221; how do you ensure the code stays readable and team-friendly rather than turning into an overly complex &#8220;wizardry&#8221;?</strong></em></p><p><strong>Sam Morley:</strong> Yeah&#8212;I mean, wizardry is the right word. I&#8217;ve seen some horrendous template metaprogramming in my life. I&#8217;ve written some horrendous template metaprogramming in my life. I&#8217;m going to be the first one to admit that it&#8217;s never worth it.</p><p>Generally, I stay away from template metaprogramming nowadays. The need for it has diminished somewhat with concepts and constexpr functions being part of the standard now, and the amount of flexibility that those afford you going up. The need for very complicated template metaprogramming has gone down.</p><p>There are other reasons, of course. Templates are very expensive from a build-time point of view. Instantiating a complicated template metaprogramming construct can easily double the compile time for a particular C++ file. And that&#8217;s not healthy if you&#8217;re building 10,000 of these&#8212;that&#8217;s a lot of time. There&#8217;s a good reason why Google, when they wrote Abseil, kept their metaprogramming to an absolute minimum. They&#8217;re very explicit about this fact. It&#8217;s because the compile-time costs are just too high.</p><p>And moreover, going back to the &#8220;future you&#8221; idea: if you write template metaprogramming code, future you will have a hard time understanding it, because it&#8217;s one of those things that makes sense while you&#8217;re writing it, and then it becomes immediately impenetrable. So I would stay away from template metaprogramming as much as possible. There are some isolated things that are useful&#8212;like using SFINAE to enable or disable particular instantiations of templates and things&#8212;but always keep that as minimal as possible.</p><p>For lambdas, lambdas are interesting because, used correctly, they can really enhance the readability of your code. They can really make it much easier to understand. On the flip side of that, they can really, really make it hard to understand what the code is doing. So my general advice for using lambdas is: keep them relatively short, and avoid having lambdas which capture and modify values that are a long way away.</p><p>What I mean by that is: suppose you have a big function that is performing some kind of calculation, and at the top you have a couple of lambdas which capture a row number. Let&#8217;s say you&#8217;re doing a matrix multiplication. It captures a row number, and the lambda accesses data from a particular row and then advances the row number. Now using that lambda will always cause confusion because the row number is a long way away from where the lambda is used. So every time you think, &#8220;What is this lambda doing?&#8221; it&#8217;s modifying something that you&#8217;ve not looked at for a long time because your screen has been further down the page.</p><p>Done correctly, this can be quite a powerful pattern. Done incorrectly, it really is a hindrance to you remembering what your code is doing. Almost surely in this instance, if you have a value which is initialized and then only ever modified or used by a lambda, it would almost surely be better encapsulated in a class of some description separately, so that the dependency on this thing&#8212;and the fact that this is a value that&#8217;s only modified or used by the class&#8212;is very explicit.</p><p>So that&#8217;s my thoughts, but that only really applies if the lambda is modifying a value. If it&#8217;s just capturing and doing something to it, that&#8217;s different. One of my favorite uses of lambdas is to capture a pointer that&#8217;s come in as a span or something that&#8217;s come in as a function argument, and then return particular subspans or particular elements from that span. For writing a matrix multiplication, for example, you might want to return a submatrix, or you might want to return a row or a column, and using a lambda for that purpose is really helpful because it saves the amount of work that you have to write again and again. And also it&#8217;s not modifying anything. Modifying is the problem.</p><p>As soon as you&#8217;re just returning a particular row, a particular column, or a particular element, that&#8217;s less problematic. In the past, you probably would have used a macro for doing these kinds of operations, but this is just C++. We don&#8217;t use macros anymore.</p><p>So those kinds of uses are fine, but I would generally try to keep your lambdas very short&#8212;and if they do need to capture things, remember the locality in the code of where you&#8217;re capturing from, and try not to let that drift too much.</p><p><em><strong>9: Let&#8217;s talk a little bit about performance, concurrency, and safety, specifically in C++. You have a chapter in your book on understanding the machine, covering topics like modern CPU architecture, memory errors, SIMD instructions, and branch prediction. Why should today&#8217;s C++ developers care about these low-level details?</strong></em></p><p><strong>Sam Morley:</strong> OK, so let&#8217;s think of it like this. Suppose you are driving down a road. If you&#8217;re going along an unfamiliar road, you have to drive slower. You don&#8217;t know where the turns are. Suppose it&#8217;s dark&#8212;you don&#8217;t know where the turns are. You don&#8217;t know what the traffic is like. You don&#8217;t know what the road condition is like, so you drive slower to be cautious. And this is what writing code without thinking about the system is like. In this world, the system that you&#8217;re running the code on is the road, the code that you&#8217;re writing is the car, and you&#8217;re thinking ahead about what the road conditions are going to be like&#8212;although you actually know what the road condition is going to be like in a lot of cases.</p><p>And in those conditions&#8212;like if the road is flat and straight, the road condition is good, there&#8217;s good visibility, there&#8217;s little traffic&#8212;you can go faster. And this is really what understanding the machine is all about: understanding how the different levels of cache interact, and how one retrieves data and then operates on it efficiently is a big part of how you make applications fast. If you ignore the cache, the code will work, but it will be much, much slower.</p><p>So, for example, most people in computer games have this discussion of structure of arrays or arrays of structs. The pattern is very simple. If you have, say, a set of objects inside your game, do you put those in a vector of structs, where the struct has all the different properties&#8212;like position, velocity, mass, whatever&#8212;or do you put them in separate arrays? One array for positions, one array for velocities, one array for masses, and so on. And this makes a big difference because of the cache and also because of vectorization. If you&#8217;re going to operate on positions only, then having a contiguous set of positions in memory means you can fetch them and operate on them very efficiently. Whereas if you have an array of structs, then you&#8217;re fetching positions but you&#8217;re also fetching velocities and masses and all the other stuff that you don&#8217;t need at that point in time, and you&#8217;re wasting bandwidth and you&#8217;re wasting cache.</p><p>So that&#8217;s one of the really classic examples. Another really classic example is matrix multiplication. Matrix multiplication is interesting because, in one direction of your matrix, you&#8217;re accessing data sequentially, which is really good. That&#8217;s really great for cache hierarchy. In the other direction, you&#8217;re accessing it with a huge stride, so the elements that you touch as you move from row to row down a particular column are far apart in memory, so you have to go a long way between these elements. So this is really bad for cache locality.</p><p>In order to address this, you do tiling. You take a small chunk of your matrix and use the data in that as much as possible so that you make the most of those expensive load operations, and you do as much operation as you can on that small tile of matrix. Then you move to the next tile.</p><p>In the book, I show a very marked improvement over a very naive implementation&#8212;it&#8217;s like a factor of four or something&#8212;and this was the point at which I started to engage a bit more with the pipelining and SIMD parts of this. You can dramatically speed up.</p><p>And if you want examples of this kind of thing, FFTW is a really great code base to look at. It&#8217;s a very difficult code base to read because it&#8217;s a C code base and it&#8217;s full of macros, but you can spot some elements of what they&#8217;re doing. The pipelining is the process they&#8217;re using, and this is to sort of hit the compiler with all of these things so that it can stack up all the other operations and make the execution much faster, because it stacks all of these things up at once rather than having this situation where, &#8220;I need this value, but now I have to wait for it.&#8221;</p><p>Also, they will use lots of SIMD operations and vectorization at the end. So that&#8217;s where I would suggest that people look. This is prevalent across all compute domains. It&#8217;s just about understanding what is the limiting factor in the performance of your software and then having some knowledge of the underlying computer&#8212;or whatever system you happen to be operating on&#8212;and really making use of every part of that.</p><p>For machine learning, for example, the models are huge now&#8212;billions of parameters, trillions of parameters even&#8212;and throughput really matters. Taking an extra microsecond to do a computation might not sound like much, but those micro-efficiencies really make a big difference in the long run. For general-purpose compute, if you&#8217;re interacting with a disk, or interacting with a network, or interacting with a user, then those details might not matter because you&#8217;re limited by something else. So it&#8217;s all about understanding where and when it&#8217;s appropriate.</p><p><em><strong>10: Can you share an example of how understanding hardware behavior can guide a C++ programmer to write more efficient or optimized code?</strong></em></p><p><strong>Sam Morley:</strong> Well, I mean, OK&#8212;this structure of arrays discussion is certainly one example of this. I come from a sort of scientific computing, high-performance compute for machine learning kind of background, or at least that&#8217;s where I am now, and here I always have to think about this.</p><p>One of the real classic examples of where you really need to understand these things is matrix multiplication. Matrix multiplication is interesting because, in one direction of your matrix, you&#8217;re accessing data sequentially, which is really good. That&#8217;s really great for cache hierarchy. In the other direction, you&#8217;re accessing it with a huge stride, so the elements that you touch as you move from row to row down a particular column are far apart in memory, so you have to go a long way between these elements. So this is really bad for cache locality.</p><p>So in order to address that, you do tiling. You take a small chunk of your matrix and use the data in that as much as possible so that you make the most of those expensive load operations, and you do as much operation as you can on that small tile of matrix. Then you move to the next tile.</p><p>This is something that you have to think about if you&#8217;re writing high-performance code, because you can&#8217;t just write the naive triple loop and expect it to be fast. It will work, but it will not be fast. If you want it to be fast, you have to structure your computation so that it plays nicely with the cache and the memory hierarchy. And the same kind of thinking applies to lots of other algorithms as well.</p><p>So that&#8217;s a really quick example of how understanding hardware behavior&#8212;specifically cache locality and memory access patterns&#8212;can guide you to write code that&#8217;s much more efficient.</p><p><em><strong>11: Your book also delves into parallel computing and even GPU programming, which is notoriously difficult with pitfalls like data races and deadlocks. Coming back to the mindset aspect of things, what mental models or strategies do you recommend for designing multi-threaded C++ applications?</strong></em></p><p><strong>Sam Morley:</strong> Yeah, thankfully modern C++ really does make this a lot easier. There are two different scenarios I want to highlight.</p><p>The first is where you have a large amount of data that you need to process and you want to do this in parallel. Now, with some caveats, this is relatively safe to do in a multi-threaded environment because you just give each thread a different range of values to operate on. There&#8217;s never any overlap, and each thread goes away, does its work, and the results are put in the buffer, and there&#8217;s no overlapping. There are no data races; there are no problems there.</p><p>And this is a safe thing to do, and it&#8217;s very easy to do with parallel algorithms or OpenMP and things like that, which will do a lot of the hard work of checking that these things are not violated for you. Setting up the problem so that it works rather than the other&#8212;there are some conditions on that. Operating on self-referential data, or data that refers to other parts of the data, is obviously going to cause problems. But that wouldn&#8217;t be an appropriate usage of those things anyway.</p><p>The other type of multi-threaded environment that you might have is where you have several worker threads that are handling different events within a bigger system, and here you have shared state. So each of the threads has some kind of global&#8212;or inter-thread, at least&#8212;state that they need to access. This could be for communicating between threads. So, for example, you might have one worker which is dispatching work to all of the other worker threads. This would be your main thread stacking up operations it needs performing, and the typical way that you would do this is with a queue.</p><p>So you&#8217;d have a thread-safe queue that you put work into. Each thread comes along, queries the queue, and says, &#8220;Is there any more work for me to do?&#8221; If so, it takes the job out and works on it in isolation, and this operation is thread-safe. It has to be thread-safe.</p><p>But also, you might have some global configuration or some kind of global data that you need to access everywhere. And there it becomes really important to understand what it really means to be thread-safe. Thread safety is a tricky thing. You need to understand where things can be mutated, who has ownership over particular things, and where that ownership can change.</p><p>Ideally&#8212;and this is something that will come up later, I&#8217;m sure&#8212;you want to have this model where only one place in your code&#8212;one thread, one function, one whatever&#8212;can modify a value at any given time. This can be achieved in one of two ways. Either you design the architecture of the program so that one thread can only ever touch one value&#8212;this is the distributed data type model&#8212;or you have a synchronization mechanism like an atomic or a mutex-locked value, or some other kind of thread mechanism for controlling access to a particular resource.</p><p>In the latter case, it&#8217;s very easy to get this wrong. Deadlocks can happen. You can still end up with data races if you use these things inappropriately.</p><p>So what I would suggest is that if you do have to do multi-threaded code, you read very carefully the documentation on cppreference or some other equivalent source for all of the different synchronization mechanisms that are available in C++, and you really try and understand what each one of those things is for and how it operates. Then you&#8217;ll be much better equipped when you are trying to design a class that needs to be shared between multiple threads&#8212;how you manage the mutability. That might be in a mutability&#8212;mutable values within the class&#8212;or exterior multiple mutability where you need to take a mutable instance of the class and actually do something with it.</p><p>Ideally, you need all of that to be thread-safe, and knowing what the different options are will enable you to actually write this code. Hopefully that will mean that you don&#8217;t have deadlocks or data races. Always test your code.</p><p><em><strong>12: Robustness and security are critical in systems programming. With C++&#8217;s manual memory management and undefined behavior guarantees, how can C++ engineers improve the safety of their code?</strong></em></p><p><strong>Sam Morley:</strong> Go and learn some Rust. I know a lot of C++ programmers turn their nose up when Rust is mentioned, and generally the feeling that I get from a lot of people is that, &#8220;Oh, we don&#8217;t need Rust. We can do all of this in C++.&#8221; But that&#8217;s not the point. The point is that Rust has a bit of a learning curve, particularly for C++ developers, because they go into it with a C++ attitude, and the Rust compiler isn&#8217;t having any of that.</p><p>The Rust compiler forces you to think very carefully about ownership and lifetimes, and whether it&#8217;s safe to move things from one thread to another. That&#8217;s its whole design: managing access, the validity of values across an entire system, and very carefully managing the enforced properties&#8212;whether it&#8217;s safe to send things or share things between threads. They have these two traits called Sync and Send, which basically determine whether you can share things or send things between threads safely.</p><p>The same applies to async programming. Even if you&#8217;re not using multiple threads, you still need to think about this for async programming as well. Learning a bit of Rust will force you to think about these things up front, and many other good things that you should definitely think about&#8212;like unsafe code. These are things that C++ programmers sort of take for granted without actually thinking about what they&#8217;re doing.</p><p>When is it actually safe to dereference a pointer? The answer is almost never. It&#8217;s almost never safe to dereference a pointer. That&#8217;s fundamentally an unsafe thing to do. You don&#8217;t know where that pointer came from. You may do, but you don&#8217;t really know where that pointer came from. You don&#8217;t know whether it&#8217;s valid or not. These are things that you have to reason about as the developer.</p><p>Rust forces you to think of this as an unsafe operation, and because of that you&#8217;re far more cautious about actually doing it. And these concepts&#8212;this way of thinking&#8212;is transferable. Learning Rust, learning a bit of Rust, will make you better at writing safe C++.</p><p>The reverse is not true. Learning C++ will not make you good at writing Rust code. In fact, it will probably make you very frustrated. But getting over that frustration and understanding why Rust enforces these things is important, because these are the same principles that allow you to write safe code anywhere, not just in Rust.</p><p><em><strong>13: Are there any particular practices or modern C++ features you advocate for to prevent things like buffer overflows, memory leaks, things like that&#8212;while retaining the performance and control that C++ offers?</strong></em></p><p><strong>Sam Morley:</strong> Yeah, absolutely. I mean, it&#8217;s not exactly a new feature, but using <code>std::array</code> rather than C-style arrays is definitely a huge win. Smart pointers mean you don&#8217;t ever manage memory by hand.</p><p>There are some cases where you might actually do this, but most of the time, writing <code>operator new</code> in your code is an anti-pattern by this point. Use a smart pointer; use a container.</p><p>The mantra of my containers section is: just use <code>std::vector</code>. It applies most of the time. And use <code>std::span</code> rather than using raw pointers or C-style arrays for passing data around. It adds this extra sort of memory safety&#8212;and yes, it does carry a small runtime performance cost, but that&#8217;s negligible compared to the risk of your code crashing out because of an invalid memory access, or producing&#8212;worse&#8212;producing garbage and it going unnoticed.</p><p>The best-case scenario for a bad memory access is a crash. That&#8217;s the computer responding to a bad thing. If it goes unnoticed, it could happen for months before you notice that this has been producing garbage the entire time, by which point you&#8217;ve wasted months. So those are the things that I would reach for first.</p><p>But the other thing is: stop using C functions. The C functions that existed a long time ago have numerous documented vulnerabilities in this sense. <code>gets</code>&#8212;the function from the C library which does an unchecked read from standard input to read a line of text&#8212;is fundamentally unsafe. I can make a line of terminal input as long as I need, and that&#8217;s a sure way of getting a buffer overflow. There are safer equivalents, but generally speaking, don&#8217;t use the C library if you can avoid it. It&#8217;s not safe, and using it will always cause some problems somewhere&#8212;especially the I/O functions like <code>gets</code> and <code>puts</code> and <code>sprintf</code> and things like that. These things you have to be very, very careful about.</p><p><em><strong>14: Let&#8217;s finally talk about your book, The C++ Programmer&#8217;s Mindset. You&#8217;re both a research engineer and a mathematician, and you maintain a high-performance C++/Python library for data science. The book itself combines practical insight with academic rigor. What drove you to write The C++ Programmer&#8217;s Mindset. Did you observe a gap in how C++ developers approach problem-solving that you wanted to address with this book?</strong></em></p><p><strong>Sam Morley:</strong> So it&#8217;s an interesting question. Going in, of course I had to do a bit of market research around this, but my feeling was: I like solving problems.</p><p>The main motivation for me writing this book was to share my feelings about solving problems&#8212;my enthusiasm for solving problems. There will always be a new problem to solve. You&#8217;ll never&#8212;almost surely anyway&#8212;you will never encounter a situation where you&#8217;ve solved all the problems. There will always be a new one, and it will be interesting because it&#8217;s new. And the more problems you solve, the better you get at it, for sure.</p><p>But this is not just a passive process. As I mentioned at the beginning, a lot of people are doing this process of computational thinking using this framework that we described. A lot of people are doing this without thinking about it, and one of the things I wanted to highlight in this book was: in order to get better at solving problems, you need to be conscious of what you&#8217;re doing to solve the problems. You need to think about what it is that you actually need to do and how you can do it&#8212;not just in the context of the problem, but in the context of thinking about the problem, understanding the problem.</p><p>And something else that I feel quite strongly about is that I feel like a lot of C++ developers could benefit from being conscious of the environment in which they operate&#8212;thinking about the operating system, the underlying hardware, thinking about what the different mechanisms that they&#8217;re using are, how those things are informed by and inform the problem-solving process.</p><p>Do I need a map, or do I need a hash map, or do I need a vector? These are design questions that are informed by the implementation, and those relationships are really what the book is about. It&#8217;s about thinking about the language, the hardware, the operating system&#8212;all of those things combined&#8212;in the context of solving problems, and how the process of solving problems is informed by, and informs, the choices that you make elsewhere.</p><p>So that&#8217;s the message that I eventually decided was going to be the topic of the book.</p><p><em><strong>15: What mindset shift or new capabilities do you expect a seasoned C++ developer to gain after reading your book?</strong></em></p><p><strong>Sam Morley:</strong> Yeah&#8212;so, seasoned developers might feel that they already have a pretty strong grasp of solving problems, and this probably is true. A lot of very talented engineers out there. I would suggest, though, that everybody has something to learn. You don&#8217;t&#8212;you can&#8217;t ever know everything. So the sort of mindset shift is: you can&#8217;t know everything. So learn as much as you can from as many people as you can, and hope that that fills in as much&#8212;as many gaps&#8212;as you need. And so that&#8217;s the sort of philosophy that I would hope that seasoned developers would take away from this.</p><p>In terms of new capabilities, seasoned developers might already be pretty familiar with cache hierarchy and things like that. What they may not be so familiar with is this linkage between the problem-solving process and the implementation details and the other factors. The computers are complicated machines, so understanding all of these things is impossible, of course, but you can understand parts of it, and moreover you can tune your problem-solving process to fit what you have and where you&#8217;ll be working. It&#8217;s a two-way street, and that I hope is something that even senior engineers can think about while they&#8217;re reading.</p><p>One of the key things that I mentioned very early on in the book is this &#8220;future you&#8221; idea. That will be helpful for you in the future&#8212;future you&#8212;but it will also be helpful for less senior people who are learning this process for themselves, and being able to point out to them where and why certain parts of the process can be so tremendously helpful, and imbuing this understanding of how all of these different moving parts interact with one another can be really, really powerful. That is something that I hope that even a seasoned engineer can gain from this book.</p><div><hr></div><p>To go deeper into the ideas Sam Morley discusses in this interview&#8212;treating C++ problem-solving as a deliberate process, choosing abstractions with a clear-eyed view of their costs, and connecting design decisions to the realities of hardware, build systems, and team maintainability&#8212;see <em><strong><a href="https://www.packtpub.com/en-us/product/the-c-programmers-mindset-9781835888438">The C++ Programmer&#8217;s Mindset</a></strong> </em>(Sam Morley, Packt, 1st ed., Nov 2025). The book introduces computational thinking as a practical framework&#8212;decomposition, abstraction, and pattern recognition&#8212;and shows how to apply it using modern C++ features to build solutions that are maintainable, efficient, and reusable. Across small examples and a larger case study, Morley covers using algorithms and data structures effectively, designing modular code, analyzing performance, and scaling work with concurrency, GPUs, and profiling tools&#8212;aimed at intermediate C++ developers who want to strengthen both their technical toolkit and the way they approach complex software challenges.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rpnO!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rpnO!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775" width="332" height="409.5274725274725" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/207a7e01-560e-4438-ad85-562040511902_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:332,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The C++ Programmer's Mindset&quot;,&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="The C++ Programmer's Mindset" title="The C++ Programmer's Mindset" srcset="https://substackcdn.com/image/fetch/$s_!rpnO!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!rpnO!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F207a7e01-560e-4438-ad85-562040511902_2250x2775 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>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xpla!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xpla!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 424w, https://substackcdn.com/image/fetch/$s_!xpla!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 848w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1272w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png" width="857" height="607" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:607,&quot;width&quot;:857,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:138673,&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://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.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_!xpla!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 424w, https://substackcdn.com/image/fetch/$s_!xpla!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 848w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.png 1272w, https://substackcdn.com/image/fetch/$s_!xpla!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95a0e02e-b5b2-448e-aeb6-902f4cadb1df_857x607.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!doxG!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!doxG!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 424w, https://substackcdn.com/image/fetch/$s_!doxG!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 848w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1272w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png" width="852" height="422" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:422,&quot;width&quot;:852,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:100846,&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://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.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_!doxG!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 424w, https://substackcdn.com/image/fetch/$s_!doxG!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 848w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.png 1272w, https://substackcdn.com/image/fetch/$s_!doxG!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fac0d1272-d603-4e1c-8aec-7407048c6dd9_852x422.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ATit!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ATit!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 424w, https://substackcdn.com/image/fetch/$s_!ATit!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 848w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1272w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png" width="862" height="662" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:662,&quot;width&quot;:862,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:134677,&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://deepengineering.substack.com/i/185273208?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.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_!ATit!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 424w, https://substackcdn.com/image/fetch/$s_!ATit!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 848w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.png 1272w, https://substackcdn.com/image/fetch/$s_!ATit!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8182e4a3-ef29-458d-9515-4bad5724839a_862x662.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></p>]]></content:encoded></item><item><title><![CDATA[Rethinking Test-Driven Development for the AI Era: A Conversation with Kevlin Henney]]></title><description><![CDATA[On misconceptions, design pressure, legacy code, language cultures, and why testing and review skills&#8212;not tools or AI&#8212;will shape how teams use TDD.]]></description><link>https://deepengineering.net/p/rethinking-test-driven-development</link><guid isPermaLink="false">https://deepengineering.net/p/rethinking-test-driven-development</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 11 Dec 2025 05:11:40 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/gCPZ8oXNdm8" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Test-driven development sits in an awkward place in many teams: widely cited, unevenly practiced, and often misunderstood. For some developers, TDD is a niche technique that only applies to greenfield code; for others, it is reduced to &#8220;writing some unit tests&#8221; after the fact. In between those extremes are practical concerns about legacy systems, language ecosystems, CI pipelines, AI-generated code, and the day-to-day pressures of shipping software with limited time and attention.</p><p>In this Q&amp;A, we speak with <strong>Kevlin Henney</strong> &#8212; independent consultant, speaker, writer, and trainer &#8212; whose career sits at the intersection of software design and everyday development practice. Kevlin works with companies on code, design, practices, and people; contributes to the <strong>Modern Software Engineering</strong> YouTube channel; and is co-author of <em>A Pattern Language for Distributed Computing</em> and <em>On Patterns and Pattern Languages</em> in the <em>Pattern-Oriented Software Architecture</em> series, as well as editor of <em>97 Things Every Programmer Should Know</em> and co-editor of <em>97 Things Every Java Programmer Should Know</em>.</p><p>Across the conversation, Kevlin unpacks why TDD adoption stalls even for experienced developers, the misconceptions that blur the line between &#8220;developer testing&#8221; and true TDD, and how tests shape design without losing sight of the bigger architectural picture. He talks through introducing tests into large legacy codebases, how language and ecosystem culture influence testing practice, and what distinguishes good, specification-like tests from brittle method-by-method checks. We also explore tooling choices, where TDD fits alongside integration, acceptance, contract, and performance testing, and how team leaders can sustain testing discipline under deadline pressure. Finally, Kevlin shares his perspective on AI-assisted development, the risks of outsourcing tests to generators, and why, in an era of increasingly automated code, testing and review skills matter more than ever.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-gCPZ8oXNdm8" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;gCPZ8oXNdm8&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/gCPZ8oXNdm8?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1: Adopting TDD can be tricky, even for seasoned developers. In your experience, what are the main reasons that experienced developers struggle when first adopting TDD?</strong></em></p><p><strong>Kevlin Henney:</strong> I think there are different kinds of developers, and they will have different reasons for struggle. At one level, you are asking people to do something different from what they normally do. That is the first challenge. Just as a human being, that is always going to be difficult, particularly when you already have a set of habits in place. Regardless of how effective those habits actually are, we always perceive the habits that we have as being comfortable. That is why they are habits, and sometimes we have a justification for them.</p><p>So trying to get anybody to do something different from something they already do is going to be a challenge. The more experience you have, in this case, the more at a disadvantage you may be, interestingly. If you are relatively new to software development, then everything is fresh and every new idea is more likely to be treated equally by you.</p><p>But even then, we need to understand that a novice developer can sometimes struggle, and sometimes we have the issue with people who are in that overlap space where they are not necessarily formally a developer, but they do a lot with code. I am thinking particularly of data scientists and engineers who might not consider themselves to be developers, but who have worked extensively with Python and associated libraries such as NumPy, Pandas, and things like that. They are in the development space, but they do not necessarily have the insight of development culture and concepts, and often they have semi-effective workarounds that they have created, which get them by every day. The point is that for most people, this is the case.</p><p>When it comes to testing of any kind, we do not necessarily have as good a story for people as we do for creating a feature and doing a demo. These are very well-practiced within the software development space, and often videos and books will emphasize these, and there is much less on any kind of testing, let alone TDD. So testing tends to be more ad hoc. When you are trying to get somebody to do something like TDD, which is a very structured workflow, that is your challenge: you are trying to get them to do something different.</p><p>Then one of the other challenges is often the way that TDD is described. There is a simple mantra, &#8220;red, green, refactor.&#8221; You write a failing test for something, then you make it pass, and then you refactor. Although that is a very simple mechanical description, and it is not wrong, it is not very motivating. It leads to the reaction, &#8220;Why do you want me to write something that does not work?&#8221; That is not the right mindset. &#8220;Write a thing that does not work and then make it work&#8221; does not feel like a motivating mindset.</p><p>So I think that is another obstacle. Often the examples or the way that TDD is taught make a lot more sense to somebody who has expertise in it, or when you are coaching alongside somebody, than they do when you are just offering somebody the mantra. It is not compelling. I will be the first to say this. When I do workshops and training courses for companies, I will describe the red-green-refactor cycle. You need to know that. But then I go into it, I take it apart, and I say what is really going on.</p><p>At that point, it becomes easier to motivate. The first point is that you are not just writing a failing test. You are writing something for a behavior that you do not have. Because you do not have that behavior, of course it is not going to pass. But the goal is not simply to make it pass. The goal is to write what you want for the new behavior.</p><p>The next motivation is actually a simple constraint. In many cases, we can end up yak shaving or just running off into the horizon with complex behaviors, saying, &#8220;I will just write everything, and then I will come back and test it later.&#8221; If we do that, we often end up with things that are not as simple as they should be, and we do not ask ourselves the questions, &#8220;Is this what I need? Is there a simpler way to do this?&#8221;</p><p>So TDD is literally a limiting factor. It is like throttling back the instinct to just throw everything at the screen. Instead, you are going to take steps so that you understand every step and consider it. It is really a scoping mechanism. The idea is: now I am going to make it pass with something that is no more complex than necessary, so that I fully understand what the next step is going to be. I can guarantee that this is always working, but I am also going to give myself the opportunity for refactoring.</p><p>When explained like this, I am not going to say that it suddenly turns on all the lights, but it does make more sense. Then we move to the next level, where we say, &#8220;Let us forget the red and green. Red and green are side effects.&#8221; Your real goal is: tell me what you want to have working. Here is a piece of code. It has a certain amount of functionality. You want it to do something else. What does that extra bit look like? Show me an example. Somebody says, &#8220;Well, it should do this.&#8221;</p><p>&#8220;OK, great. Does it do that now?&#8221; &#8220;No, it does not.&#8221; That is why it either does not compile or it does not pass a test, because you are asking for something new. A test is a change request. When you describe it that way, a lot of people say, &#8220;Oh, I see. You are writing a change request to yourself. You are saying, &#8216;I want to have a piece of code that does this, but it does not do that yet. Here is a really concrete example of what I want.&#8217;&#8221;</p><p>Now I am going to work towards that, and I am done within a couple of minutes, and I can continue from there. The point is that you are not simply teaching somebody how to test, although there is a truth in that. You are actually trying to rewire how they think about the very act of coding, and that is hard.</p><p>That is why you will find that it is a skill. It is something worth practicing, and it is a practice that, once you have it, you can draw upon. It does not mean you have to do it all the time, but if you have never practiced it, how can you say, &#8220;That would be appropriate now as a tool or a technique&#8221;? You are trying to rewire how people think about the act of coding, and that is difficult. So you will meet resistance because of change, but also resistance because it is a fundamentally different way of doing something for which people already have some behaviors.</p><div><hr></div><p><em><strong>2: You have talked a lot about the mindset shifts required, and you said that adopting TDD itself is a skill. Are there any specific skill gaps you can point out that tend to be the biggest hurdles for developers who do not adopt TDD?</strong></em></p><p><strong>Kevlin Henney:</strong> Honestly, sometimes the problem in terms of skill gaps is simply testing itself, unit testing itself. In other words, developers do not have a habit of any kind for testing, or testing is something that happens later and sometimes in a mad rush. Therefore, the tests that are written are quite difficult to read, and people often have this idea of tests being second-class citizens.</p><p>Often you look at tests and you say, &#8220;Yes, they look like second-class citizens,&#8221; and people create tests that are difficult to maintain, sometimes because they have never been shown what a good test looks like. For many people, when they are learning TDD, they encounter the fact that there are many things they are trying to learn at the same time, and one of them is, &#8220;What does a good test look like?&#8221;</p><p>That is an issue. Picking up on what I said earlier, tests are specifications. There are many ways of thinking about testing, but the way that we are encouraging here is specifying. Your test should be an explanation, a description that captures intent, and it should have an example. The example is the centerpiece, and you want to capture the intent in the name. If you have a test that does too much, it is not a good test.</p><p>It turns out that many of these things are things that people do not already know or do. So in addition to the workflow, there is an additional skill: how do you write a good test?</p><p>The other skill that is often missing is that people do not necessarily have a good code sense or design sense. By that I mean they often do not know what good code looks like. When you say, &#8220;And now refactor,&#8221; they do not know what to do, because although they may have a refactoring menu available to them, and although they may know the meaning of the word, they do not actually know what &#8220;better than this&#8221; looks like.</p><p>So you end up with code that just gets bigger and bigger, with more ifs and whiles. That is not what I have in mind. Where is the simplification? They are not actively looking for simplification. That is a design skill, and that is quite difficult to teach.</p><p>Therefore, if you are going to make the best use of any workflow, and this is not unique to TDD, you need to be actively looking for good design. Many workflows suffer because people are not asking, &#8220;How do I make this simpler? How do I make sure I have less code to maintain in future?&#8221; You want to write less code. Your goal is not to produce more code; it is to produce less code.</p><p>The most common thing I see is that programmers do not know how to write less code than they need. They often go in with code like, &#8220;There is absolutely nothing wrong with writing too much code as your first draft.&#8221; There is nothing wrong with that. What matters is what you do with your second draft, and that is the problem. Many people do not have that second draft because they have not worked alongside somebody who can show them what that looks like.</p><p>You cannot expect this to be an act of magic. You start learning how to code and suddenly you develop a good instinct for the right balance and structure of a method, of a class, and what an interface should or should not look like to be effective and easy to change. Without helping people develop that sense, almost any workflow you throw at them is going to make things potentially worse.</p><p>We see that with AI: people who do not know how to code can produce a lot of code. They need to learn to produce less. You can use AI to produce less. The skill is to produce less that does exactly what you want, because then you have less that can go wrong and less to read.</p><p>This is something that I do not think we get across well enough. For me, TDD helps with that, because it always reminds me: &#8220;OK, now cook it down. You have this; cook it down.&#8221; You have tests; they work. You have a safety net. There is a skill there, which is very much code sense, both for the tests and for the body of the code itself.</p><div><hr></div><p><em><strong>3: What do you see as the biggest misconceptions or myths about TDD among developers and teams today?</strong></em></p><p><strong>Kevlin Henney:</strong> I do not know that there is necessarily just one, but there are a few. One is that you can, that it is only something that you can do with new code. Another is that, to be precise, it can only be used on a greenfield situation. Another is that your TDD is very much centered on your unit testing framework and things like that.</p><p>So there are these kinds of ideas, and we live and work in an industry where jargon is often thrown around and sometimes it is very imprecise. When something is described in a number of companies, &#8220;Oh yeah, we are doing testing,&#8221; that is great. There is nothing wrong with that. The code leads and the tests follow, which is a different workflow. That is perfectly fine. I am not here to tell people that TDD is the only way to work.</p><p>What I am trying to avoid is a kind of semantic dilution if &#8220;TDD&#8221; comes to mean just that developers are testing. That is great, but we would like to call that &#8220;developer testing&#8221; as a general term, rather than TDD, which is a very specific workflow.</p><div><hr></div><p><em><strong>4: Are there any particular false beliefs you frequently find yourself debunking?</strong></em></p><p><strong>Kevlin Henney:</strong> Oh, yes. There is one that has two sides to it. First of all, I separate out a couple of things. Sometimes when people are being negative about TDD, they are not talking about TDD; they are talking about unit testing. They are using &#8220;TDD&#8221; to stand in for unit testing in an environment where, culturally, within the organization, you do not test. That becomes a reinforcing thing. It is not about TDD at all in that case.</p><p>Then there is another one that does come from people who do practice TDD. Every now and then you will hear the slogan that TDD is not about testing, it is about design. I know what they are trying to do. They are trying to emphasize that testing is not just an act of verification. We often have this idea of testing as purely about verification, a kind of gatekeeping activity. But saying &#8220;TDD is not about testing&#8221; is not a true statement, and I always have problems when people present it that way.</p><p>At least half of my work is with companies who say, &#8220;We want to do TDD,&#8221; when what they really want to do is testing. TDD is a discipline, a workflow. You can tell when people are doing it. It is also the most extreme thing they have probably done in terms of testing discipline, so why give it a name that you also use for everything else? I always make sure people are aware there are many workflows.</p><p>What I try to make sure they understand is that when we say TDD, we mean something specific. It is not a magic spell. It is a particular way of working that gives you certain kinds of feedback and certain kinds of design pressure. Part of that pressure is on you as a developer to ask, &#8220;What am I actually asking for? Do I know what I want from the code?&#8221; Sometimes the honest answer is that you do not know what you want. That is a recognition of ignorance, that you do not yet have enough knowledge. At that point you may need to discover that knowledge, perhaps by spiking something or exploring, rather than pretending you are doing TDD when you are not.</p><p>Another part of this is the tests themselves. Some of them are actually quite large, and you have to ask, &#8220;Do I really want that? Is that genuinely helpful, or is that telling me something about the design?&#8221; Often the test is large because the design is causing that. If the interface feels very clunky, then that is telling you something about the design as well.</p><p>So as to what it feels like: testing is in fact the way you experience the design. Rather than looking at testing as a purely quantitative activity&#8212;&#8220;I got this percentage of statement coverage, I have done my job&#8221;&#8212;you can ask, &#8220;What does it feel like to write the test? What does it feel like to use the code I am providing?&#8221; If the answer is, &#8220;Yes, it is quite easy, it feels natural,&#8221; that is good feedback. If it feels like having a code review where you have to do most of the work yourself, then the tests are giving you a signal that there may be something up with the design.</p><div><hr></div><p><em><strong>5: There is an ongoing debate about TDD&#8217;s effect on software design and architecture. Some argue that focusing on small tests leads to fragmented design or lack of &#8220;big picture&#8221; thinking. How do you believe TDD influences software design?</strong></em></p><p><strong>Kevlin Henney:</strong> Hmm. I think it goes back to what I was saying about having this kind of design sense or code sense. If you are only ever going to think small, then yes, TDD will have those effects and you will end up with fragmentation rather than a cohesive design. That is one of the reasons it is quite important to make sure that you have a reasonable test hierarchy, that you are testing at all levels, and why, when you are doing this, you should always be taking the big picture view as well.</p><p>And this is, I guess, where the driving metaphor that is used extensively when talking about TDD becomes even more appropriate these days. When I drive, there are three places that I am typically looking. I am looking at the road immediately in front of me. I am also looking down at the dashboard to see what my car is telling me. And I am also looking at a map to see what the big picture is.</p><p>The problem is that I get the feeling that many people, and this is again not just a TDD thing, I find this with different roles in development, are only ever looking at one of these at a time. So it is like, of course, if you are only looking at the dashboard, you are not going to see what is in the road in front of you; you are going to slam into something. But if you are only looking at the things in the road in front of you, that does not tell you what the bigger picture looks like and what the trends are in traffic, for example. So you are not getting feedback at all these three levels. You are only ever looking at one and ignoring the feedback from the others.</p><p>So here is the thing. If you are using TDD and that has caused you to end up with a fragmented design, you are looking at the bigger picture. But also, whenever you are having design ideas, the idea is that when you are launching into TDD, you should have a vision of where you are going to go. The problem is that sometimes people do not actually have an idea of where they are going to go. I often have this thought of sketching out an approach. Do not commit yourself to detail. This is not a committed design; it is literally a sketch.</p><p>As a sketch, what you are going to do, what TDD is going to do, is fill in the details. For anybody who does draw, and I know that drawing is not a very common skill among developers, it is one of those things where I always ask people what they do. Music is very common. Gaming is very common, whether it is computer-based games or board games. Certain sports are very common. Drawing is not very common, but when you draw, you often sketch the form and then you put the detail in, but that detail sometimes tells you that maybe the form is not right.</p><p>So for me, people often launch in hoping that if they start drawing in the bottom right-hand corner a miracle will occur. If you are a brilliant artist, yes, a miracle will occur; you will produce a great picture. But sometimes people are not looking at the big picture. They always need to be asking, how is this going to be used, how does this affect that? So for me, I think that we can look at that.</p><p>When some people say, &#8220;TDD does not do this,&#8221; my answer is, &#8220;No, that is your job.&#8221; TDD&#8217;s job is to do the sketching. It is your job as the artist to see the bigger picture and say, &#8220;I am drawing the wrong thing,&#8221; or &#8220;Maybe that needs to be moved,&#8221; or to take the feedback. If you are only taking feedback at one level, that is great; many people take feedback at zero levels. However, you need to be looking at multiple perspectives. Some of them are closer and some of them are further away.</p><p>So I do not really accept the criticism that TDD causes this. I accept that there may be a misunderstanding of the role of TDD, that people are sometimes saying, &#8220;If I do TDD, magic will occur.&#8221; As I told my kids when they were growing up, there is no such thing as magic. There is you, there is you and a tool and a technique. That is it. If you are misapplying the technique, that is not the technique&#8217;s fault, so there is a learning opportunity.</p><div><hr></div><p><em><strong>6: When it comes to scaling TDD in a larger organization, what challenges do enterprises face in rolling out TDD across teams? Based on what you have seen, what strategies help make TDD stick in the long run at the organizational level?</strong></em></p><p>Kevlin Henney: I think this one is more a case of, although I am very keen on TDD, I do not necessarily know that an organization wants to roll out TDD. It is a workflow practice, and I think if you can get that working within a team, that is great, but there is no reason that another team has to do it. I think it might not be helpful for an organization to be mandating these things.</p><p>I think what the organization needs to care about is more a case, not so much of the way that we are producing the tests, but the fact that, do we have builds that work together, do we have comparable testing philosophies across different teams? If you have a team that is doing a more traditional kind of &#8220;test later towards the end of the sprint&#8221; type approach, and let us say they are really effective and they have some really good design and their interfaces evolve really nicely, I would not mess with that. They are doing a perfectly good job, and because we have organized around teams, that does not really interfere. As long as our teams have some kind of alignment and relationship with an architecture, then I do not think there is a problem there to be solved.</p><p>What we do want is the idea that we have a consistent or a reasonably consistent and compatible view of testing across the organization, and that if TDD helps me get that, then that is what I should be encouraging. But I am not going to say that it is going to be the thing I should focus on. I think that what an organization probably wants to focus on at the organizational level is: if we have various build pipelines, do these build pipelines follow similar philosophies of testing?</p><p>Because a build pipeline that does not have any testing in it is not really, certainly I will be very careful here, I am using the term &#8220;build pipeline&#8221; because people will often say, &#8220;Oh, it is our CI/CD pipeline.&#8221; Is it? Are you doing continuous integration and are you doing continuous delivery? Because CI/CD is predicated on the idea that you have tests. In fact, to be fair, CI/CD is predicated on you doing trunk-based development and you doing a lot of tests. That is what that means. You can go and look at the original books and they are very clear on this. So definitely a lot of companies have build pipelines, but do they qualify as CI/CD pipelines? Not always, not from the strictest definition. I think that is more valuable.</p><p>So let us put it this way. I do not think an organization needs to worry about what people are doing in their homes, but they probably need to worry about the road system. In this sense, organizationally, when we look at software architecture, we need to be thinking of software architecture more like urban planning. We want to have consistent rules and models for the roads. We want to have a consistent layout, see what the issues are, and agree on things about roads and services. What people do in their homes and how they structure their homes and how they do it, I think that can be a lot more freeing, as long as we have the knowledge available and maybe one team can coach another and we can say, &#8220;You can become our enabling team; we are going to try this practice.&#8221; I think that is great, but I am not sure I want the organization to get involved in some of the more detailed practices that support what goes on. I think what goes on, what the output is, and how teams integrate is probably more important than specifically what they do on the inside.</p><p>I think what can make it stick is very much, let us build off what I have said. One of the things is that a team needs to feel that it owns its practices. Teams respond, and individuals respond, sometimes quite poorly when they are told what they are going to do and they do not really feel it. If a team is told, &#8220;You are going to do TDD,&#8221; that is not a way of getting them to do anything well.</p><p>If they can make it their own habit, if they can create it, if it is their decision, then that is really important, but also if they feel like they have learned something. Again, this goes back to this idea of, within any large organization &#8211; and this is obviously a question of different organizations and different scales &#8211; in any large organization we are going to find that there are different kinds of teams trying to produce different kinds of products.</p><p>Some people say, &#8220;We do not do TDD here.&#8221; Be very careful that when somebody says, &#8220;We do not do TDD here,&#8221; that this is not also, &#8220;We do not do testing here.&#8221; Again, going back to what we have already discussed, that is what I hear when many people actually say, &#8220;We do not do TDD&#8221; or &#8220;TDD is not appropriate for us.&#8221; They are actually using TDD to mean any kind of testing, and so therefore they are using the wrong word. They are actually saying something much more bluntly as, &#8220;We do not have tests.&#8221; If they said that, that would be far more direct and we could work with that.</p><p>We need to work out whether that affects us or not. If it is a team that is just prototyping and giving us the results of prototypes, then that is not important. If it is a team that is prototyping a design, yes, we want tests, because you are telling us that this code, which we do not know whether or not it works, is the basis for what we are going to build. Prototyping can involve TDD and it can involve tests. I have done that a number of times in the past. So really it is a case of trying to understand from the organizational level how to get the knowledge out there and make the knowledge feel much more natural.</p><p>For many people, any kind of unit testing habit is the challenge. Having tests that run quickly is the challenge, and I would address these questions. I would treat those as the questions to address, and what we may find is that TDD by example may follow, particularly if we have somebody from within the organization who has experience of that and that is how they drive it and that is how they show it and that is how they demonstrate it. Then that may become a lot easier.</p><p>Lead by example in this case rather than by mandate. Basically say, &#8220;Look, there are a lot of different testing workflows. Our objective is to get better testing, to make testing more convenient of any kind. Let me show you this. I am going to use a test-driven workflow.&#8221; Suddenly when you do that, that is much more open and I think people are more likely to adopt it. Whereas if we have somebody going around measuring different teams in a very obvious way, teams justifiably feel a little bit of resistance, offer a little bit of resistance there.</p><div><hr></div><p><em><strong>7: Legacy code is a reality for most teams. If a team inherits a large untested codebase, how would you recommend they approach introducing TDD or even more testing in that scenario?</strong></em></p><p><strong>Kevlin Henney:</strong> I think that is a really good question, because it matches a lot of people&#8217;s lived experience. The key point is that you have to prioritize. From where you are, perfection is impossible, so you have to look at what is possible, and that is going to be a little different for each codebase and for each team. A lot depends on whether you have what I would call a &#8220;maintenance mindset.&#8221; If you have that mindset, it is going to be very difficult to adopt TDD.</p><p>By &#8220;maintenance mindset,&#8221; I do not just mean software maintenance in the narrow sense. I mean the broader idea that &#8220;we are just maintaining whatever it is we do.&#8221; You often see this where initial development has been done in one location, and then the work is offshored to another group. The second group is told, &#8220;You are just maintaining it,&#8221; and people there may not think of themselves as doing software development. In reality, they are. There is no real separate thing called &#8220;maintenance&#8221; when it comes to software products. It is all software development. There is not &#8220;software development plus maintenance&#8221;; there is just software development.</p><p>So the first step is to reclaim the right words. You are doing software development. Everything you do has the potential to change the architecture. It is your responsibility not to preserve the problems in the existing codebase, but to eliminate them. &#8220;Maintenance&#8221; as stasis is not what you want. Your job is to be more ambitious: to make the product better than it was when you received it. How do you do that? One obvious obstacle is that you would love to test everything, but you have poor test coverage. In that case, do not try to test everything. Instead, decide how to prioritize what you test.</p><p>A useful way to do that is to look at what is going to happen in the next quarter. Suppose over the next three months you are going to add features in a particular part of the codebase. If that corner of the codebase is already relatively well isolated, then you lean into that. Reinforce the isolation. Make sure you have good automated refactoring tools available. Remember that your compiler will still catch many type-based errors. You can introduce separation and decouple tightly coupled code without relying on a large pre-existing test suite. You can lean on automated refactoring, appropriate review, and, as Michael Feathers puts it in Working Effectively with Legacy Code, &#8220;lean on the compiler.&#8221;</p><p>I have done this with teams: we deliberately ignore much of the rest of the codebase for the moment and decide, &#8220;We are going to make this part really good.&#8221; Once you have something isolated, it becomes easier to test. Unit testing and even integration testing are really about understanding isolation, loosening coupling, and improving cohesion of code units. Those are the practices that improve your code sense. Many developers these days do not have a clear understanding of coupling and cohesion. They get distracted by principle catalogs that are not very coherent. For example, the SOLID principles do not form a coherent set of design ideas; they are a bunch of things thrown together and they miss many important aspects. I know I will get comments for saying this, but I have been doing this long enough to say that SOLID principles are next to useless if you want to learn how to write good code. You are better off learning and reinforcing the fundamentals.</p><p>If you can isolate a small part of the system, that becomes your zone of &#8220;new development.&#8221; This is a bit like urban planning. In a city you cannot change everything at once, but you can change a particular district. Because that area is separated, you can make it good and benefit from that separation. That is one technique for allowing a team to claim territory and improve not only their testing but also their design. The important idea is that you are not just improving testing habits; you are improving the code itself. Testing and design are not separate activities. Treating tests as something separate is part of the misconception. Tests show you how the code fits together and whether your design is good. If you say testing is difficult, you are actually saying the design is difficult. That is useful feedback: &#8220;What do we change so this becomes easier?&#8221;</p><p>A practical goal to hold is that in six months&#8217; time it should be easier to work on this codebase than it is now. That will involve more than just testing. It will involve changing code, build settings, and all kinds of small things. You are trying to improve the overall situation. Another way to prioritize is to &#8220;ask the system itself.&#8221; Treat the legacy system as having a body of knowledge and let it tell you what to focus on. If you have a million lines of code, a team of ten is not going to transform it overnight, so do not try. Instead, look at the system&#8217;s history. What changed? What keeps changing? Look at the parts of the code that change most often.</p><p>It does not matter whether those parts are changing for good reasons or bad reasons. If they are changing frequently, that is where you want to improve both testing and developer experience. If you are frequently changing something, you are more likely to break it, and you are also more likely to benefit from making it better. Parts of the system that are incredibly stable do not need the same attention. That does not mean they are automatically good. Some things are stable because they are terrible and people are frightened to touch them. But if they are not changing, they are no more broken than they were before, and they already &#8220;work&#8221; in the sense that people rely on them as they are.</p><p>So use the system to tell you what to change. The system already has an opinion, visible in its history and defect patterns. Do you have a heat map of where your defects are? That is where you want your tests. In that sense, you can use the legacy nature of the system constructively and positively. I think we often overlook that because it is not immediately obvious, but it is a very practical way to introduce more testing and TDD-like practices into a legacy codebase.</p><div><hr></div><p><em><strong>8: You have worked with a variety of programming languages, from C++ to higher-level languages like Python. Do you find that TDD plays out differently depending on the language or tech stack?</strong></em></p><p><strong>Kevlin Henney:</strong> Yes, it does, but not always for the reasons people might think. Sometimes it is more about culture than language features. Just as natural languages are associated with different cultures, programming languages have associated cultures, idioms, and toolchains. So you have the syntax of a language, but you also have the tools that are available and the habits that have grown up around them.</p><p>Culturally, testing as developer testing is far more prevalent in the Java world. There is nothing inherent in the Java language that makes it more amenable to testing than a language like Python, but testing is more likely to be present. That is because modern unit testing, at least in the popular sense, grew up around Java. The JUnit framework appeared in the late 1990s and was integrated with Eclipse. That made it normal for unit testing frameworks to be integrated into IDEs. Java was the language in which those practices and cultural habits were first formed. As a result, Java developers are much more likely to encounter JUnit and similar tools in an integrated environment early in their careers. In that sense, Java is &#8220;better suited&#8221; to TDD than Python, not because of the language itself, but because of the surrounding ecosystem.</p><p>Python, by contrast, does not have a single standard IDE in the same way. If you are working with Java, you are very likely using IntelliJ or a similar environment. If you are using Python, you might be coming from many different directions. If you are a data scientist, you have a different view of the world. Data scientists do not usually use Java; they use languages like Python. With Python you have people who consider themselves software developers, children who are learning to code, people who are scripting, people who are doing data science, and so on. There is not a single core culture, so you end up with disparate practices. Python itself also predates the period when automated unit testing became a strong habit. That is not to say Python developers do not test, but the cultural environment around Python does not have the same unified testing norm as the Java ecosystem. So in that sense, what you see with TDD or testing is often more about development culture, who is around you, and what information and tools are available.</p><p>If we move to C, or C for classic systems programming and embedded work, we see yet another culture. These are contexts where you are much less likely to find unit testing. If people are testing, they often test at the system level and not even at the integration level, let alone at the level of small units of code. So culturally that is an obstacle to TDD.</p><p>Then there are the language characteristics themselves. Python is a much &#8220;looser&#8221; language; it is dynamically typed, and that can actually make some aspects of TDD easier. I sometimes joke that when I am using Python, I do not need a mocking framework because Python is the mocking framework. Mocking frameworks were invented for statically typed languages like Java, where the language does not easily support meta-level behavior. Those languages are less elastic and less plastic. In Python, I can reshape almost anything. The language itself is a tool that can be used to modify itself. At that level, from a purely linguistic point of view, Python can make testing easier.</p><p>However, cultural habits can get in the way even there. For example, many Python developers, especially in more data-science-oriented contexts, have a habit of reading and writing files everywhere and accessing files in every function. That makes testing harder, and it is something I try to encourage people to stop doing. In C and C++, there are language constructs that encourage longer build times and more source-file dependencies. There are also design habits that do not lead to natural decoupling or obvious substitution points where you can say, &#8220;I can easily put something else here because the design allows it.&#8221; In those environments, you sometimes have to push uphill against the prevailing culture of the codebase to get to a design that is test-friendly.</p><p>So yes, languages can make TDD easier or harder, but only sometimes is that because of the language features themselves. Very often it is due to the surrounding culture: the design culture, the testing culture, and the practices that have grown up in and around that language.</p><div><hr></div><p><em><strong>9: The quality of tests is crucial in TDD. What are some best practices you recommend for writing good tests in a TDD cycle?</strong></em></p><p><strong>Kevlin Henney:</strong> That your tests should. So one of the techniques that I always think of is that your tests should be testing one concept, one idea. That does not mean they necessarily have just a single assertion, but they should have a single focus. What is the thing that you are trying to demonstrate? That should be easily summarized by the test name. This is one of those cases where naming something is not merely labeling, it is actually testing as design in this case, because it will cause you to create different tests if you use a different design approach or different naming approach.</p><p>My preferred habit is to use tests that are propositions. So let us just take this cup, for example. Some developers might say, &#8220;I have a constructor,&#8221; and they will write a method <code>testConstructor</code>, and <code>testFill</code>, or <code>testDrink</code>. What you are doing is you are just going through the shopping list of methods and writing a test, and you cannot test like this. There is no way to produce good tests using that technique. I actually do this when I run training courses. I show people that it is impossible to produce good tests using this technique. If you just go one method at a time and say, &#8220;I am going to write a test for this method,&#8221; you cannot test. You cannot write good tests like that, partly because, in order to drink from a cup, I need to create it.</p><p>So therefore I have already involved the constructor. I am not just testing the <code>drink</code> method. I also need to fill it, so I am using the <code>fill</code> method, and then I can drink from it, and then I need to determine whether or not it became empty. I have just used four different operations there. I am not testing a single operation, I am actually testing the interaction. This is why, when we look at the perspective from BDD, behaviour-driven development, that gives us a different way of understanding what you are after. You are after testing the behaviour. The behaviour is not just in a single method, it is the composition of different methods in different scenarios.</p><p>Another reason you do not want to end up doing <code>testDrink</code>, for example, is that I can drink in two different scenarios. I can drink from an empty cup, and I can drink from a full cup. That is not one test case, that is two. They are very different, and they have different outcomes. So the first thing is, if you are currently doing that, it is a huge test smell if I see that pattern. If I just see tests that are &#8220;here is a method, here is a test method that corresponds to it&#8221; &#8212; <code>testA</code>, <code>testB</code>, <code>testC</code> &#8212; you do not have the tests. It is as simple as that.</p><p>I always lay it down as a challenge to people: show me if you have any counterexamples. Nobody has ever been able to come up with a good example that contradicts that observation. What you need to be doing is testing behaviours, or in some cases we would look at it as testing a property. There is a fluid overlap between these approaches. You make a statement, a propositional statement. By propositional statement, I mean that we describe something and the way that it is.</p><p>&#8220;A new cup is empty.&#8221; &#8220;Drinking from a full cup empties it.&#8221; These two sentences are the test names. So literally your test name should be as easy to read as if it were a specification, which is what I said earlier. In other words, each test needs to be organized and thought of as a specification with an example. Here is the thing that we are showing. This is the expected outcome in this scenario. This is the behaviour or the property that we are entitled to, and that we are requiring at this particular point.</p><p>When we start looking at it like that, you suddenly realize your tests are not just a bunch of assertions with bits of setup. You are telling a story. You are describing the system from a specification-oriented point of view. You are giving people a series of logical propositions. If the test fails &#8212; if I say, &#8220;A new cup is empty&#8221; &#8212; that is a proposition. If that test fails, what does that mean? It means a new cup is not empty. I can tell immediately by looking at the test name what is wrong. I might not know why, but I know what. Whereas if I say <code>testConstructor</code> and that fails, I have no idea what that even means.</p><p>So the point is, your tests are units of meaning. Or, put another way, they are not just verification, they are communication. You are communicating actual meanings. If your testing philosophy is that you are just poking your code to verify it, you are going to end up with tests named after methods, or even worse &#8212; and I have seen this a few times &#8212; <code>test1</code>, <code>test2</code>, <code>test3</code>. Honestly, that is not going to help anybody.</p><p>You can always tell whether or not a team has really understood or has a good testing habit, because if they are testing like this, there is no way they have a good testing habit. They are doing testing as an afterthought. It does not feel good. I would not like to write tests like that, and if somebody said, &#8220;Kevlin, why are you not writing tests?&#8221; I am going to say, &#8220;Because it feels wasteful and it is annoying, it is frustrating.&#8221; If you adopt those practices, it is annoying. I would not want to write tests like that.</p><p>So test quality needs to be quite high; otherwise you are going to end up with unmanaged technical debt in your test base as well as problems in your code base. You do not want to double your problems, you want to reduce them. Your tests should be a clear explanation of what your system does in the detail, along with intention. For me, that is what I put under the heading of GUTs &#8212; good unit tests &#8212; and that is a term from Alistair Cobra. TDD does not miraculously cause you to do GUTs. You need to again realize that you are in the driving seat. Having a nice car does not cause you to be a better driver, and I think there are a lot of people who would benefit from that analogy.</p><p>Then you need to listen to your tests. What are your tests telling you? Your tests are telling you, &#8220;This is not cohesive.&#8221; Everything is bound up, and you have too much in one place. If you want to test a behaviour in that, or a related group of behaviours, then that related group of behaviours is its own module or its own class. Why is it hiding inside another class? This is design feedback. Again, sometimes the difficulty of testing comes from the difficulty of the code.</p><p>So I would say listen to your tests. My standard answer when people say, &#8220;How do I test the private stuff?&#8221; &#8212; my stock answer is that, generally speaking, you do not. That is a signal that you need to separate something out. You are not dealing with one idea, you are dealing with two ideas, and one of them is hidden inside the other. Pull it out. Do an Extract Class and focus on that. It is clearly important because you value it. You just said, &#8220;I want to test these behaviours.&#8221; You probably even have words and names for it, but it is hiding embedded inside another class. So give it first-class citizenship and extract it.</p><p>At the same time, I recognize that there is a point here. If I told you that and you have a major release tomorrow, that is probably not helpful advice from me. So that is why I do not say, &#8220;Do not test,&#8221; or &#8220;You should never test private stuff.&#8221; What I say is that you should take it as a signal, and you probably do not want to do that. So in those cases where we need a little pragmatism, I would say, &#8220;Yes, I am either going to weaken the encapsulation on the class in one way or another, but I need to put a huge deprecation, or, &#8216;This is technical debt I need to manage.&#8217;&#8221;</p><p>If I have ignored that warning three times, take it as a &#8220;three times and you are out&#8221; rule. If I keep coming back to the same code, and my colleagues and I keep coming back to the same code and saying, &#8220;Yes, we said we would fix this,&#8221; now you need to properly schedule it as a piece of work, because you are always working around it. You are not working with your code, you are working around your code.</p><p>That would be something I would say. Again, that is not really so much a tooling thing as understanding what your test is telling you. When it comes to mocking, I do not have any strong opinions, except that most people mock too much, rather than understanding that excessive mocking is an indication that you have a problem that you should be solving. Do not lean into it by adding more mocking. Lean into it by asking a different question: &#8220;How do I mock less? What rearrangement of interfaces or class responsibilities would make this easier?&#8221;</p><p>I generally think that people use too much mocking anyway, even in quite good designs. There are simpler ways of looking at it, and they confuse themselves. So you end up with a lot of mocks and a lot of mock noise, which is not to say that mocking is not useful. It is just that most of the time, I think the guidance I gave to one team years ago still holds: if you are not mocking, you probably need to learn how to mock. Learn how to mock. But if you are already mocking, you probably need to learn to mock less.</p><p>I do not have specific feedback on mocking tools, except to say that sometimes I do not find them particularly necessary because of the language. I made the comment about Python earlier. In some languages the language itself is effectively the mocking framework. So for me the emphasis is less on specific tools and more on what your tests are telling you about your design, your responsibilities, and your coupling.</p><div><hr></div><p><em><strong>10: Does tooling make or break the TDD experience?</strong></em></p><p><strong>Kevlin Henney:</strong> If you are able to establish the workflow, and the code that you are working with has the right properties, or you are moving in the direction of it being loosely coupled and highly cohesive &#8212; you are using good, classic design practices to organize your code &#8212; then you are going to get most of the experience that you need, and that will not change too much between testing frameworks.</p><p>I used a technique years ago where I would get people initially testing with just plain asserts, just a straight assertion, whatever is available in the language or library, without using a testing framework. Then I would get them to refactor towards a framework. That actually turns out to be quite useful. One company did this for their C and C++ code and actually created a framework that they then controlled, which was very useful for their embedded environment. It is not something I ever really did with the Java folks, and I occasionally do it, sometimes as a bit of fun with Python. But I do not do that very much anymore because these are solved problems.</p><p>The point of that exercise was to show people that, first of all, the fundamental ideas in a testing framework are not too complex, but also that you would be surprised how little you need to get a testing workflow. But that said, I like to have a testing framework that supports a number of basic features. Obviously, when a test fails, I want to continue with the rest of the tests. I want to be able to have parameterized tests so that my tests can be data-driven.</p><p>Any testing framework that does not support that in 2025 is, in my view, an interesting beta, but it is not yet a proper testing framework. It is a 2000s testing framework. I like to have a testing framework that allows me a way of organizing and grouping tests easily. These features streamline the overall testing experience, but they also allow you to have more expressive tests.</p><p>Whether that makes or breaks TDD, I do not think it goes quite that far, although I can imagine being sufficiently frustrated in some cases that it would break. However, I think good tooling improves the experience dramatically, and if you get a better experience, you are going to do more of it. There is something to be said for that: good tooling can encourage better habits and more frequent testing.</p><p>In terms of specific tools or frameworks that I personally like using for TDD, I have mentioned some of them already a couple of times &#8212; the ones that I think flow best for me. Obviously some of these are going to be a matter of personal experience. If I am using Java, then JUnit 5 is my choice, and that is actually a little bit different from JUnit 4. I found JUnit 4 got in my way a little bit, but JUnit 5 has just enough that allows me not to be working around the framework.</p><p>In the C++ space, I mentioned Catch as the framework of choice. I would also encourage the use of Catch for C. In other words, if you are in an environment where you are doing the production code in C, do your testing in C++, because the tools are generally more powerful. That is a common pattern anyway, but I would use Catch there. It allows you to be much more specification-oriented.</p><p>There is no surprise, I think, if I say that I am comfortable using Jest with TypeScript and JavaScript. With C#, I have already mentioned NUnit as being the one of choice. Occasionally I will do work in languages where I have less familiarity with frameworks. I did something for a client a couple of years back in Ruby and we used RSpec, and I was quite impressed with RSpec. It had been years since I had used it. I found it still a little limited in some senses, but I also found that I could create some really nice testing approaches with it.</p><p>My opinions on that and other frameworks are slightly less strong, but those are the ones that normally stand out. There is nothing unusual in that list. The key point is that the workflow and the design properties of your code matter most, and the tooling, when it supports that well, can significantly improve your TDD experience.</p><div><hr></div><p><em><strong>11: Putting TDD in the context of overall testing, how does TDD fit with other testing practices on a project? You have talked about it and hinted at this briefly throughout, but if you were to just focus on this aspect, how do you see it?</strong></em></p><p><strong>Kevlin Henney:</strong> Yes, I think normally when we talk about TDD, we tend to lean towards the unit testing side, because that gives us the fastest feedback cycle. There is no single standard definition of what a unit test is, but the one that is perhaps most widely used and accepted is very much about the isolation question: can I isolate a piece of code from external dependencies, external runtime dependencies?</p><p>If the answer is yes, then that is a unit. It is not about language constructs. It is not &#8220;is it a class, is it a module, is it a function,&#8221; whatever. It is about isolatability, and the idea that I am not going across a significant boundary of communication. I am not hitting the file system, I am not communicating with another service. The idea is that I am contained and therefore, as a nice consequence, it is going to run fast, but also I control everything about it. You do not control the file system, you do not control the network. Those are outside your control. They may be under your influence, but not your control. So if we define &#8220;unit&#8221; from that point of view, then we get a fast feedback cycle and it tells us something about the internal structure and looseness of coupling and the strength of cohesion of what we are building.</p><p>From that point of view, it feels like TDD is very much in the unit testing space. But that said, exactly the same workflow works for integration tests. There is nothing different there. You can use the same workflow. All of the same test recommendations pretty much apply, but I will probably be using other aspects of my unit testing framework. For example, if I can pull in data files, then it starts becoming a little more serious. It just means that when a test fails or when a test passes, I cannot guarantee that the reason it passes or fails is only to do with correctness of code. If your network is down and what you are testing involves wandering across the network, your test will fail and it will not be because your test is wrong or your code is wrong; it will be because the outside world is wrong. It is to do with the nature of feedback, and it will also be a bit slower. But everything else about it&#8212;the sensibility, the mindset, the structure, the naming, the partitioning&#8212;all of these other things are kind of the same.</p><p>Then we hit things like acceptance test driven development, ATDD, which I always find difficult to say. It is a lot easier to write it down. Acceptance test driven development is where we are actually looking just beneath the UI skin of an app, so potentially very much end-to-end without UI interaction, or at an integration level, but the idea is that it is still code on code and we are doing that. The idea with that is that clearly the small iterative steps will not be as small. They are probably very much more at the feature level. We are looking not at minutes to hours, but hours to days before a certain test may pass, and that is acceptable. The same sensibility applies. This is also where many people will associate behavior driven development, although behavior driven development is a philosophy that also applies to the unit tests. For many people, they think of BDD in this higher-level space. I want to be very careful to say it is not just in that space; it is just the way that many people approach it. But again, that can lead you to structuring your workflow in the same way, so you can see there is a sympathy between many of these kinds of testing.</p><p>We can also look at other forms of testing. Contract testing is where we would say we are testing external code. Historically I call that conformance testing. For me, contract testing is what you always do because you are testing the contract of the class; you are defining it. It is not about third-party code, but the term has come to mean code that is external to the code that you are writing and that you want to test conforms to your expectations. I think that is a really important point because it is complementary; it is not the same. It addresses an issue that sometimes people have when they are writing TDD. Let us say, for example, that I am using somebody&#8217;s cup framework that I have obtained from GitHub, and maybe I have had some bad experiences with that framework in the past and there were some bugs in it. That is annoying, because I am trying to focus on my code, but I am discovering bugs in somebody else&#8217;s third-party code.</p><p>The problem is that you end up putting an extra little test into your tests just to check that the bit that broke before does not break again, and so on. You end up with a lot of tests that are &#8220;drive-by&#8221; tests. In other words, you are testing your thing, but you are also testing this other thing. Do not do that. Your test has now got mixed responsibilities. You want to separate that. That is where contract or conformance tests fit in. The idea is that you want to say, &#8220;Everything we depend on that might cause a problem, we have tests that check that.&#8221; If those tests fail, we do not even bother running our tests, because there is no point. If the foundation of what we are building on does not work, then why are we even going to bother testing our code, because the foundation is already broken. So the idea is that is a clear separation that is written in a much more what we might call defect-driven style rather than test-driven style: &#8220;This is not working,&#8221; or &#8220;It was not working historically,&#8221; so I will write a test to make sure the latest version, or the versions we use in future, are always working.</p><p>We may also have other tests like performance tests. Performance tests are typically going to be something slightly different because they follow different experimental design. They have a different workflow. If I drink from a full cup and it does not empty it, then that is a bug straight out. But if I say that we have a particular availability or there is a particular performance limit, then statistically we may find that sometimes when we run the test we pass and sometimes when we run the test we do not, because of the way the operating system schedules and so on. We are not dealing with something that is simply about true and false. We are dealing with something that is better or worse, and there is a kind of grey area. We really want 90 percent of the time to be in this performance space and we will tolerate 10 percent outside it. That suggests that the nature of our test requires a different philosophy. We can pass and fail at certain limits, but not in the same way, and we do not just run it once and say, &#8220;That passed.&#8221; We need to draw from different samples, sometimes scaling-based samples. Those tests feel very different in that sense. Again, they are complementary, but not in a way that fits with our TDD; it is actually quite separate. They are testing behaviors that are outside the basic semantics. They are testing performance characteristics and so on, and that requires a different mindset, I feel.</p><p>So for me, TDD sits largely in the automated developer testing space, mostly centered on unit tests, but the same workflow and sensibility extend into integration tests and acceptance-level tests. Around that, we have complementary practices such as contract or conformance tests, characterization tests for third-party APIs, and performance tests with their own experimental mindset. All of that lives together in the broader testing picture on a project.</p><div><hr></div><p><em><strong>12: Maintaining TDD discipline under pressure: from a team leadership perspective, how can leads or senior developers encourage the team to stick to TDD when deadlines are tight or when people feel tempted to &#8220;just code it and test later&#8221;? Are there any habits or cultural practices that help sustain TDD in the real world of rapid timelines?</strong></em></p><p><strong>Kevlin Henney:</strong> Yes. I think the thing is, again, it comes back to whether or not it is your idea, whether or not you feel you own that idea. If TDD is something you only do when the team leader is in the room, and the minute they walk out of the room you stop doing it, then you do not have it. Your team is not doing TDD. It is a kind of performative TDD. You are doing it because you are supposed to, and that is understandable. But it means that the minute you feel any kind of pressure, you are going to throw that out of the window. We see this with a lot of different practices; it is not unique to TDD.</p><p>The point is that you need to get to the point where it is a habit and you embody it. You know, &#8220;This is what we do.&#8221; Also, if you have enough experience, you start realizing that the minute you start throwing out certain disciplines, you are going to pay for that later. This is where our managed technical debt comes from. It does not come from some kind of magic genie that pops into your code. Well, actually, maybe agentic AI can reduce the quality of your code while your back is turned, but the point is that technical debt does not magically appear in your code. You know it got there for a reason.</p><p>People often like to say that certain practices only work in certain cases. Honestly, there is a truth to that, but the chances are that whatever you are working on is not so special that practices you normally find useful suddenly stop applying. If you are already finding TDD useful, lean into that. Lean into it a bit more. If you are not, then that is a different discussion. But from the team lead perspective, the job of a team leader is not a controlling role. It is a leadership role. Leadership is not about managing; it is mostly about example, about enabling, about making people see opportunities and making it somehow easier for them to try the right thing than to try the wrong thing.</p><p>In some cases, TDD may be a good thing for them. That is great. How do we make that feel like it belongs to them and it is their practice, not the team lead&#8217;s practice? Not the organization&#8217;s practice, but my practice. How do I make it my practice so that when I start on a new team, that is how I work? When I go for an interview, that is how I describe what I do, because it is my practice, not the team&#8217;s practice or the organization&#8217;s practice or the team leader&#8217;s practice. This is not a practice that belongs to that person or that entity; it is my practice.</p><p>For me, that is the skill, which means that there is no easy answer. I am afraid if anybody is watching and hoping for an easy answer, there is not one. But that is the skill and also the subtlety in it: moving from performative compliance under pressure to a place where TDD is something the developers feel they own, something they practice because it helps them, so they are less likely to abandon it when deadlines are tight.</p><div><hr></div><p><em><strong>13: AI can draft tests fast, but quality is uneven. What acceptance gates&#8212;e.g., minimum mutation score (PIT), property-based invariants, automated test-smell checks, and explicit review rules&#8212;would you require so they increase fault detection? How would you enforce this in CI to scale safely?</strong></em></p><p><strong>Kevlin Henney:</strong> I would actually take a step back before talking about specific acceptance gates and ask why you are using AI in the first place. Why are you using AI to generate tests? What problem are you trying to solve by doing that? A lot of teams cannot answer that question. They say, &#8220;We are using AI because we were told to use AI,&#8221; and then we are right back at, &#8220;You were told to do stuff; this is not your practice, this is somebody else&#8217;s practice.&#8221; For many people the story is, &#8220;We do not have many tests,&#8221; so now they generate a lot of tests with AI, but they do not understand those tests. They do not know what is being tested, or whether the tests are correct.</p><p>Recently I wrote a blog post called &#8220;Think For Yourself,&#8221; where I gave people four things to consider whenever they want to integrate anything that is AI generated into their code base. The first question is: does it work? Do the tests pass in a way that gives you confidence that they are actually verifying the right behavior? The second question is: do you understand the generated code or tests? If you do not know that something works, or you do not understand it, step away. Do not pretend that you are being productive.</p><p>There is a common illusion here. Somebody will say, &#8220;AI has boosted my productivity,&#8221; and then you ask them how they know. How are they measuring that? The answer is often that they are not measuring it. They just have the feeling of speed because the AI produces a lot of stuff in ten minutes. Then they spend the rest of the week fixing it. That is not productivity; that is the creation of legacy. In my view, one working definition of legacy code is &#8220;code written by somebody else.&#8221; AI-generated code fits that definition perfectly. I am not saying, &#8220;Do not use AI.&#8221; I am saying that good use of AI requires more understanding, not less, and that requires tests and review.</p><p>The number of times I have been fooled or could have been fooled if I did not have tests is significant. That is why I write my own tests. I do not trust AI to do a better job than I can, because I still have to explain the behavior I want. In the time it takes me to explain that precisely enough to an AI, I could have written the tests myself, and I would know exactly why I wrote them and what design decisions they encode. AI might be useful for generating certain coverage-oriented tests in situations where coverage is very poor, but even there I have questions. If I am using AI to generate tests as well as code, I must spend most of my time reviewing, and reviewing is a skill that many people do not have.</p><p>I spent years learning how to review: fiction, non-fiction, technical material, books, articles, and code. Code review is not &#8220;I glanced at it and it looks good to me.&#8221; That is not review. If you do not understand the generated code and tests, you have a problem waiting to happen. The upside is that you can treat AI as a teacher as well as a generator. If you are using AI-generated tests, ask yourself: do you understand what your code is doing when viewed through those tests? What can you learn from them?</p><p>Then there is the question of taking control. What is the difference between the generated code or tests and what you would have written? If you were to write that test yourself, what would you have done? They might be similar, but they will often be different. Understanding that difference is an education. Sometimes I look at the generated result and think, &#8220;That is a really good way of doing it.&#8221; In other cases I look at it and think, &#8220;That is not a very good way of doing it at all.&#8221; Either way, I have learned something.</p><p>So my final recommendation in that space is to add one more gate: can you think of at least one way to improve what has been generated? Do not treat AI output as something that is simply &#8220;good enough to accept.&#8221; Treat it as a starting point. That gives you two big groups of questions. The first is: why are you using AI to generate your tests? Do you have a clear understanding of the benefit and how you will measure that benefit? If you do not, do not do it. Being busier is not the same as being productive.</p><p>The second group applies if you do decide to generate code or tests with AI. Use this list of four gates: does it work, do you understand what you have, what is the difference between what AI produced and what you would have done, and can you think of at least one way to improve it. If you habitually apply those checks, you will learn a lot. You will be using AI as a possibility generator, not as an autopilot. You will be interacting with it, passing judgment, using your design sense, and either accepting the result because you know why it is good, or changing it because you know what is wrong and how to fix it.</p><p>In other words, you turn AI into an assistant or a coach. The problem I see at the moment is that many people are backseat drivers with AI. They have no idea what is being generated on their behalf. They do not understand what is being tested. When they have to fix an issue or extend the code, they discover that they do not know enough, and it takes them longer. They are not using AI in the right way.</p><p>So my general advice is this: be crystal clear about why you are doing something, especially with tests. For me, the strength lies in you writing the tests, not in outsourcing them to AI. Tests are your executable specification and your feedback loop. If you hand that over to a tool without understanding or review, no mutation threshold in CI will save you. You may use metrics and gates in your pipeline, but the real acceptance gates are still the human ones: clarity of purpose, understanding, comparison with your own judgment, and deliberate improvement.</p><div><hr></div><p><em><strong>14: Finally, looking forward: What do you see as the future of TDD and automated testing practices? Are there emerging trends&#8212;perhaps in tooling (like property-based testing, AI-assisted test generation) or in process (like BDD, Continuous Deployment practices)&#8212;that you believe will shape how experienced developers approach TDD in the coming years?</strong></em></p><p><strong>Kevlin Henney:</strong> If you do not already have an automated testing habit, now is a very good time to start. With AI in the mix, you will find that generated code sometimes fails in ways that are quite intuitive, where you look at the mistake and think, &#8220;Yes, I can see how that happened given the training data.&#8221; In other cases, the mistakes are very odd: failures where you think, &#8220;Why would you ever do that?&#8221; You need to become better at testing to deal with both.</p><p>Interestingly, this is something I was saying years before large language models. Around 2016 or 2017, at a conference in Poland called MobiConf, somebody asked me about the future of AI. At that point everyone was guessing about where AI would go. My answer was that you need to get better at testing. That is still my answer. The more AI we add, the more testing skill we need. I do not want AI anywhere near company-critical code without tests and without proper reviewing skills.</p><p>So one part of the future is skills. You need to get good at testing, and you need to get really good at reviewing. Reviewing is not just a testing skill; it is a design skill. You cannot review code effectively unless you have deep design experience, which means you also need to learn to code. Coding remains relevant, because otherwise you do not know what you are looking at. This is not about language tricks. It is about familiarity with a kind of precision that you normally only see in areas like science and mathematics. Those are the skills you want to build, and they sit in the same space as the precision and specification thinking that testing requires.</p><p>In terms of the specific workflow of TDD, I find it hard to make strong predictions. My sense is that TDD adoption will always be relatively low compared to the overall population of developers. As things stand, many people still do not test at all. There is a significant and increasing proportion of developers who do test, and that has changed over the last couple of decades. The needle has moved. Within that group there is a smaller subset who will try TDD, or have TDD as part of their toolkit and can employ it when appropriate.</p><p>I would like that number to go up. Leaving AI out of it for a moment, I think TDD is a good practice. I have thought that for a long time. It is helpful because it encourages incremental thinking and clarity. Done with the right sensibility, it leaves behind something worth inheriting, rather than something people curse you for.</p><p>If you bring AI back into the picture, those same qualities remain valuable. Clear tests, incremental feedback, and a strong sense of specification help you reason about AI-generated code and about changes in general. I would like to think that AI might even increase the uptake of TDD, because it will force people to confront questions about correctness and understanding more directly. But whether that happens, and to what extent, is difficult to predict.</p><p>So my view of the future is less about a specific new tool or fashionable acronym and more about emphasis. We will see more AI and more automation, but the teams that thrive will be the ones that double down on testing skill, review skill, design sense, and the ability to work with precise specifications. TDD is one of the workflows that aligns naturally with that direction, and that makes it a practice that will continue to be relevant, even if it never becomes universal.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/p/rethinking-test-driven-development?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/p/rethinking-test-driven-development?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/p/rethinking-test-driven-development/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/p/rethinking-test-driven-development/comments"><span>Leave a comment</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Architecting AI Software Systems for the Real World: A Conversation with Imran Ahmad]]></title><description><![CDATA[Designing scalable, sustainable AI&#8212;from lab prototypes to production systems]]></description><link>https://deepengineering.net/p/architecting-ai-software-systems</link><guid isPermaLink="false">https://deepengineering.net/p/architecting-ai-software-systems</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Wed, 03 Dec 2025 11:18:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/DVPZyn0WpPY" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI systems are now everywhere in software, but turning a promising model into a reliable, cost-effective, and sustainable product is still hard work. Teams are discovering that &#8220;just add a model&#8221; is not enough; you need end-to-end architecture that can take an idea from a lab-style proof of concept to a production system that meets real constraints around cost, latency, security, and operations. </p><p><strong>Imran Ahmad</strong> is a data scientist, educator, and author focused on algorithms, AI, and cloud computing. He leads machine learning projects for the Canadian government, teaches at Carleton University, and is an authorized instructor for AWS and Google Cloud. With Packt, he has authored <em>40 Algorithms Every Programmer Should Know</em> (2020) and <em>50 Algorithms Every Programmer Should Know</em> (2023), and <em><strong><a href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469">Architecting AI Software Systems</a></strong></em> (2025, with co-author <strong>Richard D Avila</strong>) and the upcoming <em>30 Agents Every AI Engineer Should Know</em>. Outside of work, he enjoys photography, biking, and mentoring developers through his Discord community and workshops.</p><p>In this conversation, we dig into how Imran thinks about AI architecture in practice: from the fundamentals of good software architecture and elastic cloud patterns to the &#8220;five pillars&#8221; he uses to evaluate AI systems&#8212;security, reliability, performance, efficiency and cost optimization, sustainability, and operational excellence. We discuss separating data and compute for sustainability, designing differently for heavy training workloads versus real-time inference, and avoiding hard coupling to any single AI or cloud vendor. Imran also shares his perspective on agentic AI and agentic RAG, what changes as AI becomes a core concern for software architects, and why UX, cross-functional collaboration, and long-term operational thinking are now central to successful AI systems.</p><div id="youtube2-DVPZyn0WpPY" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;DVPZyn0WpPY&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/DVPZyn0WpPY?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h1>About the Book</h1><p><em><strong>1: What inspired you to write Architecting AI Software Systems now, and what gap in the industry or knowledge are you hoping to fill with this book?</strong></em></p><p><strong>Imran Ahmad:</strong> When you design AI systems, or when you have a Gen AI solution, you have to have an end-to-end solution, so you have to look at that from its totality. What happens is that whenever there is a new technology, whenever there is a new idea&#8212;a technical idea&#8212;we start by focusing on depth. We develop solutions, we experiment with them, we iterate through different versions of them until we are ready to use them for solving large-scale problems&#8212;so, you know, beyond solving those cats-and-dogs pictures, differentiating between cats and dogs.</p><p>Now AI has come a long way. When we have gone through our development processes, they have matured. Then we need to deploy these solutions. These AI solutions are only useful when they can solve a problem in production, and when you bring these ideas to production then, from start to end, they need to work properly, and around that you have to design architecture. I will talk about this as well&#8212;how you quantify a good AI architecture. There are five pillars, as we call them; I will talk about that later: security, reliability, performance, efficiency, cost optimization, sustainability, and something that is perhaps the most important, that is operational excellence. I will talk about that later, but the need for this is that we are bringing these ideas to production. That can only be done if we are designing, if we are giving it proper thought. We are bringing all the best design patterns to the plate, and this is something that motivated me to write this book.</p><p><em><strong>2: Your book takes a very practical, architecture-centric approach to AI, doesn&#8217;t it? It mentions a structured journey with real-world examples, hands-on exercises, and even a fictional AI system&#8217;s architecture as a learning tool. So can you give us a quick overview of the key themes or unique features of this book?</strong></em></p><p><strong>Imran Ahmad:</strong> So the way we have designed this book is that we have essentially divided it into two parts, and the first part is about the fundamentals&#8212;about the fundamentals of architecture. If you look at the first part, it zooms out and talks, in general and not very specifically about AI, about what the principles for good architecture are.</p><p>Of course we are talking about AI, but we start with the fundamentals of AI systems, and this is where we define terms. We define microservice architecture, and we discuss a couple of actual use cases. Then we also define terminology like data lake, what a data warehouse is, and why AI is so important in the context of designing AI systems.</p><p>When we bring these systems to cloud computing, then you have elastic architectures, and as soon as you move to elastic architectures, your system can become cost-effective and performant at the same time. If you think about this, usually the performant systems are not cost-effective. It is like you buy a Ferrari. Ferrari is performant; it is one of the expensive cars, one of the fastest cars&#8212;but Ferrari is expensive. So we are trying to buy a Ferrari at the cost of a Toyota Corolla. We want systems that are cost-effective and performant at the same time, and elastic architectures get you there, where your systems can expand and shrink based on the immediate needs. This is where we talk about why cloud computing is so important. This is chapter one.</p><p>Then we talk about a case of architecture. You know my co-author, Richard, he is a great architect. This is where he brings his 30 years of experience, and he talks about the role of the architect, the vision, the style, and, most importantly, he talks about what the implications are if the architecture is not properly designed.</p><p>Then chapter 3 is about bringing software engineering into the picture, and we see: OK, what are software-engineering-specific topics related to architecture? So this is part one&#8212;part one is done. Part 2 is about AI systems. It is specific to AI: what are the architecture templates, what are the architecture philosophies that are relevant to AI systems?</p><p>Here we talk about something that is called &#8220;concept of operations,&#8221; and we talk about how the concept of operations is relevant to AI systems. Then we talk about, as you mentioned, certain use cases&#8212;large-scale use cases. We go over a complete use case so that, if you want to design a RAG system, you know how you can use the ideas that we have developed in this book and how we can apply that to an actual use case.</p><p><em><strong>3: What key skills can readers expect to gain from the case studies and exercises, and also perhaps changes in mindset?</strong></em></p><p><strong>Imran Ahmad:</strong> So the first skill is how to convert an idea into a design. This is skill number one. Requirements capture the idea in a formal way, but requirements are usually written by a non-technical person. The first part of the solution is to convert those requirements into a technical design&#8212;that is the architecture. Skill number one is what they will learn.</p><p>Skill number two they will learn is that there may be surprises. The design that you came up with may not be the perfect one. You have to iterate through that. You come up with something and it may or may not work. Now you need to find ways to differentiate between a good design and a bad design. Functionally, they will work; the functional requirements are met. It is the non-functional requirements that differentiate between a top-notch design and a design that is not that great. This is about looking at how cost-effective your architecture is, how performant it is, how secure it is, how reliable it is, whether you are using the principles of sustainability, and whether you are bringing operational excellence.</p><p>Operational excellence is a concept that we have discussed in this book. Operational excellence is that, when you are designing these architectures, you are looking long term. You are using YAML files. You are using orchestrators. You are using JSON files. You are using parameterization whenever possible so that you can reuse them and you can maintain them. And you are again looking at the long term.</p><p>So this is where the second skill they will learn is that your first attempt at the design may not be the best one. You have to rethink; you have to evolve. You have to quantify how good your design is, and then, before you actually start implementing that, it is a good idea that you start with a pilot project. A pilot project is something that is usually one-tenth of the scale of the original project, but it actually covers all the critical points, all the critical design parts&#8212;they are there. It validates that, and then you basically go towards the full-blown solution.</p><h1>Key Challenges in AI Architecture</h1><p><em><strong>4: In the current context, many teams struggle to turn AI prototypes into reliable products. From your experience, what are the main challenges in bridging the gap between a promising AI demo and a production-ready system?</strong></em></p><p><strong>Imran Ahmad:</strong> Yeah. So you can look at it from three aspects. Let&#8217;s look into that. It is a very important topic as well when you move towards an AI solution. Initially, you feel that it is a silver bullet; it can solve any problem, so that is when you are experimenting with it. But the success of an AI project depends on three factors. The first is cost, the second is performance, and the third one is accuracy.</p><p>Let me explain that. Usually, you are applying AI to an existing problem. You already have an alternative solution; you know that things are already working. Now you are upgrading that and bringing AI into the picture. You want to do things in a new way. So, while you are moving to this new world, if I can call it that, first you need to quantify what the effect on the cost is&#8212;whether the investment in the AI, the initial investment and then the running expenditures, can be justified by the aggregated cost saving that you expect to get. This is point number one.</p><p>The second one is&#8212;I said performance, but it is actually time. Your current processes&#8212;whether they will be optimized in certain ways, whether the time to get things done, to meet some of the requirements&#8212;will they be done in a more timely way? Let me give you an example here. If it is a bank manager looking at people applying for a mortgage and that bank now wants to use AI, then perhaps instead of taking that bank manager four hours, it will take four seconds. Now this is the time that has been saved. So this is the second dimension.</p><p>And the third one is accuracy. Whatever your current systems and current processes are, now this new world, the new ideas that you are introducing through AI&#8212;whether that will be more accurate or not&#8212;you have to basically look at that. In these three dimensions you have to make some progress. Maybe it will be more costly, but if you can justify it in terms of time and accuracy, then you may be able to sell the idea to the senior management.</p><p>I have encouraged that this is something that we should do right at the beginning. It should not be an afterthought. I have seen that people come up with these AI architectures, the architecture is implemented, and then they do some sort of time and motion and try to see whether they justify it or not. By that time, you have already implemented; you have already invested; you are already using that system. So it is more like you buy a car. Let us say that you buy a plug-in hybrid, you have already paid the money, and then you go to check and see whether it made sense or not. So you need to basically check that before buying a car.</p><p><em><strong>5: How can software architects specifically help ensure an AI proof of concept scales into a sustainable real-world solution?</strong></em></p><p><strong>Imran Ahmad:</strong> OK, now, sustainability is handled at two levels. If you are using cloud computing, then maybe it is not your job &#8212; this is the job of the vendor at AWS or Azure or Google Cloud. But still you can do a lot. For example, if you are using virtual machines, even if you have subscribed to virtual machines, in all of these cloud offerings, if you pay a fee, you can keep running these virtual machines 24/7, and you pay a flat fee. The same goes for the servers that you are running in-house. Now what you need to think about is that these are performance-hungry, power-hungry machines.</p><p>The second thing is that these days a good design is where we design the compute dimension in an ephemeral way and the data dimension separately. So there are two dimensions: when we talk about the architecture of these systems, there is a data dimension and there is a compute dimension. The design pattern that we suggest is that, first of all, there should be clear bifurcation, so the data and compute dimensions should be separate. The data dimension should be long term &#8212; it means your data is stored there for two or three years. The compute dimension should be ephemeral; it should be temporary. When there is a need to process the data, you provision the compute dimension, you process the data, and then you suspend it or just remove it.</p><p>Let me give you a simple analogy. All of us work in our favorite word processor like Microsoft Word. When you open Microsoft Word, the file is usually already there. Let us say that you are working on a research paper: the file is there, and whenever you find time, you open Microsoft Word, you work on the file, you store it, and then you go back to your work. So in the data dimension, your paper is stored for those four or five years, perhaps on your hard disk. But the compute dimension is your word processor. Whenever you want to change the paper, you open the word processor &#8212; for example, Microsoft Word &#8212; you change it, and once you are done, you close Microsoft Word. Now we can have the same design pattern for AI systems.</p><p>So it means that whenever there is a need to change something, whenever there is a need to train a model, whenever you need to change the processing pipeline or you want to process the data, you provision your compute dimension. The compute dimension should be need-based and the data dimension should be long term. If you follow this clear bifurcation between data and compute dimensions, our AI system will be cost effective, it will be performant, and it will also meet the needs of sustainability.</p><p><em><strong>6: There&#8217;s a concern about the sustainability and vendor lock-in of today&#8217;s AI platforms. For example, Open AI reportedly reached 10 billion in revenue but is losing around 5 billion a year, a situation some have dubbed a &#8220;subprime AI&#8221; crisis. Now, if enterprise architects build around such providers, they face continuity and lock-in risks. According to you, how should architects mitigate these risks?</strong></em></p><p><strong>Imran Ahmad:</strong> OK, so wherever there is a choice, do not use the proprietary vendor-specific APIs. Almost always we have two choices. You can use the vendor-specific APIs, or you can use a higher-level generic API. I will give you a specific example. When you are working with large language models, you can use the APIs that are provided by OpenAI &#8212; this is choice one, and because you were talking about OpenAI, let us go with that example. The second choice is that you can use LangChain. LangChain is an orchestrator. If you use the LangChain API, then what will happen is that your code will be talking to LangChain, and in LangChain it will be talking to the OpenAI-specific API.</p><p>Now let us look into a scenario that is unlikely, but can happen: let us say that OpenAI goes bankrupt. If the code is not directly talking to OpenAI, the connection between LangChain and OpenAI will change to perhaps LangChain to Gemini or LangChain to Claude. That is all that is needed. Your code is not dependent on OpenAI. Now, you can repeat this design pattern for the clouds as well. For example, if you are using cloud and you use open source APIs, then it means that, let us say you are using Docker containers. If you are using Docker containers, then your cloud computing is just a living space for your Docker containers. If you are using Kubernetes as an orchestrator for Docker containers, that is even better.</p><p>So it means that all you need is that your cloud computing platform becomes a Kubernetes enabler. Now, if one of those enablers goes bankrupt, all you need to do is move to a different one and you do not have to change even a single line of code. But in this approach we have issues as well. For example, all these vendors sometimes provide the best tools in their vendor-specific APIs. I will give you an example here. If you use Google Cloud, one of their most polished tools is called BigQuery. BigQuery is vendor-specific, and for AWS that is Redshift. Redshift is vendor-specific as well.</p><p>My recommendation is that still the risk of being hard coded to a vendor-specific tool is higher, especially at this point when things are changing so fast. We should be cautious, and we should be putting effort into being as vendor-agnostic as possible.</p><p><em><strong>7: Ensuring quality and maintainability in AI software is an emerging concern. Studies find many AI/ML codebases have minimal testing and documentation, often due to &#8220;lab-style&#8221; development by data scientists. And according to the State of Software 2025 report, only about 1.5% of the code in AI/big-data systems is test code (versus around 43% in traditional systems). Why do you think AI projects often end up with weaker software engineering practices? And what can architects do to instill better rigor&#8212;for example, would organizing cross-functional teams of data scientists and software engineers help bridge the gap and improve things like testing, security, and code quality?</strong></em></p><p><strong>Imran Ahmad:</strong> It is a new technology. In many cases, people are learning as they are implementing, and this is a byproduct of that. With mature technologies, the test cases have already been established, so we know from other similar projects what the criteria of success are, both in a functional and a non-functional way.</p><p>With something new, it is just working, and then we need to ask ourselves: what is the best way to test its functionality? For example, if hallucination is a concern, how do we test whether our solution is hallucinating or not? If accuracy is a concern, how do we test that? In a Gen AI solution, the metrics themselves are still evolving, and testing is all about quantifying whether our solution is meeting the agreed-upon goals or not. Those agreed-upon goals are still evolving, and that is one of the reasons, as you said, that these projects are not well tested. Yes, that is a concern, but things will improve.</p><p>AI is quite subjective in different ways, so a project may be successful for me, but for you it may be a failure. There is some subjectivity there, and as you brought up, one way of mitigating that is to come up with a consensus among people with different roles and different skills&#8212;a data scientist, a data engineer, a project manager, and perhaps a business analyst or a person who is in charge of production. They will have different views of the success of a project.</p><p>For a data scientist or an AI engineer, success is mostly about meeting the functional requirements. For a person who is in production, they may have no idea about algorithms, ROC curves, AUC, recall, or precision. For them, success is putting the Dockerized solution on a server and making sure that the non-functional requirements of reliability, security, performance, and availability are met. If it is an application for approval or refusal of a mortgage application, for the person who is in production it is all about whether the service is available or not, whereas for the data scientist it is all about the metrics related to data science.</p><p>So we have to bring them all to the table and come up with a consensus: what does end-to-end success for this project look like, both in development and in production? Whenever there is a problem, people do not have to agree on everything, but they have to speak out about what they think of the solution. Then they discuss, they understand each other&#8217;s world, and they come up with a consensus&#8212;a compromise. Once that is made, you follow that as the criteria of success. This is something that needs to happen, especially for large-scale projects.</p><h1>Designing Scalable and Robust AI Systems</h1><p><em><strong>8: AI systems must be built with scale in mind from the start. On the training side, deep learning models demand substantial compute (GPUs/TPUs) and efficient distribution of tasks. On the inference side, serving many users requires horizontal scaling, containerization, and load balancing to keep latency low. What are some architectural strategies you recommend to handle scalability for AI? How do you approach designing for heavy training workloads versus high-volume real-time inference in a production system?</strong></em></p><p><strong>Imran Ahmad:</strong> First of all, it depends on the problem you are trying to solve. The scalability requirements are different for different problems. Let me give you an idea. When you are training a model, this is where most of the costs are incurred. You need GPUs&#8212;GPUs are expensive&#8212;you need CPUs as well, and you need to experiment and train over and over again.</p><p>However, scalability requirements in development have two characteristics. Number one is that once the training is done, you do not need those resources anymore. You still need to retrain the model, of course, but if you are developing the solution for two, three, or four months and then your model is trained and in production, all those 20 machines that you brought in will sit there doing nothing. That is why cloud computing is really good there: you can provision resources, and once you are done, elasticity becomes important.</p><p>Point number two is that there is no hard deadline associated with the training process. At inference, there is a deadline. If you swipe your credit card, the fraud detection result needs to come back within a few seconds. If someone is paying at a restaurant, that person cannot wait for 40 seconds. So at inference you have those deadlines.</p><p>When you are training the model in development, there is no such hard deadline; it is more about your comfort factor. If you can live with evolving the solution on a scaled-down system during the daytime, then at night you can submit the full-scale training job before you leave for home. It runs overnight, and you come back in the morning and the solution is there. During the daytime again, you work on a scaled-down system&#8212;one-tenth of the size&#8212;and you evolve it. If you follow that pattern, you can save a lot of cost. You use the off-hours for training, and in that case you can use a much smaller number of resources. You need to be innovative and creative there.</p><p>The second part of the equation is scalability for inference. Now let us say the model is trained and put into production. We need to carefully analyze the scalability requirements there. We should not over-design; we should not under-design. Let me give you a couple of scenarios.</p><p>Again, take the example of a credit card. Each time you swipe the card, the result&#8212;whether it is a fraudulent transaction or a regular one&#8212;needs to come back in about two seconds. That is a hard deadline, so you have to make your servers performant enough to meet that deadline. On the other hand, imagine a bank manager who, at the end of the day, just needs to look at a spreadsheet of the transactions that went through and see how many were likely to be fraudulent so they can be reviewed. In that case, the requirement is &#8220;end of day.&#8221;</p><p>There, we do not need real-time endpoints. We can live with batch-mode inference and save a lot of cost. You do not need to provision real-time HTTP endpoints. All you need is to gather your unlabeled data and create a batch&#8212;at the end of the day, the top of the hour, the end of the week, whatever granularity works&#8212;submit it to the server, and it produces the labels: how many are likely to be fraudulent and how many are not.</p><p>So real-time inference is not always needed; if you use it everywhere, it is expensive and you may be over-designing the system. To get scalability right, you have to carefully analyze the requirements first and, based on that, design and architect the system.</p><p><em><strong>9: Integrating AI into existing enterprise environments can be complex. Teams often need to balance cloud-native AI services with capabilities within the customer&#8217;s current on-premises infrastructure so they can leverage existing investments and avoid disruption. How do you evaluate which deployment strategy is appropriate for a given project? What factors&#8212;for example, data sensitivity, legacy system constraints, regulatory requirements, or team skills&#8212;should influence whether AI systems run on-premises versus in the cloud or in a completely new environment?</strong></em></p><p>Imran Ahmad: OK, so there are two things here. First of all, I suggest that we carefully determine the maturity level. There are four maturity levels, and those maturity levels are about the technical infrastructure maturity and the skill maturity level as well.</p><p>Let us imagine a company. There are 30 people working in that company, and they are working on developing a product that deals with recommendations. It is a recommendation engine that recommends products to their existing customers, and they are using some algorithms, but now they want to modernize that. They want to use deep learning, they want to use Gen AI, and they want to use cloud computing.</p><p>The first requirement is that they cannot afford any disruption. So, first you need to look at what maturity level you are going for, but you also have the hard requirements that you have to use the existing infrastructure and you have to use the existing people. Then we have to develop a phased approach. Usually there are four phases.</p><p>Phase one is where we come up with the plan, looking at the current situation and deciding what the path forward is. This is where we start. In that phased approach, depending on the maturity level, we may say that in phase one perhaps we can move this part of the system and keep the other part on-premises. Using the example of that company, perhaps accounting can stay on-premises, but the algorithms can move to the cloud. That is one thing we can do.</p><p>Then we have to figure out how we are going to create a pipeline that can link the on-premises environment with the cloud. Usually what we do is keep redundant systems both on the cloud and on-premises, and slowly we test that and then we remove the part that is no longer needed on-premises. So this phased-out approach will be vertical, it will be company-dependent, and it will reduce the risk, and that usually works.</p><p>In some cases we do not have a choice. If you are working for a government organization or at a financial company, then sometimes there are regulatory requirements that your data cannot be on the cloud. There are three sectors&#8212;usually government, healthcare, and the financial industry&#8212;and in these three, some of their data needs to be compliant with existing regulations. It is not impossible, but it is more difficult for them to bring the data to the cloud. For government, sometimes it is not even possible to bring the data to the cloud.</p><p>Let me give you an example. There is a tool from IBM that is called IBM SPSS Modeler. Banks and companies in the banking industry are still using that. If your processes are dependent on that and it is working fine, you will not get the same level of comfort if you move to the cloud, because you are using a legacy system with a lot of embedded knowledge. All of that embedded knowledge will not be available, so now you are tied to your legacy system unless and until you are ready to retire your legacy software. There is no way you can move to the cloud.</p><p>Then sometimes what happens is that companies, when they say that they will move to the cloud, think mainly in terms of cost savings. I will give you an example here. The Canadian federal government, about four or five years ago, thought that they would move to the cloud, and they started that journey. The infrastructure to support the Canadian federal government is worth billions of dollars. They thought that they would save money, and the study was that it would save about 20% of the cost. That was the initial study.</p><p>Now, five years down the road, that did not happen. They moved to the cloud and now they have spent more money. Cost has increased by about 12%. That is the number. And there is a reason for it. The reason is that if you do not make a conscious effort, the simplest architectures in the cloud are not cost-effective. If you run a virtual machine 24/7, it will meet the functional requirements, but it is not elastic&#8212;yet that is the easiest solution.</p><p>That is why, throughout this talk, we have been talking about the case for architecture: taking a step back and spending some time there, because in the long run, in that example, if you calculate cost, initially they thought that the cost would be 20% less; it is 12% more. What that tells us is that we should not rush into the cloud. We should first understand what architecture we need, and once you have that clear architectural vision, then you implement that so that in the long run you are going to be saving the money.</p><p>If, in a hurry, you have already started with something like the easiest possible solution, it will be very difficult to change it down the road when you have already started your computing resources, you already have your compute dimension, data dimension, and functional dimension running. If you want to change it, it will be very difficult and risky. You are doing something that you should have done a couple of years ago. That is why there is a whole section in our book that talks about the case for architecture&#8212;why we should, and what is the need for system architecture for AI systems.</p><p><em><strong>10: User experience is a critical yet often overlooked aspect of AI systems. Even if the model is accurate, poor UX can block adoption. What can architects and designers do to ensure an AI system delivers a good UX and drives user adoption? For example, what is your view on using user-centered design practices or designing for diverse user needs such as voice UIs and accessibility features? Do you have any best practices for aligning AI architecture with great UX design?</strong></em></p><p><strong>Imran Ahmad:</strong> Yes. So for UX we should always be designing the system, we should always be thinking of it as a service. If you are a technical person and you have a spouse who is non-technical&#8212;or if you have a brother or sister who is non-technical&#8212;think about that person and whether that person can use this service or not.</p><p>My brother is a medical doctor, so I always think: OK, the eventual service that I will provide, can he use it or not? Sometimes what happens is that we bring too much technicality to the front. We are very impressed with our own algorithms, our own models, and our own infrastructure, but the end user is a non-technical person. They should not even need to know the details in the data dimension or the compute dimension or which models we are using. That all should be a black box once things are done.</p><p>It is a good idea to always try to see, from the eye of a non-technical person, how easy it is for a non-technical person to use it as a service. So think about it as a service. Your solution should be a service to the end user. There are different zoom levels. You can think of your solution as a microservice architecture. Now, microservice architecture is quite technical; it is great for providing abstraction to a data engineer, but not to the end user. We need to zoom out more.</p><p>I am into photography, so I give examples from zoom levels. Zoom out more and think of it as a service. At the highest zoom level the user just sees, &#8220;This is a service that helps me do X,&#8221; and everything else is hidden.</p><p>The example of that is that sometimes we are using AI without noticing it. The greatest example is when you use Google Maps. When you use Google Maps, it uses an optimization algorithm to get you from point A to point B. If you look under the hood&#8212;because my PhD was in algorithms&#8212;optimization algorithms are one of the hard areas. There is a famous example of the travelling salesman&#8217;s algorithm, and the travelling salesman&#8217;s algorithm is basically that you have a list of cities&#8212;city one, city two, city three, city four&#8212;and you try to find the optimal route. This is an NP-hard algorithm.</p><p>So it means that whenever you say, &#8220;OK, I want to go from point A to point B,&#8221; you do not know that under the hood there is a lot going on. First of all, your GPS location needs to be tracked. Then the destination needs to be there, and the traffic situation needs to be there&#8212;what are the real-time traffic conditions on each of the possible routes&#8212;so it is dynamic in nature as well. And then you reach your destination, and it asks you for feedback, and we do not even realize that for this simple use case there is so much power being used.</p><p>This is the best example. People use it. My daughter can use it; she uses it to go to her school. People will use it if they find the service easy to use, and we do not need to know what is under the hood. That is the UX.</p><p>And I will tell you the gap there as well, that I talked about earlier. In real time it needs to know that we are travelling on those routes, and the way it collects that information is that it assumes people are carrying those devices in their car, and if those cars slow down, it means that there is traffic congestion. It works most of the time. But where I live in the north, there is a place called Gatineau Park. It is about 80 kilometers long. People are biking there on their bikes, on their cycles, and their GPS devices, Google Maps, are being used, and Google Maps always thinks there is traffic congestion. It is always red. But if you go there, there is no one there. So there will be failures. It is not that algorithms always work.</p><p>Still, as a user, you trust it because of the overall experience: it is easy to use, it hides the complexity, and most of the time it works. That is what we should be aiming for when we align AI architecture with great UX design.</p><h1>Emerging Trends and Future Outlook</h1><p><em><strong>11: The rise of &#8220;agentic AI&#8221; is a hot topic in 2025. We touched on it in the last conversation we had. Major platforms are jumping in&#8212;for instance, Microsoft&#8217;s new Azure AI Agent service helps orchestrate multiple specialized agents and tools. What might this shift from single AI applications to multi-agent systems mean for software architects? How might architectures evolve to accommodate networks of AI agents that can plan, collaborate, and act autonomously? What challenges should we be prepared for in areas like agent coordination, security, or reliability?</strong></em></p><p><strong>Imran Ahmad:</strong> OK, first, let us think about this. Right now, when we design an AI system, the goal is to mimic human wisdom. That is what artificial intelligence is: mimicking human wisdom.</p><p>Imagine a person who wants to develop a fraud detection system and wants to get it done by the end of Monday. The first step in the human mind is discovery: OK, what are the requirements, and what are the tools that are available? Maybe there are existing tools, maybe there are friends to ask about which tools exist. In my mind, I will orchestrate. I will use those tools in different ways, I will come up with a plan, and I will start using those tools. Some of those tools will work, some will not, and the solution that I deliver will be the result of using existing tools, being aware of the tools that are available to me, and combining them in a meaningful way.</p><p>An AI agent is mimicking exactly this human behavior. An ideal agent should be aware of the tools that are available. Second, it should be able to orchestrate those tools in a way that leads to a meaningful solution. Third, it should be ready for surprises. Just like I can change my plan when something unexpected happens, the agent should be dynamic enough that it can change and re-plan as it goes. These are the three attributes of an agent.</p><p>In an agentic system, a large language model is just one of the tools. It is one of the important tools, but right now the large language model sometimes becomes the &#8220;king&#8221; and everything else is forgotten. What Azure has provided, and what Google has also provided with their own agent solutions&#8212;for example, agent spaces, agent design tools&#8212;is a way to step back and see these as orchestration platforms. We can zoom out and look at them in a vendor-agnostic manner; essentially, they are all doing almost the same thing.</p><p>Now, for architects, the first thing is that they should be aware of these new developments. That is why this book is about the architecture of AI systems. We are entering a time&#8212;2025 and 2026&#8212;where AI architecture itself is becoming a specialty. You need to be aware of these developments and track them on a regular basis. One way I keep up is by subscribing to good YouTube channels and other high-quality sources. There is a lot of content out there where people give talks but do not really know what they are talking about, so you have to be selective. And you have to recognize that what is relevant today may not be relevant at the end of 2025.</p><p>At the same time, some fundamentals do not change: the need for good architectures, the need for performant architectures, the need to create operational excellence, and the need to have data that is reliable. If agentic systems are one way of doing things, they are not the only way. There will always be new ways coming. You should keep an eye on them and keep incorporating new ideas as they come along.</p><p>The challenges are very similar to what we saw with Kubernetes. When Kubernetes was introduced, there was so much excitement. I used to teach courses on Kubernetes, and people mainly wanted to learn how to design and manage applications on it; they were less interested in the internals. Now, if you use a managed service like Vertex AI, under the hood it provisions a Kubernetes system for you and you do not need to think about those details; you just use it.</p><p>Right now, these agentic systems are like Kubernetes in its early days. They are still being developed, so sometimes they will work, sometimes they will not. But you will see that in less than a year these systems will become mature. As an architect, you should expect that maturity. Things like agents talking to each other should come out of the box; multi-agent systems, where each agent is a specialist with its own piece of wisdom for a particular vertical, will become the norm.</p><p>Our responsibility as architects is to start bringing these entities into our architecture and then let the system evolve and mature in the coming months. Some glitches will be there, but over time those glitches will be resolved.</p><p><em><strong>12: Enterprises also grapple with how to integrate their data with AI models effectively. One common pattern is bringing domain knowledge into AI workflows so that models can reason over real enterprise context. What is the right approach for infusing domain knowledge into AI systems? Do you think Retrieval-Augmented Generation (RAG) will remain the dominant architecture for bringing enterprise data into AI workflows, or will other patterns become more prominent as AI capabilities evolve?</strong></em></p><p><strong>Imran Ahmad:</strong> Yes. RAG is becoming obsolete in some ways&#8212;you are right about that&#8212;because context windows are becoming larger and larger, and that can remove the need for RAG. But it also means that with each request you may have to send a lot of information, and that may not be an efficient use of the model.</p><p>The advantage of RAG is that it is more efficient. Instead of sending everything, you only attach the right vectors or the right text. So our requests become more focused, and we are not wasting capacity on irrelevant context.</p><p>Agentic RAG is a step ahead. This is something that is still being developed, and classical RAG may become obsolete eventually. That is why I was saying earlier that these systems are expected to evolve. But RAG is still important, because you need to understand RAG in order to get to agentic RAG. In the book we have talked about RAG, and I feel that this is the right learning path: learn the simple use case before moving to the more complex one.</p><p>Coming back to your question, there are always multiple ways of doing things. You can have agentic RAG, you can have a large context window, you can have what I would call &#8220;classical&#8221; RAG. There will be an overlap in functionality between these approaches. In that case, it becomes subjective. You have to carefully see what the advantages and disadvantages are for each option, and then choose the approach that gives you the best solution that is available currently.</p><p><em><strong>13: Some say the &#8220;AI architect&#8221; is no longer just a technologist, but a strategic leader at the intersection of data, infrastructure, and product. How do you see the role of architects changing as AI becomes a core part of software systems?</strong></em></p><p><strong>Imran Ahmad:</strong> Yes. So the traditional architect was basically operating in the days of the waterfall methodology, where you had clearly defined phases: your project gets approved, it gets funding, then someone writes the business requirements for you. Then there is a layer of red tape. After that comes the architect, who designs the system&#8212;and whatever that person designs is written in stone. Then the technical team needs to implement it, and the criteria of success is meeting that design in the most precise way. Gone are those days.</p><p>The reason is that now the architect needs to be involved in the iterative process. When you are doing AI, you are trying new things, you are experimenting, and sometimes ideas will not work. So it means that the role of the architect is more dynamic in nature. As you move towards AI systems, the architect has to be involved in the pilot project; the architect may need to refactor, may need to redesign the data dimension or the compute dimension if they see performance bottlenecks. So the role has become more agile, but the need for the software architect is still there. It is very important&#8212;it has become more important than ever.</p><p>Let me give you a reason. A large-scale project is like building a home. In some villages, people still build houses without an architect. They have bricks, they have an idea&#8212;&#8220;let us build a room here, let us put a kitchen there&#8221;&#8212;and they just start. But in an organized way, an architect first plans: &#8220;OK, this is the room, this is the hall, this is the kitchen,&#8221; makes a blueprint, gets it approved, and then we start building the home.</p><p>Now think about this: if the architecture is wrong&#8212;let us say the bedroom was supposed to be on the ground floor because the owner has a knee problem and cannot climb the stairs&#8212;but that decision was not captured, and the bedroom ends up on the first floor, then you have a serious problem. You can imagine how expensive and disruptive it is to change the structure after everything has been built. The same goes for large-scale software architecture. The basic templates need to be decided before you build the system.</p><p>That is why there needs to be an architect who designs the large-scale components, and then someone starts filling in the details. Otherwise, you end up with very costly mistakes. If you look at some real-world stories&#8212;for example, JP Morgan&#8212;you will find cases where they designed their system and spent minimal time on architecture. They picked, for example, MongoDB, went ahead with their design, and eight months down the road they realized that this was the wrong choice. There was a loss of revenue, a loss of time, and this is something we want to avoid at all costs.</p><p>So the role of the architect in the age of AI is not going away. It is becoming more central: more dynamic, more involved throughout the lifecycle, and more responsible for making sure we do not build the &#8220;bedroom upstairs&#8221; when the user cannot climb the stairs.</p><p><em><strong>14: What new responsibilities or skills&#8212;for example, understanding model behavior, data governance, or AI ethics&#8212;should architects cultivate now to successfully design and oversee AI-enabled software in the coming years?</strong></em></p><p><strong>Imran Ahmad:</strong> This is essentially about making yourself aware of what technologies are available and what is happening in AI. The architect should not treat AI as a black box or something that is &#8220;someone else&#8217;s job.&#8221; You should be able to understand, at least at a high level, what these AI components do and how they behave.</p><p>A key skill is the ability to choose the right AI components under given requirements: which model to use, what kind of data pipeline is needed, what kind of storage is appropriate, and how the compute dimension should be designed. You should be able to look at the requirements and say, &#8220;Under these constraints, this combination of components will work best.&#8221; That selection ability is very important.</p><p>Another responsibility is to understand the implications of AI decisions on things like data governance, security, and compliance. When you bring AI into the system, you are also bringing in new questions: how the data is collected, how it is stored, how it is used for training, how it is monitored in production, and how you make sure that you are meeting ethical and regulatory expectations.</p><p>So for many architects, this means retraining themselves in AI. For some, AI is a blind spot at the moment. Closing that blind spot is crucial: keep learning about AI concepts, stay current with the tools and patterns, and build enough understanding that you can make informed architectural decisions. You do not have to be the person implementing every model, but you should be comfortable enough with AI that you can confidently design, review, and oversee AI-enabled systems end to end.</p><div><hr></div><p>To go deeper on designing robust, scalable AI-enabled systems&#8212;from integrating machine learning into existing architectures to managing risks like underperformance, cost overruns, and operational complexity&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469">Architecting AI Software Systems</a></strong></em> by <strong>Richard D Avila</strong> and <strong>Imran Ahmad</strong> (Packt, 2025). Through a structured progression of architectural concepts, real-world case studies, and hands-on exercises (including a fictional AI-enabled system you can dissect end to end), it shows software and systems architects, CTOs, VPs of Engineering, AI/ML engineers, and developers how to select the right models and data pipelines, use architectural models to ensure cohesion, simulate and optimize AI performance through iteration, and apply patterns and heuristics to integrate AI into large-scale systems with strong user experience and performance&#8212;so you can confidently architect AI-driven products across a range of domains.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3POB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!3POB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775" width="312" height="384.85714285714283" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:312,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Architecting AI Software Systems&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/architecting-ai-software-systems-9781804619469&quot;,&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="Architecting AI Software Systems" title="Architecting AI Software Systems" srcset="https://substackcdn.com/image/fetch/$s_!3POB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!3POB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!3POB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd2305437-423b-4c1c-a6f4-e3dab69fc532_2250x2775 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>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!aZpm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!aZpm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 424w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 848w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1272w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png" width="853" height="802" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:802,&quot;width&quot;:853,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:192721,&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://deepengineering.substack.com/i/180580239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.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_!aZpm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 424w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 848w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.png 1272w, https://substackcdn.com/image/fetch/$s_!aZpm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd06f8ef9-65e6-43a3-a492-bb9478950297_853x802.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!KWHP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!KWHP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 424w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 848w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1272w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png" width="851" height="753" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:753,&quot;width&quot;:851,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:151425,&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://deepengineering.substack.com/i/180580239?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.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_!KWHP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 424w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 848w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.png 1272w, https://substackcdn.com/image/fetch/$s_!KWHP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5540c6d8-b754-46af-b453-c37a646ec2e9_851x753.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></p>]]></content:encoded></item><item><title><![CDATA[Architecting AI-Native Platforms in the Real World: A Conversation with Amar Akshat]]></title><description><![CDATA[Agentic architecture, cell-based boundaries, and prompts-as-code for reliable, auditable AI in payments and wallets]]></description><link>https://deepengineering.net/p/architecting-ai-native-platforms</link><guid isPermaLink="false">https://deepengineering.net/p/architecting-ai-native-platforms</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Wed, 19 Nov 2025 10:52:24 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/R8xSq42-iOM" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>AI is already in the loop for writing code, reviewing changes, and even drafting architecture diagrams&#8212;but turning those capabilities into resilient, auditable, production-grade systems in regulated domains is still hard. In payments and financial services especially, architects have to reconcile non-deterministic models with deterministic guarantees around correctness, security, and compliance.</p><p>In this conversation, we speak with <strong>Amar Akshat</strong>&#8212;<strong>SVP of Architecture</strong> at <strong>Paysafe Group</strong> and author of the forthcoming book <em><strong>Decode the Compiler </strong></em><strong>(Packt, 2026)</strong>. At Paysafe, Amar has led large-scale modernization and AI-native transformation across payments, wallets, and compliance platforms. Earlier, at Apple, he helped shape the architectural foundations of Apple Pay and contributed to wallet and tokenization frameworks. His work focuses on making architecture itself intelligent&#8212;blending principles like CAP, Twelve-Factor, and Zero Trust with AI-driven reasoning and automation.</p><p>Over the course of the interview, Amar explains how his teams are bringing AI into the architecture loop through MCPX, ArchX, and &#8220;cell&#8221; architectures that keep analysis and decision paths safely bounded. We dig into when to keep workflows purely deterministic versus putting an AI in the path, how to structure data, guardrails, and system prompts as first-class design elements, and how to choose between modular monoliths and microservices for AI-heavy workloads. Amar also shares concrete practices around confidence-based routing and trust deltas, prompts-as-code and AI Behavior Reviews, prompt manifests as &#8220;Dockerfiles for AI,&#8221; cost control with &#8220;cache, batch, distill,&#8221; and vendor-neutral orchestration via protocols like chat completions and MCP.</p><p>Looking ahead, Amar reflects on the skills architects now need and how compiler-level thinking informs the design of AI-driven systems. We close with a preview of <em>Decode the Compiler</em> and why understanding what compilers actually do to our code can change how we reason about performance, optimization, and large-scale architecture.</p><p>You can watch the full conversation below or read on for the complete Q&amp;A transcript.</p><div id="youtube2-R8xSq42-iOM" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;R8xSq42-iOM&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/R8xSq42-iOM?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><h1>Introduction</h1><p><em><strong>1: Can you give us a quick overview of your current focus areas at the AI&#8211;architecture intersection? Which lens do you think we should use today&#8212;compiler-centric system design or product architecture, and why?</strong></em></p><p><strong>Amar Akshat:</strong> Right now my focus is what I call agentic architecture&#8212;designing systems that can reason about themselves. At <a href="https://www.paysafe.com/en/">Paysafe</a>, that&#8217;s embodied in MCPX, which you talked about.</p><p>And we have something else called Archx, which is basically an AI-native workflow that powers things like onboarding, fraud analysis, and observability, but also reasoning with systems and their own capabilities&#8212;such as Zero Trust and the CAP theorem. A lot of what we do today is about codifying architectural experience.</p><p>For example, we have trained internal AI agents to analyze architectural decision records, or ADRs, and suggest reusable design patterns, effectively learning from the scars of every project before it.</p><p>And when you talk about lens, it&#8217;s an interesting analogy you use with compilers and system design. I would want to use the <strong>system design lens</strong>. You see, architecture isn&#8217;t abstract for me&#8212;it&#8217;s very progressive, it&#8217;s very pragmatic, and it has building blocks like servers, data flows, queues, failure domains.</p><p>My work sits between things like compiler intelligence and distributed systems and their logic. So if you think about it, the compiler is just an early architect. It takes intent, it optimizes it under constraints, and it produces an executable structure&#8212;and that&#8217;s the same mental loop I want our AI agents to have when designing complex systems through architecture.</p><h1>AI&#8217;s Impact on the Architect&#8217;s Role</h1><p><em><strong>2: I think you are probably one of the best people to ask my next question to, because you sit exactly at this intersection, which I think a lot of people are still trying to make sense of. So where does AI practically help architects now, and where is human judgment non-negotiable?</strong></em></p><p><strong>Amar Akshat:</strong> I get this asked a lot in my team as well. We have tools like <a href="https://cursor.com/">Cursor</a>, for example, or Replit <a href="https://replit.com/agent3">Agent 3</a>, or GitHub <a href="https://githubnext.com/projects/copilot-workspace">Copilot Workspace</a>. They basically act as junior architects today.</p><p>They help me generate documentation. They suggest failure patterns from known premises and known previous experiences, and they help me validate deployment diagrams. For example, every good generative AI can create brilliant Mermaid diagrams. So it can start off as your starting whiteboard&#8212;where you throw in your constraints and components&#8212;and it&#8217;ll start with a basic Mermaid diagram that would take an architect a few hours to actually come up with.</p><p>At Paysafe, we are using AI during architectural reviews. It will ingest our ADRs from before, diagrams, and codebases, and then it will flag inconsistencies between what we said we would build and what we actually deployed, because it has the whole lineage of design from scratch all the way to deployment. So it can reason and tell you, with evidence, that this was the original plan to be deployed, this was the scale pattern, and what we ended up deploying.</p><p>Human judgment, on the other hand, still owns context, risk appetite, regulatory nuance, and product trade-offs&#8212;the politics between product and regulatory. That is all still owned by humans. The AI can propose, but humans prioritize.</p><p>I know that my business can reasonably make money in EMEA and Europe for now, so I will prioritize regulatory nuances of EMEA and Europe, and then put through my roadmap what will come in the Americas. So that is the beautiful mix between how humans and AI interact in architectural designs.</p><p><em><strong>3: What&#8217;s a good mental model for deciding when to put AI in the loop versus keeping a purely deterministic path?</strong></em></p><p><strong>Amar Akshat:</strong> If a task mainly benefits from pattern recognition, that&#8217;s where putting AI in the loop makes sense. If it instead requires legal, financial, or compliance certainty, we keep it deterministic. I think of AI as a kind of auto-complete for patterns: it can look at data and say, &#8220;this is PII,&#8221; &#8220;this is PCI,&#8221; &#8220;these are the compliance guardrails you&#8217;ll need.&#8221; That&#8217;s where this kind of AI can work. The parts that demand strict, predictable behavior should stay more deterministic, and that&#8217;s where we sometimes choose not to use AI.</p><h1>Architectural Patterns for AI&#8209;Infused Systems</h1><p><em><strong>4: What baseline architecture patterns do you recommend for shipping AI features reliably?</strong></em></p><p><strong>Amar Akshat:</strong> The first one is the <strong>data postulate</strong>, the second one is the <strong>guardrail postulate</strong> and the third one is the <strong>system prompt package</strong> itself. </p><p>So when I say <strong>data</strong>, I mean what is the current state of the data that is made available by MCP servers. Such as transactions, such as user records, addresses, etc. </p><p><strong>Guardrail</strong> is about making sure what is allowed to be done and what is not allowed to be done. Do you want to completely ground the system? Do you want to have fairly only deterministic responses or do you want to use the existing LLMs? </p><p>And then the <strong>system prompt</strong> is about saying, what is my input format and what am I expecting the results to be in? And what are the other nuances I want my system to take care of automatically? So, for example, do you want more deterministic performance or do you want more accuracy? Do you want more transparency or do you want more speed? These are the kinds of trade-offs that we encode into that system prompt package. This also includes things like Langchain and Open AI or Azure&#8217;s AI Foundry for RAG. </p><p>And we have our own prompt manifests for governance. So, each inference has a manifest attached to it, and that is published to a data plane. So you can imagine things like Kafka plus Fast API. And each inference is observable, so you can actually observe the latency, the accuracy, etcetera. That is the current model which works for us. Where it breaks is things where execution is critical, user experience is critical. If things need to be made quickly using judgment, then you cannot rely on LLMs. Then we deploy lightweight sidecar models. Things like Open AI Mini or Llama 3 local for shared and even fraud scoring, which has to be real time in a transaction. We try to do these things in a centralized fashion.</p><p><em><strong>5: How do you decide boundaries for AI components such as separate services, sidecars or embedded libraries?</strong></em></p><p><strong>Amar Akshat:</strong> So it is all what I call an architecture based on cells. A cell, you know, is a human component which is the tiniest unit of life. So, all of our AI deployments are tiniest units of life with their own regulatory nuances within themselves.</p><p>So, for example, if I&#8217;m talking about a wallet cell, everything which can support the wallet&#8212;its guardrails, its prompt package, its MCP servers, RAG, plus its fine-tuned models&#8212;will also participate in that cell and it won&#8217;t leak any data. <strong>The idea is the critical path of analysis never leaves the cell boundary, and it only leaves the cell boundary for audit and storage purposes.</strong> That keeps us safe first of all, fast, and then deterministic. No other data is going to change the way my wallet cell behaves, for example. The same thing applies to payment execution, the same thing applies to transaction ledgers, and so on and so forth.</p><p><em><strong>6: When would you favor modular monolith over microservices for AI and vice versa?</strong></em></p><p><strong>Amar Akshat:</strong> If shared memory and stateful context really matter&#8212;say in a conversational commerce system&#8212;a modular monolith with well-defined internal modules works best. Imagine two people asking what to buy for Diwali (an Indian festival) in different parts of India, but their shared history and the same product catalog matter for recommendations&#8212;that&#8217;s a great case for a modular monolith with clear internal boundaries.</p><p>If scalability and isolation are paramount&#8212;for example, in fraud detection&#8212;microservices tend to win, because AI workloads often oscillate between those two needs. Many of us think of this as the <em>context&#8211;isolation trade-off</em>: which is more important for your use case, rich shared context or strong isolation?</p><h1>Reliability, Safety, and Testing</h1><p><em><strong>7:  How do you design for correctness and failure isolation when models are non-deterministic?</strong></em></p><p><strong>Amar Akshat:</strong> We route by confidence, really. If the model&#8217;s output confidence is less than a threshold, it escalates to a deterministic rule-based system, or we bring a human in the loop. We use things like LangSmith and internal logging to track trust deltas per request. We have effective guardrails and fallbacks&#8212;prompt validation and schema enforcement; we use things like Pydantic. We are a big Python shop for some AI-based workflows, and we use Pydantic plus semantics and sanity checks.</p><p>So a human only steps in for logic failures, not syntax, really. And we have a comprehensive testing strategy for AI features. For example, one of my cohorts runs drift pipelines. They will evaluate daily by comparing outputs to gold datasets&#8212;datasets that are deterministic and known to be correct&#8212;and any semantic drift triggers a review. Basically, you have to look at AI prompts as code. That&#8217;s it. Our CI/CD basically treats <strong>prompts as code</strong>. Every change goes through the peer-review process, with automated regression and some kind of sandbox deployment to test these against the gold dataset.</p><p><em><strong>8: You mentioned guardrails and fallbacks earlier. If you had to distill it down to a couple, which guardrails, fallbacks, or human-in-the-loop steps have been most effective in practice?</strong></em></p><p><strong>Amar Akshat:</strong> In terms of guardrails and fallbacks, first of all, as I was describing before, guardrails are learned. We learn these guardrails from execution. Every prompt package has a version, and with every new execution and failure we put in more guardrails.</p><p>For example, if the AI system ended up putting someone&#8217;s email address from the RAG into a response that was meant to be PII-sanitized, we will again augment the guardrail to include that sanity check. Those guardrails are implemented by tools like prompt security, which ensure that none of these guardrail filters let you pass the data back to the customer. </p><p>If you apply a middleware kind of concept like prompt security&#8212;or any of those use cases where you can apply these guardrail policies before the prompt goes into the LLM and before the response comes back to the user&#8212;you will have effectively masked your failure pattern.</p><p>Human-in-the-loop is usually very, very important when it comes to response quality. Every response has a confidence score, and if the confidence score goes below a certain threshold, a human will come in and try to analyze what was wrong. Was the data too noisy? Was there too much guardrail or too little guardrail? Or was it a format problem, right&#8212;did we come back with bad formats, like something breaking the CI/CD somewhere or changes, etc.? So <strong>the combination of middleware components like prompt security and the usage of guardrails with a human in the loop is very important.</strong></p><p><em><strong>9: Can you describe a sensible testing strategy for AI features covering eval data drift and regressions?</strong></em></p><p><strong>Amar Akshat:</strong> I think the testing strategy for AI systems is fundamentally about learning from mistakes. Similarly, we have to make sure the AI learns from its own mistakes. The idea is that we have to monitor things like semantic drift, hallucination rate, and related metrics, and you have to monitor them with real-world data in sandboxes.</p><p>And then you have to, first of all, come up with a reasonable notion of success for yourself. So let&#8217;s say you are dealing with a lot of complaints. You have an AI system which analyzes your complaints and makes sure they&#8217;re being handled correctly. You run it in sandboxes with masked PII so that you have a reasonable testing ground around them, and then per execution you look at things like their semantic drift, hallucination rate, and the trust delta, right?</p><p>Every pipeline will come back with these metrics, and those evals plugging into your CI/CD are very important because your prompt is changing&#8212;changing just like code on a daily basis&#8212;and your prompt changes can sometimes be exponentially impacting your determinism.</p><h1>Observability and Operability</h1><p><em><strong>10: What production signals matter most in production for AI features?</strong></em></p><p><strong>Amar Akshat:</strong> Yeah. So these signals&#8212;basically everything we tested for&#8212;now start to matter in production as well.</p><p>The first is <strong>cost</strong>. We are a financial company, we have millions of transactions going through, and a small change in cost per transaction can exponentially impact our revenue or margins.</p><p>The second thing is <strong>hallucination rate</strong>. Each hallucination in something as deterministic as fraud analysis costs us money, because it can lead to incorrect decisions on transactions.</p><p>And then the third part is obviously things around the <strong>sanity of the whole system itself</strong>. You should be making sure that, as you introduce or tune AI, you&#8217;re not unintentionally impacting real transactions or degrading the user experience&#8212;you might otherwise be causing attrition in your user base. These things matter for us, and we monitor them very closely in our production systems.</p><p><em><strong>11: How do you set up automated and human feedback loops to improve models or prompts without breaking user-reliable behavior?</strong></em></p><p><strong>Amar Akshat:</strong> Yeah, so feedback is pretty automated. The agent will log all low-confidence events, and a human reviews them, and it will relabel itself. And as I was telling you, prompts are versioned with Git tags so we can replay failures exactly. Because it is an agent, you can always augment asynchronous activities by itself. So what we have today is that every failure in them is then analyzed by a different model so that we don&#8217;t have model bias itself, right? </p><p>For example, a failure derived in an OpenAI model will now be reviewed by a Sonnet Claude model. And the feedback we obtain from there will be asynchronously applied to the OpenAI package, which went into OpenAI&#8212;the whole thing. And we then, over time, figure out what is working better for us. Which model is able to review the feedbacks and failures of a different model? And then we have these model couplings formed by that, and all of it is tracked via Git tags. So every release has a JSON in it which says, here was the analysis and scoring, and here were the recommended prompt changes or guardrail changes, applied it, and got this score as the final one. So auditability is incredibly important in our ecosystem because this is real data you&#8217;re dealing with, this is real developers&#8217; time you&#8217;re dealing with, and then also sometimes you&#8217;re dealing with real transactional data. So we need to understand which particular change and which particular recommendation caused us transactional benefit across the feedback loops.</p><h1>Data, Privacy, and Governance</h1><p><em><strong>12: How do you protect sensitive data while keeping AI useful?</strong></em></p><p><strong>Amar Akshat:</strong> That&#8217;s a great topic, and it&#8217;s at the top of every executive&#8217;s mind in the industry right now. We basically <strong>redact personally identifiable data before inference</strong> and use <strong>hybrid RAG</strong>, where private embeddings always stay in-house.</p><p>For example, we can use something like <strong><a href="https://www.pinecone.io/learn/series/vector-databases-in-production-for-busy-engineers/cicd-pinecone-local/">Pinecone Local</a></strong>, where it runs as a local instance and private embeddings never leave our environment. Public context is then fetched externally in a secure and deterministic way&#8212;for example, a regulatory change, the impact of that change, or human sentiment around a new law. Those external signals are handled in a more deterministic, controlled way.</p><p>At the heart of all this is our <strong>middleware</strong>. That&#8217;s where we apply these policies: even if you wanted things like sentiment or PII, it will not flow into the inference layer if we don&#8217;t want it to. All AI access is integrated with <strong>SAML-based authentication</strong>, so we know who is accessing it and can augment their prompt with their role, etc. On top of that, there&#8217;s a <strong>guardrail middleware</strong> where we always apply a particular set of rules based on their role and permissions.</p><p>So even if you accidentally put my email address into the prompt, it will be filtered out before it leaves the system. That&#8217;s where our middleware stack plays a huge role, along with <strong>lightweight governance around prompting</strong>. Our <strong>prompt manifest</strong> defines who owns the prompt, what its data scope is, and its safety rating. You can think of a prompt manifest as a <strong>Dockerfile for AI</strong>&#8212;basically, it&#8217;s auditable but still fast to work with.</p><p>And finally, for governance, <strong>auditability and traceability are paramount</strong> for us. We log every inference as an &#8220;architectural replay,&#8221; which includes things like model ID, prompt version, and data snapshot. That way, our compliance teams can reproduce any decision path deterministically.</p><p><em><strong>13: What is your take on auditability and traceability for AI decisions when regulation applies.</strong></em></p><p><strong>Amar Akshat:</strong> Regulation is paramount. I&#8217;m dealing with EU regulation and the AI Regulation Act on a daily basis, and basically it goes back to how deterministic you can make your decision paths.</p><p>Our whole goal is that anytime anything breaks our determinism, or that score, we either chuck it out of production immediately or we treat it as a P1&#8212;like a priority-one incident, right? So any production workflow losing determinism at a given threshold will be treated as a production incident. It is no longer a developer playground or anything.</p><p>And because we are able to log the model ID&#8212;basically the architectural snapshot, the replay of it&#8212;we are able to log the model ID, prompt version, and the data. We can go back to the decision path and change any of these variables to make sure determinism can be achieved immediately. Our Ops teams are actually trained to do this on a daily basis.</p><h1><strong>Cost, Performance, and Vendor Strategy</strong></h1><p><em><strong>14: How do you avoid provider lock-in without slowing delivery?</strong></em></p><p><strong>Amar Akshat:</strong> We try to stick to the protocols the market is standardizing on&#8212;for example, the chat completions APIs and MCP. These may start with a single company, but over time they become common practice across the industry. So we abstract orchestration through these well-known protocol APIs.</p><p>When I talked about MCPX, that&#8217;s essentially our <strong>multi-provider orchestrator</strong> across OpenAI, Azure, Anthropic, and our on-prem models. The reason this works is that all of them support chat completions&#8211;style APIs and MCP-compatible patterns. So as long as any external or internal AI provider follows those APIs and protocols, we&#8217;re fine.</p><p>On top of that, we put an <strong>AI gateway</strong> in front. Based on things like request headers or your SAML identity information, we can route you to an Azure model versus an OpenAI model, or to an internal model. That is how we avoid lock-in in practice while still moving fast.</p><p><em><strong>15: If we talk about capacity planning and cost control, what has worked for you in terms of caching, batching, smaller models, etc.?</strong></em></p><p><strong>Amar Akshat:</strong> I think the mantra is very simple: cache, batch, distill. We use a tiny Llama for high-volume routing tasks and GPT-4 Turbo for design-time reasoning. So if it is dynamic data like customer support or architecture, design, etc., we stay with prompt engineering because, in that case, flexibility beats precision.</p><p><em><strong>16: When do you feel training or fine-tuning is worth it versus prompt engineering when it comes to a foundation model?</strong></em></p><p><strong>Amar Akshat:</strong> I think if your domain is stable&#8212;maybe KYC or risk scoring&#8212;the signals are very well known, the domain is stable, and then we use fine-tuning, because it&#8217;s a very well-known, stable, signal-based domain.</p><p>And as I was saying before, if it is dynamic&#8212;if it is changing a lot&#8212;Spanish customers complain in a different way, English customers complain in a very sarcastic tone, and Indian customers complain in a very direct tone, sometimes in Hindi or regional languages. Then we stay with prompt engineering, because we have specialized customer teams who know how their customers complain and can create prompts more easily to manage those customer complaints. So yeah, that&#8217;s my short answer.</p><h1>Team, Skills, and Process</h1><p><em><strong>17: What new skills should architects or senior engineers acquire in 2025 and beyond to stay effective with AI in the stack?</strong></em></p><p><strong>Amar Akshat:</strong> Architects and senior engineers must learn <strong>prompt literacy</strong>, <strong>model evaluation</strong>, and <strong>probabilistic reasoning</strong>. That&#8217;s paramount. You don&#8217;t need to train models; you need to <strong>design systems that can survive their uncertainty</strong>.</p><p><em><strong>18: How do you adapt design reviews, ADRs, and incident response for AI-specific risks and ongoing learning?</strong></em></p><p><strong>Amar Akshat:</strong> Our design reviews have introduced a first-class concept called <strong>AI Behavior Reviews</strong>. We explicitly acknowledge that AI behavior is non-deterministic, and we treat that as a first-class part of the review process.</p><p>ADRs now capture <strong>prompt decisions</strong> and <strong>fallback strategies</strong> as part of the architecture record. And on the operations side, our SREs include an <strong>AI SRE</strong> role&#8212;someone who understands when it&#8217;s <strong>model drift</strong>, not code, that broke the system.</p><p>As I mentioned earlier, we&#8217;ve trained Ops people to understand the determinism profile of every AI pipeline. So now they can recognize that it wasn&#8217;t the code that failed; it was drift in the AI behavior&#8212;and they know when to switch off that pipeline or replace it with something else.</p><h1>Case Study</h1><p><em><strong>19:</strong> <strong>Can you walk us through a recent AI&#8209;related technical decision you&#8217;ve made: the options, the trade&#8209;offs, and how you validated the outcome.</strong></em></p><p><strong>Amar Akshat:</strong> That&#8217;s actually a very good question. I recently had a very interesting case. We create wallet workflows almost daily, and one of my teams was tasked with designing the checkout experience I was telling you about earlier&#8212;for our digital wallet.</p><p>This is the same problem I&#8217;ve solved multiple times before, in products like Paysafe and Paysafe Checkout. So it&#8217;s a problem I know well, and I had a clear sense of where I wanted to end up. What we did this time was use an AI assistant to generate candidate designs and then critique its own designs whenever they broke our zero-trust rules.</p><p>Eventually it produced essentially the same Mermaid diagram I would have drawn myself. It compressed many years of my experience into about 35 minutes of assessment, and it did a beautiful job of reproducing that design while honoring the constraints: partition tolerance was paramount, zero trust was paramount, and it respected those.</p><p>Then we validated it against failure scenarios&#8212;almost like a chaos check. For example: what if the system crashed at point A, B, or C? Does the system remain deterministic? Is the integrity of transaction persistence still correct? As I mentioned, the AI kept iterating until all the constraints were met. Its initial few iterations achieved consistency but not zero trust.</p><p>Next time, I plan to pair it with a chaos agent of some sort to automatically explore failure domains, and we&#8217;ll see how that goes.</p><p><strong>20: Are there any emerging patterns or standards you&#8217;re watching that could reshape how AI components integrate?</strong></p><p><strong>Amar Akshat:</strong> I think all of this starts from orchestration. You can look at things like OpenAI&#8217;s protocols, Google&#8217;s APIs, or Visa&#8217;s agentic commerce protocol&#8212;everything starts from orchestration. And when orchestration is involved, zero trust is involved. And when zero trust is involved, deterministic fallback is also involved.</p><p>You&#8217;re an orchestrator: you&#8217;re orchestrating tasks, and you cannot blindly trust anyone in the world. So you apply zero trust, and then you ask, &#8220;When something fails, how do I fall back?&#8221; That&#8217;s where workflow engines come in, and I&#8217;m watching patterns that bring those engines together with AI.</p><p>I&#8217;m especially interested in cases where ambiguity is not known until the ambiguity actually shows up. I don&#8217;t think the <em>existence</em> of ambiguity has been mathematically described yet&#8212;<em>when</em> will ambiguity occur? When ambiguity occurs, it&#8217;s obviously not a clean mathematical situation, but predicting <em>when</em> it will occur is still unknown to systems.</p><p>That&#8217;s why I want to see chaos agents enter the market&#8212;agents whose job is to disrupt AI workflows. Right now we live too much in &#8220;happy path syndrome,&#8221; where we assume the happy path is the only path that really happens in execution. That is not true; anything can happen, anything can fail.</p><p>Every design must still be explainable by a junior engineer, basically. And simplicity is still the ultimate scaling factor. That&#8217;s all.</p><h1>Hot takes</h1><p><em><strong>21: According to you, which production metric most correlates with perceived quality?</strong></em></p><p><strong>Amar Akshat:</strong> The trust delta.</p><p><em><strong>22: OK, and what&#8217;s the smallest useful model card or change log for shipped prompts or agents?</strong></em></p><p><strong>Amar Akshat:</strong> We use Microsoft Guidance, and Microsoft Guidance lets you treat your prompt as code. So even the commit messages become the smallest kind of change log that tells you what changed between two versions of a prompt. I would say commit messages, now.</p><h1>Looking Ahead</h1><p><em><strong>23: What constraints or first principles do you feel keep AI projects grounded, and what will look obvious in five years from now about architecting AI-heavy systems?</strong></em></p><p><strong>Amar Akshat:</strong> So first principles still apply, as in, any AI project will still not break the CAP theorem. The CAP theorem, when it applies to the determinism of applications&#8212;distributed applications&#8212;will still apply. So you will have trade-offs when you want consistency and partition tolerance. Availability will suffer irrespective of whether a human or an AI is writing the code or designing the system, right?</p><p>So those first principles remain, and an app will still be judged by its Twelve-Factor App principles,. AI apps are no exception. They may be self-healing, but their app constraints are still Twelve-Factor. Zero Trust is a model defined to safely execute critical workloads in the world, and that will still continue to apply.</p><p>One thing AI will add is the ability to self-heal with ample data and context at hand, which is a great principle we should actually capitalize on and try to create systems which, over time, go towards determinism rather than away from it. And &#8220;fail fast&#8221; is still very important, right? If something is not working for you&#8212;if determinism is not there&#8212;we should fail fast rather than have our transactional integrity or our customers suffer.</p><p>Looking ahead, I think if all the architects are on the same page, we should start versioning and feeding our contexts back into the AI. All the ADRs should go into the AI. The codebase should be scanned and understood by the AI on a regular basis. And then we should keep ourselves honest whenever the AI tells us that our ADRs and our codebases have diverged, which means we haven&#8217;t been true to our architectural design, right?</p><p>That will allow our AIs to have even more context in the world, and then they can apply these contextual patterns to create any advanced AI system, right? Any advanced AI system will still have deterministic models and dimensions&#8212;it is still working under those same constraints of the CAP theorem, etc. These are solved problems in every nook and cranny of the world. We just have to bring them together in an architectural model&#8212;not just a conversation, but an actual architectural model out there&#8212;and then let it weigh in with you on your high-scale design as a senior architect.</p><h1>About the book: <em>Decode the Compiler</em></h1><p><strong>24: You&#8217;re working on Decode the Compiler. According to you, how does deeper knowledge of compilation or codegen inform how we design AI-driven systems today?</strong></p><p><strong>Amar Akshat:</strong> Actually, that&#8217;s a great question. I&#8217;ll start with an anecdote. When I was growing up, I read this book by Yashwant Kannadkar, called <em>Let Us C++</em> , and I was taught that when you initialize a pointer&#8212;or when you allocate a pointer in C using <code>malloc</code>&#8212;you must always typecast it to the right variable type you&#8217;re using. I kept that in my head; it was my first education, and it stayed with me until I went into the depths of the GRU compiler at Apple, the Clang.</p><p>And I realized that I should not be doing this extra typecasting, because I am now telling the compiler what to do. The compiler knows what to do. It has seen your system, it has seen your code&#8212;however beautiful or ugly it may be. It has known your system constraints. It knows what to do, right? Let it do what it does best. The problem is we don&#8217;t understand what it does.</p><p>How a compiler makes your <code>for</code> loop efficient, for example, or makes your incrementing variable within the loop efficient, for example&#8212;we don&#8217;t know. Many of us don&#8217;t know that compilers will automatically make some variables <code>register</code> variables in C and C++, and it is very important for us to know that so that, when we are writing more advanced code, those design patterns can stick with us. And those same patterns we can apply in larger-scale habits.</p><p>In one way, compilers are trying to spoil us&#8212;trying to make us lazy&#8212;because they let us not take care of those finer performance details by ourselves and do it on our behalf, which is great, but then we are also losing that sharp curve of learning there. So my book is about understanding, from the compiler&#8217;s own output or the compiler&#8217;s own dump of what it has performed, what it has done on your code, right?</p><p>You shouldn&#8217;t be surprised. I think it&#8217;s a very, very interesting thing to learn&#8212;even for a simple <code>for</code> or <code>while</code> loop&#8212;how many performance improvements the compiler is making on your behalf. And that is what my book is all about: trying to decode the compiler&#8217;s kindness towards us.</p><p><em><strong>25: Is there a personal motivation or vision that led you to, you know, make the decision to write this book at this point in your life?</strong></em></p><p><strong>Amar Akshat:</strong> Oh yes, of course. So when I was at Morgan Stanley and when I came into Apple, I was deeply involved in build and integration systems. I was deeply involved in deep compiler workflows&#8212;understanding common build failure patterns&#8212;and I was at the heart of a team which was basically accepting code changes from the entire Apple operating system developer base inside Apple.</p><p>So I was seeing these common failure patterns across, and I was like, I wish I could run a podcast and almost every week tell people that this is a very common failure pattern all of you have. It&#8217;s just that it is not well documented. And, you know, sometimes the compiler steps in and does it for you and things like that. So syntax failures&#8212;sure, the compiler will reject you. But the subtle efficiency improvements which the compiler does, or sometimes we as humans do to make a couple of integrations work correctly, were almost too beautiful for me to just keep to myself.</p><p>So I wanted people to understand&#8212;when students go into engineering college today, or write their first few C programs&#8212;that they should be surprised to see what is happening beneath the compiler, right? Even if a &#8220;hello world&#8221; just comes in front of you, what it took the compiler to do it for you is a beautiful experience I went through, and I want the world to go through that as well.</p><p><em><strong>26: Who is the ideal reader for this book? According to you, who will find it to be the most useful?</strong></em></p><p><strong>Amar Akshat:</strong> I think the architects and the senior developers would be the most ideal readers, because they understand that when they look at how the compiler optimizes their code, they will be surprised and inspired. Those optimizations apply to us in real-world architecture as well,. You would realize that the compiler does so many things to scale your tokens, your token chunking, or to make your lookup of a particular data structure faster.</p><p>And those are the same patterns which we apply in our day-to-day architecture as well, like when we do caching or when we do streaming of tokens, etc. So senior developers and architects will be inspired. Junior developers and people who are upcoming in the market will be surprised. So it will also apply to them&#8212;to get surprised, beautifully surprised, flabbergasted, I would say.</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/p/architecting-ai-native-platforms/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/p/architecting-ai-native-platforms/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/p/architecting-ai-native-platforms?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/p/architecting-ai-native-platforms?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Mastering GitHub in the Real World: A Conversation with Ayodeji Ayodele]]></title><description><![CDATA[Scaling collaboration and CI/CD on GitHub&#8212;branch protections, rule sets, inner source, and Copilot-powered workflows that boost delivery without compromising security.]]></description><link>https://deepengineering.net/p/mastering-github-in-the-real-world</link><guid isPermaLink="false">https://deepengineering.net/p/mastering-github-in-the-real-world</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 06 Nov 2025 07:43:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/qfZWQg4AnV4" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>From secure collaboration and branch protections to reusable workflows and AI-assisted development, GitHub now sits at the center of how software gets built&#8212;and scaled&#8212;inside modern organizations. In this conversation, we speak with <strong>Ayodeji Ayodele</strong>&#8212;author of the <em><strong><a href="https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040">GitHub Foundations Certification Guide</a></strong></em> (Packt, 2025)&#8212;about helping teams move from &#8220;using Git&#8221; to leading with GitHub: collaborating transparently, automating confidently, and protecting the software supply chain without slowing delivery.</p><p>Ayodeji is a seasoned architect, DevOps evangelist, and Agile coach with over 18 years of experience across Financial Services, Tech, FMCG, Manufacturing, and the Public Sector. He&#8217;s worked with CIOs and engineering leaders throughout Asia, Oceania, and Africa to drive enterprise adoption of DevOps and Agile practices&#8212;helping teams ship better software, faster. Currently a Senior Customer Success Architect at GitHub, Ayodeji partners with large organizations to align GitHub&#8217;s tools and workflows to real business outcomes&#8212;improving developer velocity, security, and collaboration at scale.</p><p>In this interview, we dig into what the GitHub Foundations Certification covers in practice, how to level up from issues and pull requests to governance with rule sets and quality gates, and where GitHub Copilot (and emerging agentic capabilities) can responsibly boost productivity. We also discuss inner source as a cultural shift, strategies for CI/CD that avoid pipeline bloat, and pragmatic approaches to secrets management, dependency hygiene, and build provenance.</p><p>Looking ahead, Ayodeji shares how AI is reshaping developer workflows, what skills will keep engineers relevant, and how to cultivate a documentation-first, asynchronous culture across time zones.</p><p>You can watch or listen to/download the full conversation below&#8212;or read on for the complete transcript.</p><div id="youtube2-qfZWQg4AnV4" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;qfZWQg4AnV4&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/qfZWQg4AnV4?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div class="native-audio-embed" data-component-name="AudioPlaceholder" data-attrs="{&quot;label&quot;:null,&quot;mediaUploadId&quot;:&quot;e97c54b2-75ae-4e35-a0b0-d742584d68bc&quot;,&quot;duration&quot;:4245.4204,&quot;downloadable&quot;:true,&quot;isEditorNode&quot;:true}"></div><div><hr></div><p><em><strong>1: What gap does your book, GitHub Foundation Certifications Guide, fill for today&#8217;s developers?</strong></em> </p><p><strong>Ayodeji Ayodele:</strong> My book bridges the gap between knowing Git basics and truly mastering GitHub as a collaborative, secure, and scalable platform. Many developers know how to commit and push code, right? But sometimes they struggle with collaboration, automation, and security, so I believe the book helps with that. Secondly, the GitHub Foundation certification is a great benchmark, but in the past there wasn&#8217;t a practical, hands-on guide to help people prepare and apply those skills in real terms, given the fact that GitHub certifications in general are just barely two years old. So we don&#8217;t have that many resources out there. I wanted to create a resource that&#8217;s not just exam-focused, but also helps developers become confident contributors in any organization. So <strong>I wrote this book to help developers go from &#8220;I can use GitHub&#8221; to &#8220;I can lead with GitHub.&#8221;</strong></p><p><em><strong>2: Your book promises to take readers from fundamentals to advanced GitHub features&#8212;from better collaboration and project management to secure workflows and even AI-powered coding with Copilot. Now, as you said, many developers use GitHub daily but may not be leveraging features like issues and pull requests. How can even experienced developers benefit from this book, and what are some important GitHub best practices or features that even seasoned engineers often overlook?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yes, you&#8217;re correct&#8212;even seasoned engineers often miss out on GitHub&#8217;s advanced features that can supercharge collaboration and code quality. For example, GitHub Copilot&#8212;our latest product&#8212;is a massive game changer in the developer world, particularly if you&#8217;re using it both within the IDE and on the github.com platform. So GitHub Copilot is not just for the IDE; you can use and benefit from the great values that Copilot brings even on the github.com platform. Not just that&#8212;it supports multiple IDEs, up to about six, which means you don&#8217;t have to learn a new IDE for some of those other AI products in the software development space today. You can bring those models and still use the development environment you already use today. </p><p>So Copilot supports multiple models&#8212;you can use all the GPT models, you can use the cloud sonnet models, and there is also the Google Gemini models as well, all within GitHub Copilot. And then there are the agentic capabilities that we now see as the future of AI in software development&#8212;whereby you can have this huge backlog of issues, assign those issues to Copilot, and Copilot will spin up its own separate environment, triage the issue, and write the code that fits the description to implement that issue. That may be a feature request, or it may be fixing a bug and things like that. And going down the line, there are so many other things coming out in the next few months around helping to improve code quality as well. There is also a feature called GitHub Advanced Security that helps people manage vulnerabilities, and you can also bring in vulnerabilities that are reported by other security tools and even fix them within the same platform. Those are things you can use today. </p><p>Then, in terms of best practices, we&#8217;ve got pull request summaries. In terms of improving the semantics for pull requests and titles, we have rule sets for protecting branches and for protecting workflows when you run them&#8212;there&#8217;s a huge number of different rules you can apply to improve governance and to improve CI/CD automated checks&#8212;leveraging issues for transparent communication and collaboration as well. Finally, mastering these tools elevates you from an individual contributor to being a team enabler. GitHub isn&#8217;t just a code repository&#8212;it&#8217;s a platform for building better software together.</p><p><em><strong>3: As we know, open-source code now underpins almost everything. Given this ubiquity, what advice do you have for developers to harness open source effectively?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Open source is the backbone of modern software. Contributing is the best way to learn&#8212;contributing to open-source projects helps you grow and give back to the community. In fact, roughly 50&#8211;60% of software today is built in open source or on top of open-source components and libraries. So open source is integral to how we build software in the world today. I&#8217;d say start small&#8212;fix typos in public repositories and improve documentation. You&#8217;re just like everyone else, and the GitHub platform is home to over 150 million developers across different skill sets and interests, so you&#8217;ll always find a space that fits you. If you&#8217;re worried your contribution won&#8217;t meet standards, there are tools to help. GitHub Copilot&#8212;free for open-source projects&#8212;can suggest or improve code; after you&#8217;ve written code, you can ask Copilot to review it for standards. </p><p>We also have GitHub Advanced Security, and many of its security components are free for public repositories. GitHub takes its role as the home where the world builds software seriously, so we provide security and AI-powered tools to open-source communities at no cost. Beyond that, there&#8217;s the GitHub Community Discussions space where people suggest improvements to GitHub itself&#8212;join in and learn by doing. Open source is a two-way street: you give, you learn, you grow.</p><p><em><strong>4: How can engineers get involved in open-source projects on GitHub while balancing the risks and rewards of depending on community-maintained code?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Rephrasing that: if you want to get involved, start at github.com/explore. You&#8217;ll also find /trending, where you can see repositories gaining popularity&#8212;sometimes a new repo skyrockets because it&#8217;s exactly what everyone was looking for, whether a library, a design template, or a scaffolding component. There&#8217;s no judgment&#8212;you&#8217;re just like everyone else among 150 million developers on GitHub, spanning all experience levels and interests. About the risks: you may worry your contributions aren&#8217;t up to standard, or fear embarrassment. There&#8217;s no shame. </p><p>Use GitHub Copilot&#8212;currently free for open source&#8212;to suggest or improve code, and even ask it to review your code for standards after you&#8217;ve written it. Plus, GitHub Advanced Security offers many features free for public projects. As part of our commitment to the open-source community, we provide these security and AI-powered tools free to help you get started and build with confidence.</p><p><em><strong>5: How can the principles of open source&#8212;such as open collaboration, transparency, fork and pull&#8212;be used within companies to improve teamwork and code reuse?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> <strong>Bringing open-source practices inside companies&#8212;what we call inner source&#8212;breaks down silos and accelerates innovation.</strong> Transparency, forking and pulling workflows, and opening discussions all drive better code and teamwork. If companies are looking to get started, the InnerSource Commons website has very useful resources; my colleague Yuki in Tokyo is involved there. </p><p>To mitigate common issues like unclear ownership, be transparent and make it easy for people to contribute: add a CONTRIBUTING.md file with clear guidelines, explain what the project is, what it does, and where help is needed. </p><p>Use GitHub Discussions internally so people can collaborate and ask questions, and look to the GitHub Community for inspiration on how a discussion forum works. Leadership support matters, too&#8212;secure buy-in and celebrate contributions, whether at the community level or with incentives like badges or prizes. Inner source turns every developer into a potential innovator, not just a code consumer.</p><p><em><strong>6: Have you seen any challenges in adopting this inner source model, and what strategies can help overcome them?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Resistance to change is common&#8212;people feel comfortable with the status quo. </p><ul><li><p>Start by explaining what inner source is and keep the environment judgment-free so everyone feels supported. </p></li><li><p>Provide clear guidelines on what can be done, and communicate&#8212;over-communicate&#8212;so no one is surprised by the rollout. </p></li><li><p>Keep everyone in the loop on what you&#8217;re doing as a program, when you&#8217;re starting, and how you&#8217;ll deliver the change. </p></li><li><p>And don&#8217;t underestimate leadership involvement; having leadership support is critical to driving the change internally.</p></li></ul><p><strong>7: Modern software teams automate extensively. How critical is it for developers to integrate CI/CD pipelines and automation into their workflow?</strong></p><p><strong>Ayodeji Ayodele:</strong> CI/CD is one of my my favorite topics. CI/CD is hard. For example, you can introduce pipelines. In GitHub we have 3&#8212;one for the build phase, one for the test phase, and one for the deployment phase. And so when you think about CI/CD, you think of a process and a practice or a methodology that helps you automate all of those steps from the point where you collect requirements and analyze what needs to be developed. Then you build. You want to build and test as you build. So test-driven development&#8212;if that&#8217;s the model you follow&#8212;will require that you build your test before you write your code. And sometimes what we also do is that you write your code and write your test, depending on your methodology&#8212;whether it&#8217;s an agile methodology or something like that. So, for me, CI/CD is a practice and a set of standards that you want to have within your organization that helps you to be able to automate all of the very tedious, boring, repetitive tasks that you would ordinarily have to do by hand. </p><p>And when you think of CI/CD, you want to think about the entire journey of software development&#8212; so from the point of having an idea, building a prototype, and then having a feature request that you want to design, you want to test that feature, you want to deploy it and make that feature available to your customers. That&#8217;s the journey&#8212;it&#8217;s a life cycle. And when you think of life cycles, you&#8217;re thinking in terms of product management. You&#8217;re thinking about getting your ideas and turning those ideas into a reality for customers, for your users. And if you are going to really do that at scale, you cannot do it by having people on your team run commands manually on their terminal&#8212;copying configuration files here and there. You want to eliminate the room for human error. You want to make sure it&#8217;s repeatable. You want to make sure that the process that you follow in Team A is the same process Team B follows. </p><p>So CI/CD allows you to have those standards built so that your teams are able to build with confidence, and you&#8217;re able to ship more frequently. So the more frequently you ship a feature, the faster you are going to meet customer needs. And so CI/CD gives you that leverage to repeatedly ship features to your customers and not have to wait so long just because you&#8217;re following a very careful manual process. And the second thing I want to say is that CI/CD helps you to test early, test often. When you test early, you reduce the cost of the bugs&#8212;you reduce the cost of fixing them because you can discover them very early in the production life cycle. And when you do that, it&#8217;s cheaper to fix as opposed to discovering a bug after you have launched a feature to production. And then the last thing about CI/CD that I want to highlight is that you want to make sure that it&#8217;s repeatable. And so you want to write your configuration as code so that you are able to repeatedly do the same thing over and over again and in the shorter time possible. And that just lets you to ship faster and safer. </p><p>There are a number of things that you can do to improve the CICD pipeline. You can introduce automated quality gates within your pipelines so that there are quality checkpoints to prevent low-quality code from going into your main branch. And for that, for example, in GitHub today, we have code scanning tools&#8212;you know, GitHub Advanced Security is available to scanning tools today. And that helps to set up a rule set that helps you to scan your code for vulnerabilities. And you can then block a pull request from being merged based on that. You can also block pull requests by checking for tests, checking that your tests are passing, checking that your build pipeline&#8212;like the build process&#8212;completes and is successful. And then checking that the deployment pipeline meets all of the criteria or standards required before you actually deploy to production. </p><p>And so once you have those things set up&#8212;essentially a very solid, repeatable process with automation and with quality gates in place&#8212;you are going to be able to confidently ship production-grade software again and again. And when you centralize CI/CD&#8212;like, for example, you can have it built as reusable workflows&#8212;you are able to introduce them across all your repositories whenever you want to merge a pull request, and you know you have a central place where you can manage the quality gates, the rule sets, and the different things that need to be checked across your organization. This gives you a very repeatable process across the organization&#8212;across different business units&#8212;and you&#8217;re able to have that single pane of glass that lets you to see every single repository and the quality within them. So CI/CD is integral to software development today&#8212;I would say it&#8217;s an integral part of software development, yeah.</p><p><em><strong>8: What guidance would you give for using tools like GitHub Actions to streamline testing, integration, and deployment, and to avoid &#8220;pipeline overload&#8221; while maintaining software quality?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> I would say start with one of the most important points around deployments&#8212;which is continuous deployment. If you&#8217;re working in an organization where you are able to build a great testing culture&#8212;so you are building both unit tests, and you have automated tests running end-to-end, and you are able to bake quality into your check-in process&#8212;then you can enable continuous deployment so that you have frequent deployments to production without, you know, requiring someone to push a button after every merge into main. And the reason is that you have discipline, and you have the culture and processes to ensure that they are repeatedly passing. So when a pull request lands on main, the deployment happens automatically&#8212;so that is one. </p><p>And another point is that release gates help with staging: having an environment that lets you validate in a staging environment before you deploy to production. That&#8217;s really helpful when you have many teams and the product is really important and there are key delivery timelines, so release gates make sure that when you are in the staging environment it&#8217;s actually very close to the production environment. You can do some scale testing and even chaos testing in that staging environment. Now, in terms of avoiding pipeline overload, what I&#8217;ve seen is that when teams begin to add more and more and more&#8212;like, you know, &#8220;Let&#8217;s add this because it&#8217;s nice to have&#8221;&#8212;you&#8217;ll quickly get to a point where your pipelines take a long time to run. That means you&#8217;re blocking people from shipping because you now have this huge, long pipeline. So what I would say is that you want to keep your pipelines small in terms of the number of steps for a particular task. And when you break down your tasks&#8212;like, for example, your unit tests are separate&#8212;you want to break integration tests separately, and then you have performance tests and other tests that you want to do. And by breaking these things down into a pipeline for a particular purpose, you are able to keep them short and small, so that when you are running the pipeline for unit tests, it&#8217;s not including all the steps that you need for, say, integration tests. </p><p>So you want to break them down, and you want to make sure that they are not blocked by each other. For example, let&#8217;s say you want to run a build pipeline, and then you also want to run unit tests and integration tests. You should be able to run them in parallel such that when your build is done and the unit tests are done, then you can do code coverage and other types of analysis. And then you can go ahead and, you know, do the integration tests while, you know, the unit tests are done&#8212;rather than having them chained where you have to wait for one to finish before the other can begin. So run in parallel&#8212;parallelization lets you to be able to get things done faster. The other thing I would say is: cache artifacts between steps. When you are building artifacts, you want to be able to reuse them easily between pipelines or even within a pipeline. And so when you cache artifacts, it saves you the time it takes for a build to start from scratch every time. </p><p>For example, in Node.js, you can cache the dependencies; in Python, you can cache the dependencies; in Java you can cache dependencies&#8212;you can cache them across runs. And this makes sure that if there&#8217;s a small change you&#8217;re making, you don&#8217;t have to start from scratch. And then, finally, when you&#8217;re thinking about CICD, you want to think about making it reusable&#8212;so reusable workflows. That way, when you have standard, common steps, you can reuse them across teams. And then also make it modular. So when you have a task, you want to have a complete task that&#8212;once that task is completed&#8212;then you can run the next task in parallel or the next task that depends on the outcomes of the previous tasks. And so with all of that, I would say, yeah&#8212;automate the boring stuff. Focus on what makes your products unique.</p><p><em><strong>9: In your experience, what branching strategy works best for teams on GitHub?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> I have a preference, so disclaimer first: this is an opinionated preference. For me, I prefer trunk-based development. A lot&#8212;because with the trunk-based development model, your history stays clean and you have only one single branch at every point in time. And you might ask, what if I need to go back in time and roll&#8230; the Git flow, which is always, you know, very good&#8230; the best branching strategy is the one that your team understands and they can follow consistently. Gitflow can work for&#8230; you know, complex release cycles and&#8230; and you want to have different release version numbers, and you jus&#8230; consistency and clarity matter, that&#8217;s&#8212;uh, what I would say, yeah.</p><p><em><strong>10: Now we know what you prefer personally. But for teams&#8212;what would you recommend for them? Would it be trunk-based development or Git Flow?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yeah, I would say trunk-based development&#8230; most&#8212;that&#8217;s your speed because you&#8230; you know that you know how to&#8230; You can quickly make quick changes, validate&#8230; collaborate. So that is particularly important for teams, yeah.</p><p><em><strong>11: Let us talk about the pros and cons of branching strategies. Could you quickly summarize what trunk-based development is best suited for versus Git Flow?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> If you&#8217;re building software and want to release features more often&#8212;say, once every day&#8212;with frequent changes to production or very short sprint cycles, trunk-based development is the best approach. If you tend to have multiple release versions in production&#8212;or you have the kind of application where customers expect separate versioned releases, then Git Flow is good so you can keep those separate branches of the same codebase and use them regularly. So yeah, that&#8217;s what I&#8217;ll say.</p><p><em><strong>12: With supply-chain attacks on the rise, security is a huge concern. What steps should developers and teams take on platforms like GitHub&#8212;enabling two-factor authentication, etc.&#8212;to prevent such attacks?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Security is everyone&#8217;s job. Every role needs to consider security as very important&#8212;not just the security architect. Every developer needs to factor security into the code they build, so use the built-in tools you have on GitHub to protect your code at every stage. For example, at the development stage, GitHub push protection helps block secrets from leaking from your IDE when you&#8217;re about to push to the remote&#8212;so that helps even before you get to CI/CD. When you&#8217;re about to merge, there are rule sets to protect code&#8212;for example, from accidental deletions of branches&#8212;and from dependencies and vulnerabilities. You can run vulnerability scans out of the box with a single-click default setup for code scanning. For secret scanning, you can scan code at rest or even code in PRs. </p><p>There are also security configurations you can apply to different sets of repositories&#8212;and you can have code scanning run on a predetermined frequency, whether weekly or monthly, across the organization. There&#8217;s also Dependabot, which looks at your dependencies and recommends updates; it tends to keep dependencies up to date and even opens automatic pull requests you can review and merge. For software supply chain integrity, you can implement build provenance using SLSA (a build attestation standard)&#8212;GitHub Actions is always on SLSA Build Level 2. Within GitHub, you can generate attestations to prove dependencies are valid&#8212;like a blockchain of your deployments&#8212;to show nothing was tampered with; you can store that on GitHub or as an artifact in Artifactory or other external package managers. </p><p>Definitely use two-factor authentication. GitHub supports that, and you can use single sign-on with Okta or Azure AD. You can also use Teams to manage roles, create custom roles with different permissions, and make sure every commit is signed and verified to add confidence that the person changing the code is the right person.</p><p><em><strong>13: How do Git and GitHub enable effective asynchronous collaboration across different time zones and teams?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yep, like you rightly hinted&#8212;yes, I work remotely. In fact, Git&#8230; for asynchronous teamwork. It helps. You don&#8217;t have to be in a meeting to make changes on GitHub, and clear communication is built into the platform. You can use GitHub Projects for planning. You can track status for each task&#8212;who&#8217;s working on what&#8212;and then it also provides a timeline view where you&#8217;re seeing the timeline for the different tasks. That helps when you have teams in different parts of the world, even people in the U.S. and [elsewhere]. GitHub Issues is that central place where we collaborate on a particular issue&#8212;you can create an issue to capture an idea, a bug, or a feature request. You can tag people, add labels that are customized to the way your organization works, and then you can create task lists within the issue. So it helps pull requests as well&#8212;it comes out of the box. </p><p>Pull requests is one very fantastic feature of GitHub, and I think&#8230; Peter&#8230; You can also use GitHub Discussions for discussions that are not specific to a particular issue or pull request&#8212;discussions are really trackable. You can also use discussions for social collaboration. Yep, these are the different features I can think of now. And then there&#8217;s documentation&#8212;wiki pages and README files. You can also create road map views and use that to collaborate on different projects.</p><p><em><strong>14: Are there any secret tips you&#8217;d like to share&#8212;little things we might have missed&#8212;about GitHub collaboration in a remote-team context?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yeah, absolutely. And the fact that you work with different people across different time zones means documentation becomes very important. Even though they&#8217;re developers&#8212;and we&#8217;re geeks&#8212;sometimes we just want to write in, you know, shorthand and short comments. But if you&#8217;re working asynchronously, you want to provide context in your issues and pull requests so people can understand the &#8220;why&#8221; and the &#8220;how&#8221; behind changes without a meeting. Use templates for issues and PRs, and follow a consistent convention for titles and descriptions. Use labels and project boards to make status clear at a glance. Encourage code owners and reviewers to leave actionable comments, not just approvals. And you want to have regular check-ins with your team&#8212;maybe you have a sync once a week or once in two weeks&#8212;so people feel connected while still relying on async work.</p><p><em><strong>15: You have a background in DevOps and change management&#8212;how do you see platforms like GitHub influencing team culture and process?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> GitHub is a fantastic tool for collaboration&#8212;especially when you want to bring people from silos to becoming more collaborative. In the development world, we call GitHub &#8220;social coding&#8221; because you&#8217;re writing code and working together with people. There&#8217;s transparency: when you&#8217;re making changes, you&#8217;re seeing what others are doing, and others can see what you&#8217;re doing&#8212;that transparency is really important for providing feedback. When you&#8217;re reviewing code, you can add inline comments, and that feedback can also drive continuous improvement. When you put automation in place, it saves people time, and they can use that time to work on bigger problems&#8212;so having that automation helps teams become more collaborative. People feel this developer happiness when they use GitHub in a transparent, collaborative way. Collaboration is a very core pillar of how the platform is built and shaped. Yeah.</p><p><em><strong>16: Are there any common pitfalls teams should avoid when integrating GitHub into their DevOps workflows?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Over-customization is one I see often. Sometimes platform engineering teams or DevOps engineers want to customize everything, and that can take away from the standard way of doing things. There&#8217;s overhead for you to maintain the customization, and overhead for the people who have to use it. You want to reduce that so your end users and consumers can use your application or software more easily&#8212;so avoid over-customization. Neglecting documentation is another. I&#8217;ve seen people create pull requests with great changes but little or no context. </p><p>Today, with GitHub Copilot, you can easily summarize the changes you&#8217;ve made in the pull request&#8212;beautifully&#8212;so try not to neglect documentation. Also, skipping retrospectives is a pitfall. Retrospectives help you look at what you did well, what you didn&#8217;t do well, and where you can improve&#8212;so don&#8217;t skip them. Over-customizing your platform, neglecting documentation, and skipping retrospectives are common pitfalls&#8212;and culture matters. The right tools can change what&#8217;s on your menu.</p><p><em><strong>17: Let&#8217;s talk specifically about Copilot and the future of coding. Is AI-assisted coding a boon to productivity&#8212;that&#8217;s the debate. From your perspective, how is AI changing the day-to-day work of developers?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> I&#8217;ve been in the industry for about 20 years writing software, and I&#8217;ve never seen anything like this before. It fundamentally improves and changes the way we write software today, and it&#8217;s not just a buzzword&#8212;AI has come to stay. We&#8217;ve seen people reduce the time it takes to introduce new features; we&#8217;ve seen improvements in the quality of the code as well&#8212;higher test coverage and fewer vulnerabilities when they scan the code. </p><p>On GitHub, we build GitHub on the GitHub platform&#8212;Copilot is the number one contributor with the highest number of contributions per week and per month today on the GitHub platform. We believe in the platform and in what AI has come to improve. The agentic capabilities I&#8217;ve seen today&#8212;if I had them coming up earlier in my career&#8212;I would achieve a lot of great things. </p><p>So if you&#8217;re not using AI today, you&#8217;re likely playing catch-up, because AI allows you to move at a much faster rate and safely, in a secure manner. AI is now the pair programmer. You&#8217;ll be seeing agents&#8212;so you can assign some complex or boring work to an agent within GitHub, the GitHub platform. You can have multiple issues in your backlog, and an agent can hand off from one agent to another, and GitHub Copilot has different agents. We will also be releasing many new agents, so you&#8217;ll have agents as your pair programmer or even your teammates. In addition to a development team of humans, you&#8217;ll now have these agents alongside the team, doubling and tripling their output and their throughput compared to those who don&#8217;t have AI today, yeah.</p><p><em><strong>18: Do you foresee AI assistance becoming a standard part of development? How should developers&#8212;especially junior engineers&#8212;take advantage of tools like Copilot while continuing to hone their coding skills?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> GitHub Copilot not only helps you write code&#8212;it helps you understand the code. You can ask GitHub to get a better understanding of the codebase. Let&#8217;s say you inherited it from a senior developer; Copilot can help you understand the different components of the code and what it thinks the code does. It can explain concepts for you&#8212;coding terminologies and development practices&#8212;and even identify components in the stack you use: &#8220;These are the components you use here.&#8221; You can ask questions such as, &#8220;How can I test-run this?&#8221;&#8212;and GitHub Copilot can help you go through that flow. Secondly, it helps with prototyping. You&#8217;re given the requirements, and you need to quickly prototype and experiment&#8212;an important task many people underestimate that developers do today. </p><p>From translating ordinary business needs into what software should do, Copilot can help with ideation, brainstorming, and prototyping as well. These are very good areas where a junior developer can really benefit. It also ensures the code follows certain coding practices, which means a junior developer can work as if the person is an experienced senior developer&#8212;because they have that assistant by their side. So you can solve problems with AI, not just write code.</p><p><em><strong>19: Let&#8217;s talk about caveats a little bit. Can Copilot negatively impact code quality? What is your take on this?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Oh, that&#8217;s a tricky one. Yes and no. Yes&#8212;in the sense that if you don&#8217;t know what you&#8217;re doing and something goes wrong, it will be hard for you to understand the code base and figure out how to fix it yourself. And if you find yourself in a remote area or you don&#8217;t have internet access, or you&#8217;re on a system where AI is not allowed&#8212;say, you&#8217;re building some, you know, covert, highly secure environment software&#8212;how will you be able to cope? So you also want to make sure that you understand the language it&#8217;s written in, so that you can triage some of those things. And in terms of whether AI in general can introduce bugs in the code&#8212;there are times when, if there is no good prompting, Copilot can build (or AI tools can build) code in a different way, because the model has its own preferences. You can read up on how to write good prompts for AI tools&#8212;for GitHub Copilot. </p><p>Then there are times where you can say, &#8220;I want you to do it in this particular way, and I want you to use these libraries,&#8221; because typically there is more than one way of solving a problem. If you have a kind of library that is homegrown or approved for use, you can create what we call Copilot Instructions to instruct Copilot to write code in a way that is accepted by the organization you&#8217;re working in. And whenever it introduces bugs, there is a review agent you can use to review the entire code base itself and review it against best practices&#8212;and even against your internal standard practices&#8212;within an enterprise or a team.</p><p><em><strong>20: How do you feel teams can use AI coding assistance responsibly to ensure the generated code meets quality and security standards?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Good question. Not all AI coding tools are the same, and the dependency models you use can determine what kind of responsible use is available. For example, GitHub Copilot&#8212;being part of the Microsoft ecosystem&#8212;has multiple layers internally for the responsible use of AI. First, it looks at the kind of prompts you&#8217;re sending, checks them against responsible-use standards, and sanitizes them. When it sends back the code, it looks at that code to be sure it&#8217;s a responsible use&#8212;making sure there are no personal data leaks or similar risks&#8212;within the system itself, depending on which AI system or product you&#8217;re using. And for the human being, I would say always review&#8212;and then use automated checks&#8212;because the volume of what AI will be contributing to your code will increase. </p><p>That means it will be time-consuming to review everything manually, so you want to reduce that burden with automation while still reviewing. Make sure a lot of the review work has been done in advance with automated checks, and treat Copilot as a collaborator, not a replacement&#8212;that&#8217;s why GitHub calls them &#8220;co-pilots,&#8221; not &#8220;the pilot.&#8221; Someone is still in charge, driving what it does, yeah.</p><p><em><strong>21: Continuing to talk about AI and its impact&#8212;The latest Stack Overflow developer survey shows a paradox &#8211; nearly 80% of devs are now using AI tools in some form, yet only ~3% highly trust the answers from AI. In fact, 75% of developers say they turn to a human colleague when they don&#8217;t trust an AI&#8217;s answer. How do you envision the collaboration between developers and AI tools going forward? For example, will coding become more about validating and refining AI-generated solutions?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yeah, I think I&#8217;d be keen to see that report and see, you know, what tools they&#8217;re using. It would be good to see a breakdown of that.</p><p>Because I feel it may not be the same experience for every tool. With that said, this revolution itself&#8212;right? Oh, you know, this is another time in human history when you look back and see that a huge change has occurred. This is another change. And there is, as expected, a bit of resistance when change comes. Some people have not used it, and some people who used it don&#8217;t understand exactly everything it does and how it runs in the back end. And it&#8217;s difficult to trust what you don&#8217;t know&#8212;how it runs or how it works. You may need to understand the design and the architecture of that AI tool to know what&#8217;s going on under the hood. </p><p>And many of these AI tools also give flexibility to configuration&#8212;so you can configure what the AI is able to do, and that way you can control and decide the way the AI works and what it&#8217;s allowed to see. For example, with GitHub Copilot, there&#8217;s a set of files you can exclude so the AI will never look at those files&#8212;maybe they&#8217;re secret files and things like that&#8212;and it will never look at them, even in your codebase. So this helps. And then knowledge sharing as well&#8212;some people love it because they&#8217;ve used it very well and they&#8217;ve seen the impact it has had on their productivity. So knowledge sharing will help the community; it will help people balance up and understand things better&#8212;how some of these tools work. So, yeah, I&#8217;ll encourage knowledge sharing, and then understanding the design and the architecture and the documentation on how it runs.</p><p><em><strong>22: That&#8217;s some really good advice. But how do we ensure that junior devs still learn critical thinking instead of blindly accepting AI output? Because that&#8217;s a genuine risk the community is facing at this time.</strong></em></p><p><strong>Ayodeji Ayodele:</strong> Yes&#8212;I would say use AI as a learning tool, not a crutch. Use AI to understand and know things better. And you can also use AI to augment knowledge. At times, knowledge is scattered within an enterprise in different sources, and it&#8217;s hard to have access to&#8212;or remember&#8212;all those areas&#8212;of course we have other platforms like SharePoint, Jira, ServiceNow, and things like that. Use AI to augment and consolidate this knowledge base so that people can have a richer source to derive information from. Consolidating the knowledge base can really help. </p><p>And of course you can also have meetups; they can really help to improve knowledge sharing. You can also come and showcase what you&#8217;ve built, and people can, ask questions to&#8212;maybe, you know&#8212;test your assumptions for the software you built, and that can help. AI itself can help in the scaling of that&#8212;in bringing all of it together. In building that, you can also capture notes and conversations and improvements, suggestions, and interpret them back into the code. Or document those comments and feedback in your repositories&#8212;AI can help with that for you&#8212;but humans have to ask the right questions, yeah.</p><p><em><strong>23: There&#8217;s a lot of debate about whether AI makes people more productive or makes them worse&#8212;you know, more dependent on them. And I think at this point it&#8217;s a bit pointless to go into that debate. But complex, creative problem-solving is still uniquely human. In the same Stack Overflow survey, roughly 40% of developers said that AI tools performed poorly on complex tasks. Having said that&#8212;since the space is moving fast and there are developments and improvements&#8212;given AI&#8217;s current limitations, what uniquely human skills should developers focus on strengthening now to remain relevant?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> First and foremost, there was that comment about AI not being able to perform complex tasks. In the last three months, I&#8217;ve seen a major change. The models have evolved and people are beginning to say, &#8220;You know what, this is going really magical.&#8221; So I&#8217;ve seen some AI models out there that can perform really complex tasks. There are some models that take a shorter time and just give you&#8212;on the fly. So there are different strengths to different models. </p><p>So yes, AI can help with complex tasks, but AI can&#8217;t replace creativity. AI cannot replace empathy. It cannot replace problem-solving. These are innate skills for humans. I know I&#8217;ve seen some people trying things like that, but I don&#8217;t think they can ever be like humans. So I don&#8217;t think AI will replace human beings, you know. So you want to focus on creativity, on communication, on design thinking, and, you know, adaptability as well.</p><p><em><strong>24: What core competencies will define a successful developer in the next 5&#8211;10 years as tools like Copilot evolve?</strong></em></p><p><strong>Ayodeji Ayodele:</strong> So the developer role is evolving&#8212;more than just writing code, it&#8217;s about understanding systems, collaborating across functions, and adapting as tools change. You want to build strong fundamentals, but stay curious and keep learning new paradigms, frameworks, and practices as they emerge. Communication and collaboration matter a lot&#8212;being able to explain your thinking and work well with others. So the most valuable skill in tech isn&#8217;t coding&#8212;it&#8217;s learning.</p><div><hr></div><p>To build practical mastery of Git and GitHub&#8212; from version control basics to collaborative workflows, secure automation, and AI-assisted productivity&#8212;check out <em><strong><a href="https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040">GitHub Foundations Certification Guide</a></strong></em> by Ayodeji Ayodele (Packt, 2025). Through step-by-step labs, real-world projects, and exam strategies, it helps you prepare for the GitHub Foundations certification while adopting best practices for issues and pull requests, GitHub Projects, privacy and security controls, and GitHub Copilot&#8212;so you can level up your skills and ship better software, faster.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c1C4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775" width="372" height="458.86813186813185" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:372,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;GitHub Foundations Certification Guide&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/github-foundations-certification-guide-9781836206040&quot;,&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="GitHub Foundations Certification Guide" title="GitHub Foundations Certification Guide" srcset="https://substackcdn.com/image/fetch/$s_!c1C4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!c1C4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9d177e6d-10ef-4e8d-9954-776efd177ad7_2250x2775 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>Here&#8217;s what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FvUZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FvUZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 424w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 848w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 1272w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FvUZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png" width="885" height="485" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e55c92f-6989-49dc-9cdc-702306914746_885x485.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:485,&quot;width&quot;:885,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:109413,&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://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.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_!FvUZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 424w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 848w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.png 1272w, https://substackcdn.com/image/fetch/$s_!FvUZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e55c92f-6989-49dc-9cdc-702306914746_885x485.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!VEfR!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VEfR!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 424w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 848w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1272w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png" width="871" height="386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:386,&quot;width&quot;:871,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:84580,&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://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.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_!VEfR!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 424w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 848w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.png 1272w, https://substackcdn.com/image/fetch/$s_!VEfR!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff0b9a3ff-7f6b-44c5-8c4e-a8bf341e5845_871x386.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Jch1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Jch1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 424w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 848w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1272w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png" width="856" height="388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d8328e5f-7901-4408-8343-78bba89ca87a_856x388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:856,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:95238,&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://deepengineering.substack.com/i/178064311?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.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_!Jch1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 424w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 848w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.png 1272w, https://substackcdn.com/image/fetch/$s_!Jch1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd8328e5f-7901-4408-8343-78bba89ca87a_856x388.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></p><p></p>]]></content:encoded></item><item><title><![CDATA[From Enablement to Reliability: How Platform Engineering Aligns with SRE Goals – A conversation with Sean P Alvarez and Ajay Chankramath ]]></title><description><![CDATA[On distinguishing SRE from Platform Engineering, building developer-first platforms, and the practical lessons behind treating platforms as products.]]></description><link>https://deepengineering.net/p/pragmatic-platform-engineering-at</link><guid isPermaLink="false">https://deepengineering.net/p/pragmatic-platform-engineering-at</guid><dc:creator><![CDATA[Sushma Reddy]]></dc:creator><pubDate>Thu, 25 Sep 2025 09:06:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/7iW_vavUFs0" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Platform Engineering is often confused with Site Reliability Engineering (SRE) or seen as the latest rebranding of DevOps. In reality, it represents a distinct shift: treating internal platforms as products, designed for adoption and developer experience. In this conversation, we speak with <strong>Sean P. Alvarez</strong> and <strong>Ajay Chankramath</strong>&#8212;co-authors of the forthcoming <em><strong><a href="https://www.packtpub.com/en-us/product/the-platform-engineers-handbook-9781806380121">Platform Engineer&#8217;s Handbook</a></strong></em> (Packt, 2026)&#8212;about where SRE and Platform Engineering converge, where they differ, and why collaboration between them is essential in today&#8217;s organizations.</p><p>Sean P Alvarez is the <strong>Chief Technology Officer</strong> of the <strong>Life Sciences business </strong>at<strong> Brillio</strong>, where he leads engineering teams and advises clients on cloud strategy and platform modernization. With over 15 years of experience in regulated industries and consulting, he specializes in applying Platform Engineering principles to drive enterprise-scale transformation.</p><p>Ajay Chankramath is the <strong>Cofounder and CEO of <a href="https://platformetrics.com/about.html">Platformetrics</a></strong>. With more than 35 years of global experience, he has led platform engineering at Thoughtworks, Oracle, Broadridge, and Xilinx, and is recognized as a Platform Engineering Ambassador and Team Topologies Advocate.</p><p>Together, they are writing <em><strong><a href="https://www.packtpub.com/en-us/product/the-platform-engineers-handbook-9781806380121">The Platform Engineer&#8217;s Handbook</a></strong></em> on building secure, developer-focused platforms that streamline modern software delivery. The book takes a hands-on, &#8220;build first, clarify as you go&#8221; approach&#8212;guiding readers from source control governance and Kubernetes runtimes to observability, self-service onboarding, and AI-augmented tooling. Built for practitioners, it equips engineers to design platforms that scale without disrupting delivery.</p><p>You can watch the full conversation below&#8212;or read on for the complete transcript.</p><div id="youtube2-7iW_vavUFs0" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;7iW_vavUFs0&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/7iW_vavUFs0?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1: Sean, Can you take us back to the moment when you first realized you were doing what we now call Platform Engineering? I imagine the term might not have existed yet, but the work was already there. </strong></em></p><p><strong>Sean Alvarez:</strong> It&#8217;s a great question, and thank you. I&#8217;ve always worked in somewhat regulated industries&#8212;life sciences and the financial industry. In those industries, the release process tends to be cumbersome. There are a lot of compliance issues, a lot of sign-offs that need to happen, and a lot of double and triple checking to make sure things won&#8217;t go wrong or that auditing won&#8217;t get messed up.</p><p>When I first started working with companies that would burn deliverables onto CDs and move things back and forth, I tried to introduce automation, but there wasn&#8217;t much trust in it at first. That led to centralized teams who stayed up all hours of the night doing deployments over and over again. There really wasn&#8217;t trust to move to something like what we&#8217;d now call DevOps.</p><p>At the same time, there was a desire to speed up development as the industry matured and as more start-ups entered the space and moved more nimbly. We wanted to make the deployment process&#8212;and the SDLC overall&#8212;less of a dreaded gate. Instead of developers thinking, &#8220;Oh no, I need to submit this to the DevOps team or the Operations team,&#8221; we wanted to turn it into an enabler.</p><p>To do that, we had to work across silos&#8212;security teams, networking teams, compliance, even upper management and governance&#8212;to put automation in place. That&#8217;s really where Platform Engineering took off for me: ensuring deployments were safe, compliant, and reliable, while allowing developers to move faster and giving the organization confidence that releases would go smoothly.</p><p><em><strong>2: Every company has its own unique engineering culture. In your journey across different organizations, how have those environments shaped your understanding of what Platform Engineering really is?</strong></em></p><p><strong>Sean Alvarez:</strong> As I moved into more of a consulting role, I worked across industries and saw more organizations that operated closer to start-ups&#8212;moving fast even at the risk of breaking things. As the industry matured, I realized Platform Engineering wasn&#8217;t just about enabling speed. Developers also had to want to use it.</p><p>When you have individual full-stack teams in control of their deployments, Platform Engineering can look like extra work&#8212;just another backlog item or another check. If the platform feels forced, adoption won&#8217;t happen. Instead, it has to be something they want to use.</p><p>It&#8217;s not a &#8220;build it and they will come&#8221; situation&#8212;it has to be product-oriented. Just like external products compete for market share, internal platforms have to attract developer adoption. If we think of engineers as internal customers, that adoption becomes the real measure of success. It&#8217;s where the interests of leadership and developers align, and that&#8217;s when Platform Engineering really becomes powerful.</p><p><em><strong>3: Ajay, in your journey across organizations, how did different environments shape your understanding of Platform Engineering?</strong></em></p><p><strong>Ajay Chankramath:</strong> My journey has been slightly different, but not too dissimilar to what Sean described.</p><p>Many years ago, I had a role supporting developers, though I was a developer myself. My first job involved building complex algorithmic software. But I noticed that developers&#8212;including my own team&#8212;always encountered friction. They couldn&#8217;t get things done the way they wanted.</p><p>Back then, roles were structured differently. There was a clear divide between developers writing code and then &#8220;throwing it over the wall&#8221; to another group&#8212;experienced engineers who built the product software. I started looking at this challenge through fundamental principles of software design, like loose coupling and high cohesion, which have been around for decades.</p><p>The first thing I did was identify common developer pain points and create reusable components to improve productivity. That led to a patent we called ROMS&#8212;Reusable Object Modules. This was 25 years ago, before DevOps, SRE, or Platform Engineering existed. Looking back, ROMS was essentially a fundamental building block of a platform: reusable capability packaged as a library.</p><p>We presented it at conferences, and that became my first foray into what I&#8217;d now recognize as platform thinking. The spark came from applying core software principles, which is probably the theme of today&#8217;s conversation&#8212;how principles extend into SRE and Platform Engineering.</p><p>Eventually, leadership asked me to build a team around this work. That transition took me from software development into support-related activities, which we then called Software Productivity Automation. We didn&#8217;t have today&#8217;s terminology, but the idea was similar.</p><p>Over time, working mainly in large companies, I saw the federated model in action: a centralized set of services with individual teams building on top. It&#8217;s a strong model because it gives smaller teams autonomy. But the downside is that centralized teams face huge backlogs. That almost never works out, and so the real question becomes: are individual teams able to be self-sufficient?</p><p>With the advent of public cloud, that&#8217;s changed significantly. Over the last 10&#8211;15 years, cloud services have made it much easier for smaller organizations to adopt these practices and integrate them into their ecosystems.</p><p><em><strong>4: What are some of the biggest misconceptions you see today when teams practice SRE and Platform Engineering?</strong></em></p><p><strong>Ajay Chankramath:</strong> One of the biggest misconceptions is that constructs like SRE, Platform Engineering, DevOps, or DevEx are simply newer terms replacing older ones. That&#8217;s absolutely not the case. Each has its own role in the larger scheme.</p><p>We emphasize this in our book: DevOps is not a team and should not be a job title. It&#8217;s a cultural paradigm&#8212;about improving collaboration and communication across the SDLC.</p><p>SRE is about applying software engineering principles to operations to create highly reliable production systems. DevEx&#8212;developer experience&#8212;has always been there. It&#8217;s about how developers interact with tools, frameworks, and processes throughout the SDLC.</p><p>If you define these clearly, the overlap becomes easier to see. Platform Engineering sits at the center, enabling all three&#8212;giving developers self-service capabilities, enabling SREs, and supporting the DevOps culture.</p><blockquote><p>That&#8217;s why <strong>it&#8217;s wrong to think Platform Engineering replaces SRE. SRE has existed since 2004 and continues to serve a distinct purpose</strong>. Platform Engineering is complementary&#8212;it enables SRE and DevOps to succeed together.</p></blockquote><p><strong>Sean Alvarez:</strong> I&#8217;ve also seen misconceptions where SRE is viewed only as production support. People then ask: does Platform Engineering automate that away? The answer is no.</p><p>SRE plays a vital role in ensuring production reliability. Platform Engineering spans the entire SDLC&#8212;from onboarding new team members to building, deploying, and running software in production. SRE should inform what Platform Engineering builds to make their jobs more efficient, but they aren&#8217;t the only users of the platform.</p><p>Platform success depends on collaboration&#8212;SREs, developers, and business owners all need to work together.</p><p><em><strong>5: There&#8217;s a lot of overlap between SRE, DevOps, DevEx, and Platform Engineering. Would you like to talk a bit about what each brings to the table, how you personally draw boundaries between them, and where they are similar or different?</strong></em></p><p><strong>Ajay Chankramath:</strong> Absolutely, that&#8217;s a great question. Let me step back and offer one-line definitions for clarity.</p><blockquote><p><strong>DevOps</strong> is the cultural movement aimed at breaking down silos. It improves collaboration across the SDLC&#8212;from planning and coding to testing, deployment, and operations.</p></blockquote><blockquote><p><strong>SRE</strong> began at Google as the application of software engineering principles to operations. Historically, operators knew systems well but weren&#8217;t software developers. Google shifted that model, expecting SREs to understand both software design and systems. Today, not every organization does it the same way, but the principle remains: SRE is more than system administration&#8212;it&#8217;s a role that requires deeper software knowledge.</p></blockquote><blockquote><p><strong>DevEx</strong> is the outcome of enabling developers to be more productive. It&#8217;s well-studied now, with research from books like <em>Accelerate</em> and companies such as GetDX. Measurement is critical: you can&#8217;t improve productivity without first understanding where you stand.</p></blockquote><blockquote><p><strong>Platform Engineering</strong>, then, is about the tools, processes, and techniques that improve all these aspects. It&#8217;s not just building automation or software&#8212;it&#8217;s building capabilities as a product. That&#8217;s why it&#8217;s different. Platform engineers don&#8217;t &#8220;take code and push it to production.&#8221; That&#8217;s an anti-pattern. Instead, the role is about enabling developers to be self-sufficient, reducing friction, and making the SDLC as fast and efficient as possible.</p></blockquote><p>Think of it like providing APIs or libraries. Developers need to ask: &#8220;What do I need to be productive?&#8221; Platform Engineering exists to deliver those capabilities.</p><p><strong>Sean Alvarez:</strong> What makes our <em>Platform Engineer&#8217;s Handbook</em> unique is its practical approach. We&#8217;re less about theory and more about how to actually create these capabilities.</p><p>If you look at tooling, the distinctions become clearer. Platform Engineering often brings to mind Kubernetes clusters and deployment automation&#8212;but it&#8217;s much broader than that. It includes enabling new projects, automating pipelines, and creating self-service capabilities.</p><p>SRE, on the other hand, focuses on reliability&#8212;SLIs, SLOs, SLAs, and observability. Their responsibility is to ensure systems are running well in production.</p><p>Developer experience isn&#8217;t just about portals like Backstage. It&#8217;s about making platforms easy to use. Whether through APIs, CLI tools, or portals, DevEx ensures developers adopt what Platform Engineering provides.</p><p>Together, these roles form a layered model: Platform Engineering builds capabilities, DevEx makes them usable, and SRE ensures reliability. The overlap is real, but the responsibilities remain distinct.</p><p><em><strong>6: Looking back on your years building and scaling platforms, what was one of the hardest lessons you learned at the enterprise level?</strong></em></p><p><strong>Sean Alvarez:</strong> When people hear &#8220;enterprise,&#8221; they often think &#8220;process&#8221;&#8212;multiple levels of management, sign-offs, and compliance. That usually leads to a push for standardization.</p><p>In many organizations, Platform Engineering is introduced as a way to rein in DevOps sprawl. Instead of every team building its own pipeline or observability, leadership wants a single, standardized platform for everyone.</p><p>But forcing a single solution across the enterprise is often a recipe for failure. It creates complexity, slows delivery, and leaves some teams feeling stifled. They start working around the platform to meet their needs, and friction grows.</p><blockquote><p>A better approach is the <strong>80/20 rule</strong>: serve the 80% of teams whose needs can be standardized, and let the remaining 20% adapt their processes where necessary. That reduces time-to-value, avoids endless edge-case debates, and ensures most teams actually benefit from the platform.</p></blockquote><p><strong>Ajay Chankramath:</strong> Sean made a great point about the 80/20 rule. You&#8217;ll never get 100% success, and you shouldn&#8217;t aim for it. The question is: how do you achieve that 80%?</p><p>In our book, we outline seven principles that guide successful platforms. A few highlights:</p><blockquote><ol><li><p><strong>Measure what you improve.</strong> Quantify waste and friction so improvements are visible.</p></li><li><p><strong>Treat platforms as products.</strong> This isn&#8217;t just automation&#8212;it&#8217;s technical product management. Capabilities must be managed like products with clear value propositions.</p></li><li><p><strong>Balance build vs. buy.</strong> Engineers love to build, but with so many products available, organizations must consider total cost of ownership.</p></li><li><p><strong>Design for composability.</strong> Platforms aren&#8217;t just Kubernetes. They consist of multiple components. Each must be composable, extensible, and replaceable.</p></li><li><p><strong>Prioritize observability.</strong> Don&#8217;t limit it to applications&#8212;extend it across the SDLC.</p></li><li><p><strong>Enable team autonomy.</strong> If teams wait on others for security or approvals, waste accumulates. Platforms must empower autonomy.</p></li><li><p><strong>Articulate value.</strong> This is critical. Success depends on clearly communicating the value unlocked, not just building capabilities.</p></li></ol></blockquote><p>Without these principles&#8212;especially value articulation&#8212;platform engineering risks losing executive support and ultimately failing.</p><p><em><strong>7: Do you think there is confusion or resistance when people start working on Platform Engineering or its principles? Are there gaps, and what has worked for you in bridging those gaps?</strong></em></p><p><strong>Sean Alvarez:</strong> One reason organizations adopt Platform Engineering is to standardize and make developers&#8217; jobs easier. But in doing so, we often take control away from developers.</p><p>For example, if we say, &#8220;Every developer must use this deployment pipeline,&#8221; it might simplify things for the organization, but inevitably some teams will run into cases it doesn&#8217;t support. Maybe the pipeline was designed for a single service, but a team needs to deploy three. Now they&#8217;re frustrated, waiting on Platform Engineering to adapt the pipeline&#8212;or worse, being told to make their work fit into it.</p><p>That dynamic quickly creates confusion and resentment. Developers no longer want to use the platform, and adoption stalls.</p><p>The way to bridge this gap is to treat the platform as an internal product. The most effective way is to have a <strong>technical product owner</strong>&#8212;someone who understands product management practices but also has the technical depth to talk with developers.</p><p>This role continuously interviews developers, identifies gaps in their day-to-day work, and ensures the platform gives them flexibility&#8212;guardrails where needed, but also options to override defaults when necessary. By organizing and prioritizing a backlog around developer needs, a technical product owner ensures the platform provides real value, which drives adoption and organizational success.</p><p><strong>Ajay Chankramath:</strong> Sean covered most of it, but I&#8217;ll add this: the technical product owner role is not about saying &#8220;yes&#8221; to everything developers ask for. Every developer comes with their own perspective, and building every request would be unsustainable.</p><p>The job is to balance ROI across the organization. Building platform capabilities costs time and money. The question is: what value will this unlock across the enterprise, not just for an individual team?</p><p>That&#8217;s where challenging requirements becomes important. By pushing back and prioritizing based on organizational value, the technical product owner ensures investments make sense.</p><p>This ties back to the seventh principle we mentioned earlier&#8212;<strong>articulating value.</strong> Without it, platforms risk losing executive sponsorship. We&#8217;ve seen it happen: executives reassign platform engineers back into product teams because they don&#8217;t see visible value.</p><p>A strong technical product owner prevents that by showing how the platform delivers ROI across the organization. That value articulation is often the difference between success and failure.</p><p><strong>8: Your book, </strong><em><strong>Platform Engineer&#8217;s Handbook</strong></em><strong>, takes a &#8220;build first, clarify as you go&#8221; approach. Instead of starting with strategy decks and frameworks, you dive straight into building. Why do you think this approach works better, especially for technologists new to Platform Engineering? And what makes it more effective than leading with theory?</strong></p><p><strong>Sean Alvarez:</strong> Great question. If you think about who gets into Platform Engineering, it&#8217;s often people from two backgrounds. Some are software developers who understand APIs and architecture but aren&#8217;t used to handling infrastructure. Others come from operations or SRE roles, familiar with Terraform, ServiceNow, or Dynatrace, but less with software development practices like GitHub projects or release pipelines.</p><p>Platform Engineering covers the entire SDLC, which is a big scope. You can&#8217;t really &#8220;practice&#8221; it in a small sandbox&#8212;it requires working with real teams, real projects, and real deployments.</p><p>That&#8217;s why starting with building makes sense. It&#8217;s similar to &#8220;dogfooding&#8221;&#8212;using what you create. If you build even a small demo platform and deploy an application on it, you immediately see the friction developers face. That teaches you what features are needed. You then build those features, measure their impact, and learn from the experience.</p><p>The theory and strategy become clearer once you&#8217;ve lived through the practice. You&#8217;re not just reading slides&#8212;you&#8217;ve felt the difference. That makes the lessons stick and prepares you to scale up to enterprise scenarios.</p><p><strong>Ajay Chankramath:</strong> I agree. Developers understand software best. If you show them real builds and workflows, you bring them along much faster than if you start with strategy.</p><p>When this idea was first proposed, Sean and I knew it would be a challenge because we were used to explaining the &#8220;why&#8221; first. But flipping the model is powerful. It engages developers with what they already enjoy&#8212;building&#8212;and then shows them why it matters.</p><p>That&#8217;s why we believe this approach will make the book stand out. It&#8217;s not theory-heavy; it&#8217;s practical, hands-on, and aligned with how developers actually learn.</p><p><em><strong>9: Ajay, you&#8217;ve repeatedly emphasized the seven principles and the importance of measuring value. Do you think taking a &#8220;build first, clarify as you go&#8221; approach will help highlight that key aspect of value measurement?</strong></em></p><p><strong>Ajay Chankramath:</strong> That was the first question that came to my mind when we considered writing this book. We&#8217;ve always said you need to measure something before you can improve it. How would that work with a &#8220;build first&#8221; model?</p><p>The more I thought about it, the more I realized this approach makes value measurement easier. When you&#8217;re actually building, the value is visible right there&#8212;not in a spreadsheet or a theoretical model. Developers can see what they&#8217;ve created, how much effort it took, and what productivity gains it delivers.</p><p>Instead of abstract discussions, you have concrete examples: &#8220;I built this, and here&#8217;s the measurable improvement.&#8221; That makes the articulation of value much stronger.</p><p>This is also one of the unique aspects of the book. I haven&#8217;t seen another resource take this approach&#8212;making value measurement practical and hands-on. It&#8217;s a challenge, but we&#8217;re confident it will provide a powerful way to connect principles with real-world practice.</p><p><em><strong>10: Looking ahead, as more organizations mature their platform teams, how do you see the SRE role evolving? Do you expect a dramatic shift in the next few years?</strong></em></p><p><strong>Ajay Chankramath:</strong> Absolutely&#8212;and it&#8217;s a great question. Let me break it down across a few dimensions:</p><blockquote><ol><li><p><strong>Specialization:</strong> SREs focus on complex reliability challenges, while platform engineers focus on building capabilities that enable both developers and SREs. The partnership between these roles will strengthen. Instead of friction over &#8220;who owns what,&#8221; SREs and platform engineers will complement each other.</p></li><li><p><strong>Abstraction.</strong> SREs will increasingly work at higher levels&#8212;service meshes and clusters&#8212;rather than building individual features. Their focus will stay on ensuring reliability under pressure, not on building platform products. That&#8217;s where collaboration with platform engineers becomes critical.</p></li><li><p><strong>Domain-driven platform engineering:</strong> This means applying platform principles directly into products, not just infrastructure. For SREs, it requires more domain knowledge&#8212;something Google emphasized in its original SRE model but that has been diluted over time. I believe we&#8217;ll see a return to that principle.</p></li><li><p><strong>AI:</strong> SREs are already using AI for anomaly detection, root cause analysis, and automated remediation. Platform engineers will need to provide capabilities that make this easier. AI won&#8217;t replace these roles, but it will reshape them, moving focus from rote tasks to domain-driven decision-making.</p></li><li><p><strong>Risk and compliance:</strong> Especially in industries like finance and healthcare, SREs will need to take on more responsibility here, supported by platform capabilities. Compliance is not going away&#8212;it&#8217;s only becoming more central.</p></li></ol></blockquote><p>We&#8217;ll also see some fluidity between roles. Some SREs will transition into Platform Engineering if their interests and skills align more with building capabilities, and vice versa. This cross-pollination will strengthen both practices.</p><p><em><strong>11: Sean, you mentioned that much of Platform Engineering is about automating deployments and similar processes. Do you think AI can really make a big difference here, or is it still hype and early experimentation?</strong></em></p><p><strong>Sean Alvarez:</strong> Ajay mentioned the growing importance of domain knowledge, and I think that&#8217;s the key. Every time there&#8217;s a wave of abstraction, people ask if their jobs will disappear.</p><p>When cloud and serverless databases arrived, DBAs wondered if they were obsolete. Now with AI, people are asking the same thing: will software developers vanish because AI can write code? Do we even need Platform Engineering if AI can generate infrastructure as code, analyze logs, and raise alerts automatically?</p><p>The reality is that AI can take over rote, repetitive tasks&#8212;like writing observability queries or scanning logs for anomalies. That frees SREs and platform engineers to focus on higher-value work: understanding what uptime means in a given domain, or which processes truly require five nines of reliability.</p><p>For example, in fintech, stock-trading throughput during the day has a very different priority than a nightly batch process. One demands higher uptime even if it costs millions more; the other can tolerate delays. SREs and platform engineers, with their domain understanding, are the ones who can make those calls and guide how AI should be applied.</p><p>So I see AI as an inflection point. It won&#8217;t replace these roles&#8212;it will elevate them. The day-to-day debugging and manual tasks will shrink, while the focus shifts to domain analysis and delivering business value.</p><p><em><strong>12: For someone currently in an SRE or DevOps role who is now expected to build or contribute to an internal platform, what&#8217;s the one mindset shift or practical skill you&#8217;d suggest they prioritize? And what&#8217;s the first practical step they should take to set themselves up for success?</strong></em></p><p><strong>Sean Alvarez:</strong> The mindset shift is this: whenever you build something&#8212;whether it&#8217;s a script or an automation&#8212;ask yourself, &#8220;Will someone need to contact me to use this?&#8221; If the answer is yes, it&#8217;s not done yet.</p><p>The goal is self-sufficiency. If a developer needs help every time they use your tool, you&#8217;re stuck in an operations role&#8212;answering tickets all day&#8212;instead of moving on to the next feature. True platform engineering means building things that others can use independently. That mindset is critical.</p><p><strong>Ajay Chankramath:</strong> Sean gave a great example. To tie it together: the biggest shift is adopting a <strong>product mindset.</strong> Every script or automation should be treated like a product with long-term viability.</p><p>The second piece&#8212;and sometimes more important than technical skills&#8212;is <strong>communication</strong>. Build your soft skills. Relationships, collaboration, and communication determine whether a platform succeeds. Developers historically avoided this, but today it&#8217;s essential.</p><p>AI or not, tools will come and go. The constant is people. If you can communicate, align stakeholders, and articulate value, you&#8217;ll set yourself&#8212;and your platform&#8212;up for success.</p><div><hr></div><p>To explore the principles and practices discussed in this conversation in greater depth&#8212;including building developer-focused platforms from a blank slate, embedding observability and security, enabling self-service onboarding, and layering in AI-augmented services&#8212;keep an eye out for <em><strong><a href="https://www.packtpub.com/en-us/product/the-platform-engineers-handbook-9781806380121">The Platform Engineer&#8217;s Handbook</a></strong></em> by Sean P Alvarez and Ajay Chankramath, coming August 2026.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/the-platform-engineers-handbook-9781806380121" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9e60!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!9e60!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!9e60!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!9e60!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9e60!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775" width="370" height="456.4010989010989" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:370,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;The Platform Engineer's Handbook&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/the-platform-engineers-handbook-9781806380121&quot;,&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="The Platform Engineer's Handbook" title="The Platform Engineer's Handbook" srcset="https://substackcdn.com/image/fetch/$s_!9e60!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!9e60!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!9e60!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!9e60!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd93f741-5bea-42d7-980f-d0972b8f6ea5_2250x2775 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 book provides a hands-on, progressive journey: from source control governance and Kubernetes runtimes to developer portals, reusable CI/CD workflows, infrastructure blueprints, and FinOps observability. Each chapter combines concepts with lab-based exercises and production-ready patterns, equipping engineers to build scalable, secure platforms that streamline software delivery.</p>]]></content:encoded></item><item><title><![CDATA[Pragmatic Clean Architecture in Python: A Conversation with Sam Keen]]></title><description><![CDATA[Applying DDD and the dependency rule in real-world Python&#8212;keeping frameworks at the edge, modeling with dataclasses/Pydantic, and using AI without breaking clean boundaries.]]></description><link>https://deepengineering.net/p/pragmatic-clean-architecture-in-python</link><guid isPermaLink="false">https://deepengineering.net/p/pragmatic-clean-architecture-in-python</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Thu, 18 Sep 2025 06:28:01 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/NhG2zzaek_E" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>From structuring APIs and isolating domain logic to refactoring legacy systems, Python&#8217;s flexibility presents both opportunities and challenges for building sustainable software. In this conversation, we speak with <span class="mention-wrap" data-attrs="{&quot;name&quot;:&quot;Sam Keen&quot;,&quot;id&quot;:11641009,&quot;type&quot;:&quot;user&quot;,&quot;url&quot;:null,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a4c7deeb-ea40-40f3-8793-06bd0d441d29_500x500.jpeg&quot;,&quot;uuid&quot;:&quot;ea36128a-0f98-45f9-83fb-73f6bb9ae3b4&quot;}" data-component-name="MentionToDOM"></span> &#8212;author of <em><strong><a href="https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886">Clean Architecture with Python</a></strong></em> (Packt, 2025)&#8212;about applying architectural principles to real-world Python projects without sacrificing the language&#8217;s ethos.</p><p>Sam is a <strong>software engineering leader</strong> with over 25 years of experience, a polyglot developer who has used Python everywhere from early-stage startups to large-scale systems at AWS, Lululemon, and Nike. At Lululemon, he led the company&#8217;s first cloud-native development team, setting foundational standards for distributed architecture. Currently a <strong>Principal Engineer</strong> at <strong>Pluralsight</strong>, he focuses on leveraging generative AI for software engineering enablement&#8212;building tools that amplify developer productivity while preserving architectural integrity.</p><p>In this interview, we explore how clean architecture can be adapted to Python&#8217;s dynamic nature, where SOLID principles prove tricky, and how to keep frameworks like Django and FastAPI from leaking into core logic. We also discuss pragmatic strategies for enforcing the dependency rule, modeling entities and value objects with Python&#8217;s modern features, and managing testing and refactoring in complex systems. Looking ahead, Sam offers insights on how AI is reshaping development workflows and what it means for applying clean architecture across services and scaling applications.</p><p>You can watch the full conversation below&#8212;or read on for the complete transcript.</p><div id="youtube2-NhG2zzaek_E" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;NhG2zzaek_E&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/NhG2zzaek_E?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1: What motivated you to write Clean Architecture with Python, and why do you think Python as a language needs its own treatment of these ideas?</strong></em></p><p><strong>Sam Keen:</strong> I&#8217;ve been in development for quite some time&#8212;and in Python for a big portion of that. As for the topic, clean architecture has been around; Uncle Bob&#8217;s book has been out for quite some time. I&#8217;d always seen it discussed in the context of static languages like Java and C#. In a lot of Python communities, the thinking was that we didn&#8217;t quite need that&#8212;that it seemed overburdensome. So I wanted to take an approach and see: can we take from clean architecture the aspects that help us maintain larger Python codebases&#8212;without trying to turn it into Java? I knew there would be pushback, so I definitely wanted to keep it Pythonic.</p><p>As for why write a book&#8212;this is my first published book. During the COVID years, I had a lot of time and got into doing YouTube tutorials for game development. I really liked that content-creation process, and when the opportunity came up to write this book, I thought, yeah, that&#8217;s the next step. I&#8217;ve always wanted to write a book, so those motivations aligned with good timing.</p><p><em><strong>2: In your book, how do you explain clean architecture in Pythonic terms&#8212;especially to developers who know the classic concepts but struggle to apply them cleanly in real-world Python projects?</strong></em></p><p><strong>Sam Keen:</strong> Kind of returning to the previous answer: clean architecture aligns well with the Pythonic ethos&#8212;one of Python&#8217;s core features is to be explicit rather than implicit. Clean architecture gives you that map for your application. If you&#8217;re doing object-oriented development, the first things you&#8217;re concerned with are the classes&#8212;how to design those classes. I&#8217;m sure we&#8217;ll talk about SOLID. You use those SOLID principles on the class itself to ensure it&#8217;s cohesive. Clean architecture takes that and expands on it&#8212;how do we apply some of these same principles to the entire application as a whole? The original &#8220;clean architecture&#8221; was explicit that it&#8217;s not a framework and not a rigid set of rules; it&#8217;s a set of principles to follow and adapt to your needs. There isn&#8217;t one playbook for clean&#8212;it depends on what you&#8217;re building. You don&#8217;t want to build a skyscraper if it&#8217;s a little cottage codebase.</p><p><em><strong>3: SOLID principles are often cited as foundational to clean architecture. According to you, which of them are easiest and which of them are the hardest to apply in a dynamic language like Python?</strong></em></p><p><strong>Sam Keen:</strong> I think it&#8217;s kind of the usual suspects. With SOLID, the S&#8212;the <strong>single responsibility principle</strong>&#8212;a lot of folks comprehend pretty well. It&#8217;s the first letter, and people understand that a class should have a single concern, that sort of thing. That translates well into Python. The compiler isn&#8217;t going to help you much in that regard&#8212;it&#8217;s more of a human design decision.</p><p>It gets more into the nuance with the I&#8212;the <strong>interface segregation principle</strong>&#8212;having well-structured and focused interfaces. That&#8217;s where it gets tricky. For instance, you might have a vehicle class. You wouldn&#8217;t want to have the engine be part of that vehicle base class, because you may have gasoline cars but also electric cars. If you couple the concept of an engine directly into the vehicle base class, you&#8217;ll end up with a class that has a power level and a fuel level in liters. You&#8217;ll have parts of that interface that don&#8217;t make sense for all of the concrete classes that inherit from it.</p><p>Another one is the L&#8212;the <strong>Liskov substitution principle</strong>. This is about ensuring that if you have a base class and then classes implementing it, none of those subclasses disrupt the contract of the base class. Anywhere in your code where you&#8217;re referring to the base class, you should be able to insert one of the child classes and have it function fine. That&#8217;s something a compiler in a static language would help with. In Python, you don&#8217;t have that, so type hinting&#8212;and I&#8217;m sure we&#8217;ll talk more about this since it&#8217;s core to the book&#8212;paired with mypy gives you a little bit of that compiler-like type checking. That can be very helpful in Python.</p><p>And then of course, unit testing is always good. So the L and the I are the tougher ones, while S is the easiest.</p><p><em><strong>4: How do you enforce the dependency rule in your projects so that business logic stays free from frameworks, ORMs, or infrastructure code?</strong></em></p><p><strong>Sam Keen:</strong> The dependency rule is really core to clean architecture. If you get that one thing right, you&#8217;re doing quite well. Conceptually, clean architecture is built in layers. You have the <strong>inner domain layer</strong>&#8212;core business objects with very few dependencies. Then you move out to the <strong>application layer</strong>, which orchestrates workflows and manages those entities&#8212;for example, &#8220;save task&#8221; or &#8220;complete task.&#8221;</p><p>Then there&#8217;s the <strong>interfaces layer</strong>. It should be a thin layer, often with controllers that translate between the outer layer and the inner business core&#8212;the application and domain layers. Finally, the <strong>outer layer</strong> is your frameworks and drivers. That&#8217;s where all the volatility is&#8212;things like SQLAlchemy and external dependencies that you don&#8217;t control.</p><p>Back to your question: how do you enforce the dependency rule? The principle is that <strong>dependencies all face inward</strong>. The domain layer shouldn&#8217;t be aware of anything above it. The application layer shouldn&#8217;t be aware of anything above it. They should only depend on what&#8217;s inside.</p><p>In Python, just like in other languages, you can check this. One way is pragmatic&#8212;make sure the team understands the principle and why it matters. Another way is structural&#8212;use a folder structure: a folder for domain objects, a folder for application objects, and so on. That way, each file is bound by the rules of its layer.</p><p>You can also get precise with automation. For example, in the book we give a simple fitness function test. It runs linting across import statements and checks them against the known directory structure of the layers. If it finds that a use case in the application layer is importing from, say, a driver, it fails the build. That shifts knowledge of violations left, so developers can correct them quickly.</p><p>So, it&#8217;s a hierarchy: first ensure the team understands the principle, then reinforce it with clear folder structure, and finally back it up with tests that assert violations.</p><div><hr></div><p><em><strong>5: What kind of project structure do you recommend? Do you prefer separation by layer&#8212;domain, interfaces, etc.&#8212;or by feature? And how do you keep the layout from becoming overly rigid?</strong></em></p><p><strong>Sam Keen:</strong> One example we mentioned earlier is having a folder per layer&#8212;that&#8217;s definitely a possibility. But to step back, the bigger principle is: always have the simplest solution that meets the needs of your project and your team. You don&#8217;t want to overbuild.</p><p>For example, you might have a very simple CRUD application, essentially an API fronting a database with create, update, delete functionality. There&#8217;s not much to it. In that case, you could build it in more of a feature structure&#8212;say, a task microservice&#8212;and just go with FastAPI in a one- or two-file implementation. That makes sense because there isn&#8217;t much domain logic in that particular service. I see a lot of single-file frameworks that work quite well for these small cases. That&#8217;s one end of the spectrum.</p><p>On the other end, as projects and codebases grow larger, with more complex business rules, you start to shift toward the domain structure we talked about earlier&#8212;a folder per layer. That helps put in a structure where the right thing to do is also the easiest. For example, if your team decides to support multiple users instead of just one, you now need a User domain object. With a folder-per-layer design, the map is already there: the business object goes in the domain folder, the orchestration goes in the application folder, and so on.</p><p>It&#8217;s not one-size-fits-all. It&#8217;s something you can evolve into. And when your team makes an intentional decision to bend a common practice&#8212;for pragmatic reasons&#8212;document it in an ADR, an architectural decision record. That way it&#8217;s explicit and intentional, not accidental complexity. It also helps developers who come later&#8212;or even yourself six months down the road&#8212;understand why that choice was made.</p><p><em><strong>6: Domain-driven design is a big part of your approach. How do you model things like entities and value objects in idiomatic Python?</strong></em></p><p><strong>Sam Keen:</strong> Something very popular in modern Python is the use of <strong>dataclasses</strong>. They&#8217;re a great way to model domain objects because they eliminate boilerplate and make classes very easy to comprehend&#8212;you see just the attributes and functions.</p><p>For entities, which have an identity, I use a thin base entity class that every entity extends. A Task, a User, any entity extends this base entity class. That gives you a primary ID field&#8212;a reserved field for all entities. Anything in the system that knows it&#8217;s dealing with an entity knows that ID field is there and that it&#8217;s universally unique.</p><p>In Python specifically, in that entity class you&#8217;d also implement the <code>__hash__</code> and <code>__eq__</code> methods. That keeps you in line with the concept of an entity having identity. You can change all the attributes of that class, but it will always represent the same person or the same task&#8212;it&#8217;s just that its attributes have changed.</p><p>A value object is different. It doesn&#8217;t have an ID field; it&#8217;s defined solely by its properties. Again, a dataclass works well, but here you&#8217;d set <code>frozen=True</code> to make it immutable. For example, an exchange rate could be modeled this way. If any of its attributes change, it&#8217;s no longer the same object. By making it immutable, you guarantee that once an exchange rate object is created, its values won&#8217;t change, which fits the definition of a value object.</p><p>So, as a Python developer, the tools for following domain-driven design are built into the language. It aligns well with the principles of clean architecture.</p><p><em><strong>7: Frameworks like FastAPI or Django can easily creep into core logic. How do you recommend engineers keep frameworks out of the domain and use case layers?</strong></em></p><p><strong>Sam Keen:</strong> Again, it builds on what we talked about&#8212;deciding on the directory structure that makes sense for your project. You want to make the right thing to do the easy thing. A clear structure gives you context: when you look at a class in the application layer, it should be obvious if it&#8217;s behaving abnormally by depending on a framework.</p><p>Beyond structure, it comes down to applying principles and patterns. Take databases, for example. SQLAlchemy is a framework. You shouldn&#8217;t see any reference to it in the domain or application layers. The anti-pattern would be a User class in the domain layer with SQLAlchemy methods directly on it to save to the database&#8212;that&#8217;s direct coupling.</p><p>Instead, you use the repository pattern. You define an interface with simple methods like <code>save_user</code>, <code>get_user</code>, <code>delete_user</code>. Your User object depends only on that contract. Then, in the frameworks layer, you implement that repository using SQLAlchemy or whatever tool you need.</p><p>That way, the domain isn&#8217;t directly coupled to the framework&#8212;both the domain object and the implementation simply agree to the interface. This is the general pattern across the board: keep frameworks out of your core logic by making them details implemented at the outer layers.</p><p><em><strong>8: Do you use Pydantic or dataclasses in your domain models, or do you restrict them to boundaries? How do you handle input validation and transformation cleanly?</strong></em></p><p><strong>Sam Keen:</strong> That&#8217;s an interesting one. When I was writing the book, I actually drifted into being too strict with clean architecture&#8212;treating it almost like a framework. In some early drafts, I found myself duplicating property validation in two places: once in the interface layer and again in the domain layer, but using different mechanisms. Anytime you&#8217;re duplicating validation, that&#8217;s a red flag.</p><p>The framework I was using was <strong>Pydantic</strong>, which is very popular in Python. I use it extensively. It has strong validation and serialization methods. In practice, I made a calculated choice: in some applications, I would allow Pydantic into the domain layer. That&#8217;s because it&#8217;s mainstream, well supported, and it reduced a lot of boilerplate and duplicated code in that specific case.</p><p>That&#8217;s the bigger point&#8212;clean architecture is a set of principles, not a rigid framework. Sometimes you&#8217;ll make compromises to reduce complexity, and that&#8217;s OK. The important thing is to be transparent about it. Document the decision in an ADR&#8212;an architectural decision record&#8212;so it&#8217;s clear to the team and future developers that it was a conscious, intentional choice. That way, you&#8217;ve managed the trade-off explicitly rather than letting accidental complexity creep in.</p><p><em><strong>9: What&#8217;s your testing strategy for a clean architecture codebase? You mentioned unit testing and things like that a bit earlier, but where do unit, integration, and end-to-end tests fit within this concept?</strong></em></p><p><strong>Sam Keen:</strong> A very common approach to testing is the test pyramid&#8212;Martin Fowler and others have popularized this. You want the base of that to be unit tests, which just test individual functions. They&#8217;re very quick&#8212;testing the behavior of a class on its own&#8212;so you want a large number of those. Above that are integration tests, where classes work together; in clean architecture, that often means classes working across layers. At the very top are end-to-end tests, where you actually boot up infrastructure and test as a real user against the application. Those are slow and often brittle because they test interfaces that change quite a bit, so maintaining many of them is a burden.</p><p>If you have a tightly coupled application, it&#8217;s hard to implement that pyramid&#8212;you can end up with an &#8220;ice cream cone,&#8221; because unit tests are hard to write and you brute-force a lot of end-to-end tests. Clean architecture enables you to have the true pyramid. The domain layer has no dependencies, so you can easily write unit tests without starting a database or worrying about network calls. Up into the application layer, because you&#8217;ve used dependency inversion and coded use cases against interfaces rather than concrete classes, a test can insert a mock that implements the interface. That keeps those tests very quick as well. You still need end-to-end tests for critical workflows as final validation, but the confidence comes from a plethora of fast unit tests and some integration tests. Clean really enables that true test pyramid.</p><p><em><strong>10: What&#8217;s your approach to refactoring legacy Python code? How do you introduce clean architecture there without overhauling everything at once?</strong></em></p><p><strong>Sam Keen:</strong> We have a chapter devoted to this, and much of it is common practice regardless of language. What you don&#8217;t want to do is a Big Bang release&#8212;rewriting the entire stack and trying to release it all at once hardly ever works. It takes too long, requirements change, and the system you&#8217;re rebuilding keeps changing. You have to figure out how to slice up the problem. The <strong>&#8220;strangler fig&#8221;</strong> is a common metaphor: it&#8217;s a fig vine that grows up and engulfs a tree.</p><p>Clean helps because you&#8217;re going from a system without a map&#8212;something chaotic&#8212;to one that has a map and discrete components. That lets you take Service A and split it off into a clean architecture with full test coverage. Where to start: begin at the lower layers. Look at your legacy system and find all the parts that in aggregate build the concept of a user. Extract that domain logic and build your true domain User in clean architecture. Then, using gateways and feature flags, parallelize traffic to the old system and the new system, compare, and ensure parity in state changes for the user across both.</p><p>Beyond domain objects, look for natural bounded contexts&#8212;returning to domain-driven design&#8212;and extract them into domains with their use cases. Do everything you can to avoid a Big Bang release. Clean architecture gives you the guidance to restructure incrementally and release with confidence.</p><p><em><strong>11: How is AI changing the way we approach clean architecture?</strong></em></p><p><strong>Sam Keen:</strong> AI is the definition of disruptive. This generative AI wave we&#8217;ve come in on is really interesting because, again, we started the book a little over a year ago and, in AI years, that&#8217;s forever ago. I think baby ChatGPT-3 was coming out or something, and LLMs were just starting to be able to build Snake&#8212;that was the extent of what they could build. And then you look at where we are now.</p><p>There are a couple of dimensions. If you&#8217;re thinking of AI as a feature you would add to an application&#8212;integrating an LLM into an application&#8212;nothing really changes. That&#8217;s a driver&#8212;a framework driver. So the knowledge of, say, <strong>LangChain</strong> or <strong>LlamaIndex</strong>&#8212;really common frameworks&#8212;that&#8217;s all going to stay in the outer layers. Same playbook, and then you integrate that down to your pure domain objects. So that part doesn&#8217;t change.</p><p>The other part is using AI to build with&#8212;coding tools and these sorts of things. It helps. We talked about writing tests&#8212;AIs are great at writing unit tests, so you can definitely leverage it there. That&#8217;s where you mitigate hallucination&#8212;you&#8217;re writing tests to validate, so you can know the AI is doing the right thing. Another example: we have that User that needs to be saved to a database, and it has an interface contract. You can use AI to build the concrete class against that interface&#8212;at least get a start on it. Some might think of that as boilerplate, but it can do that.</p><p>Overall, the advantage is this idea of &#8220;context engineering.&#8221; The AI&#8217;s ability to help is only as good as the context you give it. If you let it index legacy, tightly coupled systems, it&#8217;s not really sure what the plan was&#8212;there kind of wasn&#8217;t one&#8212;so the AI will continue to build against that codebase without much of a plan. Whereas, if you&#8217;re explicit about your approach and you have these four folders with easily defined rationale of what goes into each folder, all that context goes to the LLM&#8212;since it&#8217;s helping a human. Using clean architecture and having that playbook for how to build out your application helps AIs do the right thing and not go off the rails. It&#8217;s exciting.</p><p><em><strong>12: How do you apply clean architecture across multiple services or modules? Should each follow the full layering pattern or do you recommend something else?</strong></em></p><p><strong>Sam Keen:</strong> We touched on this a little bit. It&#8217;s not one pattern to fit everything. With multiple services, it&#8217;s the same idea. The purpose&#8212;under the context that what you&#8217;re building is multiple services&#8212;matters. You may have portions that are really straightforward CRUD applications. You just know that you don&#8217;t want direct database access, so you put an API in front of that. Maybe, at this point in time, it&#8217;s kind of one-to-one mapping&#8212;you have it for defense if you need to change it in the future. In those cases, FastAPI and using Pydantic throughout might be the right mechanism.</p><p>But your larger, business-rule-centric, orchestrating parts of the application&#8212;those are where you invest in a fully layered approach. And even though you&#8217;ve built services, you treat them as framework drivers with respect to one another. You have Service A you&#8217;ve built; you have Service B you&#8217;ve built. Service B treats Service A as a detail and doesn&#8217;t let Service A&#8217;s implementation details get pulled into the deeper layers of Service B.</p><p><em><strong>13: When building scalable applications, how do you decide what belongs in the core versus what stays at the edge&#8212;for example, pagination, caching, or authentication?</strong></em></p><p>Sam Keen: Yeah, so that&#8212;again, there&#8217;s a little bit of thought process to that, of course. You kind of get into that domain-driven design mentality of what&#8217;s core to the domain. So a user&#8212;what&#8217;s core to a user&#8212;versus, like, transport protocols and these sorts of things. Those are just details&#8212;computer concepts.</p><p>A tricky kind of heuristic is: what makes sense even if you weren&#8217;t a computer program? To explain that&#8212;like, a user having a schedule is a concept that makes sense even before computers were invented. But the knowledge that we&#8217;re transferring using JSON or gRPC&#8212;that&#8217;s a computer concept; it&#8217;s a detail of the platform we&#8217;re implementing these user domain objects on. To be facetious, if we switch to quantum computers in ten years, we&#8217;ll bring our domain objects with us, but all those other details are going to change.</p><p>So that&#8217;s the macroscopic way to think about it&#8212;what are the nouns, the objects of our system&#8212;versus what&#8217;s a transport or technology detail that&#8217;s not core and should stay at the edge. There is some nuance to authentication. You may have a system that needs to be impenetrable, so every layer may need to validate the authentication&#8212;like a zero-trust approach. But you may not be in that case, so you may stop authentication at, say, the adapters layer. Then everything below either assumes authentication or doesn&#8217;t have knowledge of it. That&#8217;s an example where that computer concept could come all the way down to the domain out of need.</p><p><em><strong>14: How do you manage inter-service communication in Python systems built with clean architecture?</strong></em></p><p><strong>Sam Keen:</strong> That&#8217;s&#8212;again, that&#8217;s a common practice regardless, but clean helps you with it. In event-driven architecture, messages are another way you can leak implementation details. Be very cognizant of the information you put in the message. It should be past tense&#8212;&#8220;this happened&#8221;&#8212;those sorts of practices.</p><p>Be cognizant that it&#8217;s a common way for implementation details to get transmitted across the wire, versus, you know, a leaky interface at the code layer. Even at the transport level, you can leak implementation details that you don&#8217;t want to, and that will cause coupling as well.</p><div><hr></div><p>To learn Clean Architecture through a series of real-world, code-centric examples and exercises, optimize system componentization, and significantly reduce maintenance burden and overall complexity, check out <em><strong><a href="https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886">Clean Architecture with Python</a></strong></em> by <a href="https://open.substack.com/users/11641009-sam-keen?utm_source=mentions">Sam Keen</a>. The book helps you apply Clean Architecture concepts confidently to new Python projects and legacy code refactoring.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!NtED!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!NtED!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775" width="268" height="330.5824175824176" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:268,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Clean Architecture with Python&quot;,&quot;title&quot;:&quot;Clean Architecture with Python&quot;,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-us/product/clean-architecture-with-python-9781836642886&quot;,&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="Clean Architecture with Python" title="Clean Architecture with Python" srcset="https://substackcdn.com/image/fetch/$s_!NtED!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!NtED!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!NtED!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7059d45c-81bb-4525-8a53-28d479a5593b_2250x2775 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>]]></content:encoded></item><item><title><![CDATA[Inside Go Systems Programming: A Conversation with Mihalis Tsoukalos]]></title><description><![CDATA[Concurrency patterns, runtime optimizations, and memory management in modern Go]]></description><link>https://deepengineering.net/p/inside-go-systems-programming-a-conversation</link><guid isPermaLink="false">https://deepengineering.net/p/inside-go-systems-programming-a-conversation</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Wed, 20 Aug 2025 09:39:38 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/S6MJKJoqiQU" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>From goroutine scheduling quirks to profile-guided optimizations, Go has grown into a language that balances simplicity with systems-level power. In this conversation, we speak with Mihalis Tsoukalos&#8212;author of <em><strong><a href="https://www.packtpub.com/en-ch/product/mastering-go-9781805122647">Mastering Go</a></strong>, Fourth Edition</em> (Packt, 2025)&#8212;about what it takes to write high-performance, maintainable Go in today&#8217;s evolving ecosystem.</p><p>Mihalis is a Unix systems engineer and prolific technical author whose books <em>Go Systems Programming</em> and <em>Mastering Go</em> have become staples for developers working close to the metal with Go and Linux. He holds a BSc in Mathematics from the University of Patras and an MSc in IT from University College London, and his work has appeared in <em>Linux Journal</em>, <em>USENIX ;login:</em>, and <em>C/C++ Users Journal</em>. His expertise spans systems programming, time series data, and databases, but his reputation in the Go community comes from distilling that low-level experience into accessible, practical guidance.</p><p>In this interview, we explore what motivated the fourth edition of <em>Mastering Go</em> and the audiences it serves, the realities of structuring goroutines and channels correctly, and the concurrency patterns that actually hold up under production workloads. We also dive into Go&#8217;s runtime improvements, profiling and memory-management workflows, and the maturing role of generics in real-world projects. Beyond language features, Mihalis shares his perspective on observability, the expanding standard library, and how Go compares with Rust and Zig for systems programming. Looking ahead, he offers a candid view of where Go is headed&#8212;from concurrency safety to ecosystem maturity&#8212;without losing sight of its defining trait: clarity without unnecessary complexity.</p><p>You can watch the full conversation below&#8212;or read on for the complete transcript.</p><div id="youtube2-S6MJKJoqiQU" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;S6MJKJoqiQU&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/S6MJKJoqiQU?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><div><hr></div><p><em><strong>1: What motivated you to write the 4th edition of Mastering Go? Who should pick it up, and what kind of projects will the book help them with?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> First of all, I want to start with a disclaimer&#8212;nothing, no book or any other resource, can replace experience. You have to try things. That&#8217;s the general idea, and that&#8217;s why I wrote the book&#8212;to make you try.</p><p>The main reason for the 4th edition of <em>Mastering Go</em> was the continued growth and evolution of Go. Since the last edition, the language has seen major changes. The most important was the addition of generics in Go 1.18, a long-awaited feature that really shifted how developers think about type safety and code reuse. Alongside that, we&#8217;ve had improvements to modules, better WebAssembly support, and lots of enhancements across the standard library and toolchain, including faster testing for the testing process. So it made perfect sense to update the book to reflect where Go is today.</p><p>Another big motivator was feedback from the Go community. <em>Mastering Go</em> has always aimed to be a practical, hands-on guide, and readers kept asking for more real-world examples&#8212;especially about things like concurrency, networking, and systems-level programming. This edition builds on that by going deeper into those topics and refining the guidance on writing idiomatic, maintainable Go code. Again, I have to say it: nothing can replace experience. You have to try things all the time. That&#8217;s the point of learning something new.</p><p>The book is best suited for intermediate to advanced developers&#8212;people who already understand the basics of programming and want to work with Go and take their skills further. It is particularly useful for engineers working on backend systems, command-line infrastructure tools, high-performance network applications, or cloud-native services. It&#8217;s also a solid resource for developers coming from languages like C, C++, Java, or Python who want to build scalable, efficient systems.</p><p>For example, it can be applied in projects like migrating a payments platform from Node.js to Go to reduce latency in transaction processing and better handle traffic, writing a custom reverse proxy in Go to maximize performance and manage connection concurrency efficiently, or an observability team building high-throughput log collectors or trace aggregators that must process and forward millions of events per second.</p><p>So overall, this edition is not a minor update&#8212;it&#8217;s a reflection of how far Go has come as a language and how central it is becoming in areas like cloud computing, DevOps, and systems engineering. It is really for anyone serious about mastering Go and using it to build high-performance, real-world applications.</p><p><em><strong>2: Today, Go&#8217;s concurrency model remains a major draw, with Go 1.22 fixing the long-standing loop variable capture issue. How do you now recommend structuring goroutines and channels to avoid common bugs?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> The concurrency model of Go has always been one of its strongest features because it&#8217;s simple, easy to understand, yet powerful. You can&#8217;t have everything, but what Go offers is pretty much what programmers want. Goroutines are lightweight, channels give you a clear way to coordinate between them, and it is generally easy to express concurrent logic in a readable way.</p><p>But you can always go wrong, especially when working on more complex or high-performance systems. One issue that has tripped up a lot of developers over the years was the loop variable capture problem. If you launched goroutines inside a loop, you could accidentally end up capturing the loop variable in a closure, which meant that all your goroutines might reference the same variable&#8212;not what you intended. Usually, you wanted each goroutine to take a different variable value. The typical workaround was to reassign the variable inside the loop, but it was error-prone. This was finally fixed in Go 1.22: now the language creates a new instance of the loop variable on each iteration, so closures and goroutines get the correct value automatically. It&#8217;s a small change in behavior, but it eliminates a very common class of bugs and makes concurrent code cleaner and more predictable.</p><p>That said, even with this fix in place, you still need to be cautious when writing concurrent code. A few best practices:</p><ul><li><p>Always be explicit about goroutine ownership and lifecycle. The best way to do that is by using <code>context.Context</code> to manage cancellation and timeouts. This ensures goroutines don&#8217;t hang around longer than they should, avoiding memory leaks and unpredictable behavior.</p></li><li><p>Limit concurrency when needed. Just because goroutines are lightweight doesn&#8217;t mean you should spin up thousands of them without thinking. If you&#8217;re processing a large number of tasks or I/O operations, use worker pools, semaphores, or bounded channels to keep things under control.</p></li><li><p>Avoid unbuffered channels for high-volume communication. They&#8217;re great for synchronization, but if you&#8217;re passing a lot of data around, buffered channels reduce blocking and improve performance.</p></li><li><p>Always close channels properly. Only the sender should close the channel, and only once. Closing channels from multiple places or from the receiver side can cause panics or race conditions.</p></li><li><p>Use the <code>select</code> statement defensively, especially when working with multiple channels. A default case can help you avoid blocking in situations where responsiveness matters, like event loops or fault-tolerant systems.</p></li><li><p>Don&#8217;t force everything through channels. Although they look practical at first, sometimes mutexes or atomic operations are a better fit. Think carefully before you start writing code and designing your program.</p></li></ul><p>So overall, Go 1.22 makes life easier for concurrent programming, but writing robust concurrent code still requires discipline, clear design, and a good understanding of how goroutines and channels behave under the hood. That&#8217;s what really helps you build systems that are both maintainable and production-ready. Again&#8212;think before you start writing code, and don&#8217;t just throw in goroutines because they&#8217;re lightweight.</p><p><em><strong>3: When you think about concurrency at a systems level, which patterns do you find most effective for real-world workloads? Are there particular idioms you keep returning to, like worker pools or pipelines?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> At the systems level, concurrency is not just a feature&#8212;it&#8217;s a design principle. It influences how your software scales, how efficiently it uses resources, and how it behaves under pressure. Go gives you the primitives&#8212;goroutines and channels&#8212;but using them well requires a solid set of patterns you can rely on.</p><p>The first is worker pools. They are probably the most universally effective pattern. The Apache web server used to do this with threads. Instead of spawning a new goroutine for every task, you maintain a fixed set of workers that pull from a task queue. This gives you controlled concurrency&#8212;you&#8217;re not overloading the system with thousands of goroutines, and you stay within limits like memory, file descriptors, or database connections. This makes system behavior under load much more predictable because you know exactly what resources you&#8217;re using. For example, on a project I worked on, we used worker pools in a log processing service that handled thousands of files per hour without any issues.</p><p>The second pattern is pipelines. These are great when you want to break a task into stages and process each stage concurrently. Each stage runs in its own goroutine and passes data to the next using a channel chain. It&#8217;s a clean way to handle streaming data transformations or multi-step processing. It encourages modularity and makes it easier to deal with backpressure and separation of concerns.</p><p>Another critical piece is <code>context.Context</code>, which I consider non-negotiable in any serious concurrent Go application. It&#8217;s the standard way to manage timeouts, cancellations, and deadlines across goroutines. If you&#8217;re handling HTTP requests, running background jobs, or coordinating distributed tasks, <code>context</code> helps you shut things down cleanly and avoid goroutine leaks. This is especially important when interacting with external systems like databases or APIs, where you don&#8217;t want calls hanging indefinitely. For example, if you&#8217;re writing a TCP server and connections are not closed properly, you might run out of ports to serve new requests.</p><p>Another pattern I use is fan-out/fan-in. Fan-out means launching multiple goroutines to handle parts of a job in parallel, and fan-in means collecting the results into a single place. Combined with worker pools, this is a powerful way to parallelize work and aggregate results efficiently. I used fan-out/fan-in for a monitoring aggregator service with many microservices for health and metrics data&#8212;some over HTTP&#8212;and then collected the results into a single response.</p><p>I also rely heavily on <code>select</code> statements. Being able to multiplex across multiple channels or listen for a cancellation signal or timeout is incredibly powerful. It helps you write responsive systems that can recover from delays, retry on failure, or time out gracefully.</p><p>One principle I&#8217;ve learned over time: don&#8217;t reach for channels by default. A mutex or an atomic operation might be more appropriate, creating a simpler, cleaner, and less error-prone design.</p><p>Finally, goroutine supervision is critical. You need to track what your goroutines are doing, make sure they shut down cleanly, and prevent them from sitting idle in the background.</p><p>To sum up: the patterns I find most effective are worker pools, pipelines, the context package, fan-out/fan-in, and <code>select</code> statements. These help you create reliable, maintainable concurrent Go code.</p><p><em><strong>4: Let&#8217;s talk about Go&#8217;s runtime performance, which has improved noticeably. Tail latencies are down and the garbage collector is smarter. What profiling techniques do you recommend to help teams actually realize these gains?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> That&#8217;s a good question, because sometimes you have issues and you don&#8217;t know what&#8217;s going on behind the scenes. Go has made real progress on runtime performance. The garbage collector in particular has seen big gains in terms of pause times and CPU usage. The garbage collector runs as a goroutine&#8212;everything in Go is a goroutine, including the garbage collector. The special thing about it is that sometimes, for the garbage collector to operate, everything else must freeze briefly, because you can&#8217;t create new variables while the collector is cleaning up.</p><p>Tail latencies have also come down, which makes Go a strong option for performance-sensitive systems like APIs, proxies, and backend infrastructure. But those gains don&#8217;t happen automatically&#8212;you need to profile and measure to benefit from them. Optimization without measurement is guesswork.</p><p>Go provides excellent tools for profiling. With the right approach, those tools can lead to real improvements. A practical point: don&#8217;t wait until you have a problem to learn about optimization and measurement. Experiment ahead of time so that when a problem arises, you&#8217;re ready to use the tools. Also, be very careful when using them on production systems&#8212;you might crash them. Don&#8217;t run measurements during peak hours unless it&#8217;s absolutely necessary.</p><p>The first tool I recommend is <strong>Pprof</strong>, which is built in and very powerful. The <code>net/http/pprof</code> package exposes several types of profiles&#8212;CPU, heap, goroutines, blocking operations, mutex contention&#8212;and you can access them through an HTTP endpoint in your web browser. Then you can visualize them using <code>go tool pprof</code> or one of the newer web interfaces.</p><p>I usually start with CPU profiles. Run them under realistic load and see where your code is spending time&#8212;it&#8217;s often not where you expect. Heap profiles are equally important, especially now that the garbage collector is more efficient. If you can cut down unnecessary allocations, the collector has less to clean, and your application runs more smoothly.</p><p>One mistake teams make is profiling only with benchmarks or local tests. You need to profile under real workloads in production or in a staging environment that closely mimics production. Many teams now include Pprof endpoints in production, behind secure admin-only routes, so they can safely collect data without affecting users.</p><p>For deeper insight, I recommend <strong>runtime tracing</strong>. The <code>runtime/trace</code> package provides a timeline of goroutine scheduling, system calls, garbage collection, and other events. Paired with Pprof, it helps explain why a goroutine was delayed or what caused a latency spike. You can collect traces with <code>go test -trace</code> or via code, and then explore them with <code>go tool trace</code>.</p><p>If you&#8217;re doing micro-optimizations, the <strong>Go benchmarking framework</strong> is excellent. Metrics like allocations per operation, bytes per operation, or nanoseconds per operation help you track how small changes affect performance, especially in tight loops or hot paths like serialization or hashing. Even one extra allocation can have a big impact under heavy load, so it&#8217;s worth running <code>go test -bench</code> regularly if you&#8217;re tuning critical functions.</p><p>It&#8217;s also important to watch for goroutine leaks or contention. Use goroutine and block profiles to track how many goroutines are running and whether they&#8217;re getting stuck. If the goroutine count keeps rising, that&#8217;s often a sign of a leak or unexpected blocking.</p><p>Beyond profiling, observability matters. The best-performing teams invest in continuous metrics and dashboards. Tools like Prometheus, combined with Go&#8217;s ability to export metrics, let you track garbage collection pause times, allocation rates, goroutine counts, and more. With alerting, you can catch issues before they impact users or your boss&#8212;which is never a good surprise.</p><p>A concrete case: I once worked on a high-throughput telemetry pipeline. The team was seeing unusually high CPU usage during peak hours, even though the runtime looked idle. The issue turned out to be repeated use of <code>json.Marshal</code> inside a loop, which was allocating and copying far more data than necessary. Replacing it with a streaming encoder solved the problem and made everything much faster.</p><p>So in short, Go&#8217;s runtime has improved, but to realize those gains you must measure continuously, profile under real workloads, and act on what you find.</p><p><em><strong>5: Profile-guided optimization became stable in Go 1.21. Where does PGO make a real difference, and when might it not be worth the effort?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> The stabilization of profile-guided optimization (PGO) in Go 1.21 was a big milestone for performance-focused developers. Go has traditionally emphasized implicit, fast compiler optimizations, but PGO changes that. It gives us a new way to fine-tune performance based on how our code actually runs in production.</p><p>In simple terms, PGO lets the compiler make smarter decisions using real-world runtime data&#8212;things like which functions are called most often, which branches get taken, and where the hot paths are. With that information, the compiler can reorder functions to improve caching, inline code more intelligently, and reduce indirect calls. The result is lower CPU usage and better latency, especially in high-throughput or tight-loop scenarios.</p><p>So where does PGO shine? It&#8217;s great for performance-critical systems with stable workloads&#8212;things like low-latency services, backend infrastructure, proxies, or message brokers. In these environments, even small improvements in CPU can translate into real wins. It also makes a difference in hot-path code: tight loops that run millions of times, or CPU-bound routines like encoders, parsers, or math-heavy computations. PGO helps optimize layout and branching in those areas, reducing stalls and improving instruction-cache behavior.</p><p>If you&#8217;re running large-scale or long-lived services, even small gains add up&#8212;a 5% CPU saving across hundreds of instances is significant.</p><p>That said, PGO isn&#8217;t always worth the effort. For applications with unpredictable or highly variable workloads, the profile you generate today might not reflect tomorrow&#8217;s behavior. It&#8217;s also not ideal for short-lived command-line tools or scripts. And if your codebase is still changing rapidly, PGO is premature. Finish stabilizing your application first, then consider it.</p><p>In general, PGO is a powerful tool, but like any optimization technique, it&#8217;s most effective when used deliberately. If you&#8217;ve already profiled your application, you know where the bottlenecks are, and you want to squeeze out more performance without rewriting code, then PGO is a great next step. But it won&#8217;t solve every problem. My advice is to experiment with it on your own time so you&#8217;re ready to use it when it&#8217;s truly needed.</p><p><em><strong>6: Memory is always a tricky area. What is your typical workflow for diagnosing memory leaks or reducing high allocation rates in Go systems? Do you have any favorite tools or patterns you like using?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> Although modern computers have plenty of memory, we still need to watch for leaks and excessive allocations. When I&#8217;m diagnosing memory issues in Go&#8212;whether a potential leak or just high allocation pressure&#8212;the first step is to establish a baseline. That means running the service under real or representative load and collecting memory data that reflects actual behavior, not just synthetic benchmarks.</p><p>From there, I rely heavily on Go&#8217;s built-in tooling, especially Pprof. I usually instrument the service with an HTTP endpoint using <code>net/http/pprof</code>, then capture heap profiles at different points&#8212;typically one right after startup and another after the service has been running under load. Comparing these snapshots helps answer key questions: Are allocations growing continuously? Which types are taking the most memory? Is the garbage collector doing more work than expected?</p><p>I load these profiles into <code>go tool pprof</code> or use the web interface, focusing on views like &#8220;in-use space&#8221; or &#8220;in-use objects.&#8221; If I see unexpected memory growth, I look for object types that shouldn&#8217;t be long-lived but are still hanging around. I also use the <code>-alloc_space</code> and <code>-alloc_objects</code> views to see where allocations are happening most frequently. That helps distinguish between a true leak and simply too many short-lived allocations.</p><p>A common pattern I follow is taking delta comparisons between snapshots. If memory usage looks flat but allocation counts are high, that&#8217;s usually a sign of churn, not a leak. Tools like <code>go test -bench -benchmem</code> are useful here&#8212;they show allocation behavior in tight loops or hot paths and help validate changes quickly.</p><p>When reducing allocations, I start with escape analysis. Running <code>go build -gcflags=-m</code> tells you which variables are escaping to the heap and why. Small changes&#8212;like passing a pointer instead of a value, or reusing a buffer&#8212;can keep data on the stack and reduce garbage collector pressure. If I see repeated allocations of slices, maps, or temporary structs in performance-sensitive areas, I consider <code>sync.Pool</code>, preallocating, or reusing buffers carefully. Even avoiding repeated string concatenations in loops or unnecessary interface conversions can make a noticeable difference.</p><p>For long-running services, I also recommend taking full memory dumps periodically and tracking object retention over time. That helps catch leaks caused by forgotten references. Continuous monitoring with Prometheus and visualization in Grafana is also valuable&#8212;it makes unexpected trends easy to spot.</p><p>Ultimately, avoiding memory leaks comes down to habits: profile early, understand your allocation patterns, avoid global state, and monitor in production. It&#8217;s not just about saving memory&#8212;it&#8217;s about running a system that behaves predictably under load and doesn&#8217;t wake you up in the middle of the night.</p><p>One memorable case involved a team whose Go service gradually climbed in memory usage over several days, even under steady load. Garbage collection seemed fine, but comparing heap profiles revealed that a map of cached Protobuf messages was never shrinking. The problem was a custom cache with no eviction policy&#8212;it just kept growing. To make matters worse, the keys were strings derived from user input, so the cardinality was unbounded. The fix was introducing a bounded LRU cache with periodic cleanup. The key insight came from seeing that the live object count of a specific type kept rising across heap snapshots. Without those profiles, it would have been much harder to pinpoint and fix.</p><p><em><strong>7: Generics have been around for a couple of releases now. What patterns have you seen work well, and where do you think developers are overusing or misusing them?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> Now that generics have had time to mature over a few Go releases, we are starting to see clear patterns around where they shine and where they can go off the rails.</p><p>One of the most effective use cases has been writing reusable, type-safe data structures and algorithms. Things like generic slices, sets, maps, or utility functions&#8212;map, filter, reduce&#8212;have become much easier to implement in a way that&#8217;s both clean and performant. This has led to better library code, especially in packages dealing with collections, number crunching, or parsing. Libraries that used to rely on the empty interface and type assertions now benefit from compile-time safety with very little extra syntax. That&#8217;s a big improvement in terms of both correctness and readability.</p><p>Another area where generics work really well is domain-specific helper functions. For example, a pagination utility that works across different types of records, or a retry wrapper that can handle arbitrary operations. These kinds of generics eliminate boilerplate and keep APIs consistent without losing clarity. When used thoughtfully, they make code more declarative and reduce the need for duplicating logic across packages or modules.</p><p>That said, there have also been missteps. A common one is overgeneralization&#8212;creating overly abstract, flexible APIs just because the language allows it. Another is wrapping generic types in ways that obscure intent. Instead of simply using a slice of type <code>T</code>, some developers introduce unnecessary abstractions that add layers without real benefit, making the codebase harder to understand.</p><p>There&#8217;s also a tendency among some developers to import functional programming paradigms wholesale&#8212;monads, chaining combinators, deeply nested generic utilities. While elegant in languages designed for them, these patterns often clash with Go&#8217;s core philosophy of clarity, simplicity, and explicit flow of control. The result can be clever-looking code that&#8217;s hard to read and even harder to debug.</p><p>In short, generics are a powerful addition to Go, but like any powerful tool, they need to be used with purpose and restraint. Think before you reach for them, and prefer clear designs. The goal should always be code that is easy to understand and maintain.</p><p><em><strong>8: Go 1.23 adds iterator functions and generic type aliases. How do you see those changing how we write Go, especially in libraries?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> The addition of iterator functions and generic type aliases in Go 1.23 might look like a quiet update, but it&#8217;s actually a significant step forward in writing more expressive, reusable, and composable code&#8212;particularly in libraries. These features build on the foundation of generics and help capture common programming patterns more naturally, while still keeping Go&#8217;s strengths of simplicity and clarity.</p><p>Take iterator functions. Go has always relied on <code>for</code> loops and <code>range</code> for iteration, and that worked well. But now, with iterator functions, we can encapsulate iteration logic as values&#8212;functions that yield elements one at a time. That might sound like a small shift, but it opens up powerful patterns like lazy evaluation, functional-style pipelines, and composable data flows. You&#8217;re no longer stuck rewriting the same loop boilerplate; you can abstract iteration into helpers that are both type-safe and ergonomic.</p><p>Then there are generic type aliases, which reduce friction when using generic types across packages. Before, if you wanted to tailor a generic type like <code>map[K]V</code> or <code>Option[T]</code> to your domain, you often had to rewrap or reimplement it. That made things verbose and diluted the usefulness of generic libraries. Now, with type aliases that support generics, you can define concise, strongly typed shortcuts for common patterns. This improves readability and makes code easier to work with, without introducing runtime overhead.</p><p>I think these features will lead to more expressive APIs and more composable, domain-agnostic utility packages. We&#8217;ll likely see libraries offering richer iterator utilities&#8212;things like filter, map, and reduce&#8212;implemented in a way that feels native to Go.</p><p>That said, the real challenge for library authors will be balance&#8212;using these tools to enhance code, not overcomplicate it. If done well, these features could significantly modernize the Go ecosystem, especially in areas like data processing and systems-level programming, where reusable containers, iterators, and higher-order utilities really shine.</p><p><em><strong>9: Fuzzing is built into Go now. How have you seen teams make fuzz testing practical, and what are some tips to get value from fuzzing beyond just turning it on?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> Fuzz testing is a powerful technique for uncovering edge cases, subtle bugs, and even security issues&#8212;things that traditional unit tests often miss. Since Go 1.18 added fuzzing support directly into the <code>go test</code> tool, we&#8217;ve seen some teams begin experimenting with it. Again, it&#8217;s important to experiment first.</p><p>But as you said, just enabling fuzzing isn&#8217;t enough. To really benefit, teams need a focused, deliberate approach. The teams that get the most out of fuzz testing usually start by targeting critical code paths&#8212;places where the software processes complex or untrusted inputs. Think parsers, codecs, or deserialization logic. These are prime candidates because they&#8217;re hard to reason about and easy to break with unexpected input. And one important rule here: never trust user input. Writing fuzz tests for these areas helps surface bugs that could otherwise go unnoticed.</p><p>It&#8217;s also important to seed the fuzzer well, instead of letting it start with purely random inputs. Give it examples representative of real data&#8212;this helps the fuzzing engine explore the space more intelligently and find meaningful values faster.</p><p>Integration is key. The most effective teams make fuzz testing part of continuous integration. They run short fuzzing sessions locally during development for quick feedback, and then schedule longer runs overnight or during off-hours on CI servers. That way, fuzzing becomes a continuous part of testing, not just something you do once in a while.</p><p>Beyond just finding crashes, fuzz testing is excellent for hardening error handling. It ensures your code doesn&#8217;t panic, leak resources, or hang when it gets bad input. And when you combine fuzz tests with other tools like the race detector, you can catch data races that wouldn&#8217;t show up otherwise. That combination improves reliability across the board.</p><p>One more tip: keep your fuzz functions deterministic and free of side effects. Avoid calling external systems or relying on randomness inside the test itself. Deterministic behavior makes failures easier to reproduce and debug.</p><p>In short, fuzz testing is most valuable when used deliberately&#8212;targeting the right parts of your code, seeding it well, integrating it into workflows, and combining it with other tools. Done right, it&#8217;s not just about uncovering obscure crashes&#8212;it&#8217;s about building more robust, resilient Go systems.</p><p><em><strong>10: Observability is another area you cover in your book. What do you recommend for monitoring and tracing Go systems effectively, especially under high concurrency?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> Observability is absolutely essential when running Go systems at scale, especially in high-concurrency environments. It gives you the visibility to understand how your application behaves in production, diagnose issues quickly, and keep performance and reliability where they need to be.</p><p>For monitoring, the first step is always metrics&#8212;both system-level and application-specific. We mentioned Prometheus earlier; it&#8217;s the go-to choice in the Go ecosystem, largely because of its flexibility and strong community support. The key is to instrument your code with meaningful metrics. Put simply: if you don&#8217;t collect the right metrics, you won&#8217;t solve your issues.</p><p>So collect things like request rates, error counts, latency percentiles, goroutine counts, and garbage collection pauses. These tell you how the system is behaving and where things might degrade under load. You also get a lot of value from the Go runtime metrics exposed through the <code>runtime/metrics</code> package. These provide insight into memory usage, garbage collection activity, and goroutine scheduling&#8212;crucial when dealing with thousands of concurrent operations.</p><p>Metrics give you an aggregated view, but tracing lets you zoom in. With distributed tracing&#8212;using something like OpenTelemetry&#8212;you can follow individual requests as they move through different parts of your system. That&#8217;s where you see latency accumulation, service interactions, or contention points. Under high concurrency, tracing is especially useful for catching queuing delays, lock contention, or slow dependencies&#8212;issues that metrics alone might mask.</p><p>One of the most important practices here is context propagation. We&#8217;ve already discussed the <code>context.Context</code> type. This is your mechanism for passing timeouts, cancellations, and tracing data across API boundaries and goroutines. If you don&#8217;t propagate context properly, you&#8217;ll miss spans or lose correlation in your traces. End-to-end consistency in instrumentation is critical, especially for workloads where a request might fan out into multiple goroutines.</p><p>Of course, high concurrency also means generating a lot of telemetry data, so you need to be smart about sampling and rate limiting. Adaptive sampling works well&#8212;prioritizing traces based on latency, errors, or unusual behavior. This way, you capture the most informative data without overwhelming your observability systems or introducing overhead.</p><p>And observability isn&#8217;t just about collecting data&#8212;it&#8217;s about acting on it. Instead of relying only on fixed thresholds, use anomaly detection and pattern-based alerts. Dashboards that track Go-specific behaviors&#8212;like spikes in goroutines or increased garbage collector pauses&#8212;make it easier to spot problems early and understand what&#8217;s really happening.</p><p>In short, effective observability in high-concurrency Go systems means combining detailed metrics, distributed tracing with proper context propagation, smart sampling, and ongoing analysis. With those in place, you&#8217;re in a much better position to detect issues early, debug complex behavior, and keep systems running smoothly at scale.</p><p><em><strong>11: The standard library keeps expanding with utility packages and smarter routing in </strong></em><code>net/http</code><em><strong>. Do these reduce the need for external frameworks? What do you feel is still missing?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> The standard library of Go has always been one of its strongest features&#8212;clean, composable, well-tested, and rich. Over time, the maintainers have added to it in a very deliberate way. Things like smarter routing in <code>net/http</code> and new utility packages like <code>slices</code>, <code>maps</code>, and <code>cmp</code> have made it easier to build web services, command-line tools, and system-level software directly on top of the standard library.</p><p>Yes, these improvements are definitely reducing the need for external frameworks, especially for small to mid-sized applications. One of the best examples is <code>net/http</code>, which has steadily improved: better routing logic, smoother integration with middleware patterns, improved support for HTTP/2 and structured headers, and overall better ergonomics. For teams that prioritize simplicity, performance, and long-term maintainability, that&#8217;s a big win.</p><p>The new utility packages also help. Tasks like filtering, slicing, comparing maps, or writing type-safe logic can now be done concisely and idiomatically, reducing boilerplate and external dependencies.</p><p>That said, the standard library doesn&#8217;t replace third-party libraries entirely&#8212;especially when working on more complex systems or domain-specific problems. For example, I&#8217;ve written HTTP services in Go using Gorilla rather than plain <code>net/http</code>, and for building command-line tools I&#8217;ve used Cobra and Viper. The famous Docker tool has been written in Go using Cobra, and the Hugo static site generator also relies on Cobra and Viper. These are powerful tools for real-world utilities.</p><p>So, while the standard library is strong and keeps evolving, there are still gaps&#8212;particularly in areas like higher-level CLI frameworks or more sophisticated HTTP tooling. I expect the standard library will continue to improve, but tools like Cobra and Viper still fill important roles.</p><p><em><strong>12: Even experienced Go developers make mistakes. What are some of the less obvious ones you still see when people work on performance-sensitive or concurrent systems?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> Everyone makes mistakes&#8212;that&#8217;s how we learn. The important part is being careful not to make them in production systems.</p><p>Even experienced Go developers can run into subtle issues when working on performance-sensitive or concurrent systems. A lot of this comes from Go&#8217;s simplicity. Goroutines are lightweight, channels are first-class, and the standard library gives you powerful tools. But that simplicity can hide complexity, and mistakes often come from relying too much on defaults or making assumptions about how the runtime behaves.</p><p>One common pitfall is spinning up goroutines without proper cancellation or lifecycle management. We&#8217;ve discussed before that using <code>context.Context</code> gives you control&#8212;allowing you to cancel goroutines properly and avoid memory leaks.</p><p>Another mistake is assuming channels are always the right concurrency primitive. When I first learned about channels, I thought they could solve every concurrency problem. But that&#8217;s not true. In some cases, a mutex or an atomic variable is more efficient and easier to work with. Think carefully before using channels.</p><p>Memory allocation is another big one. Developers often overlook how temporary allocations&#8212;like slices created in a tight loop, or boxing values into interfaces&#8212;can lead to heavy garbage collection overhead, which gets worse under high concurrency. Tools like Pprof or <code>go test -bench -benchmem</code> help you spot these patterns, but ideally, you should design with memory efficiency in mind from the start.</p><p>Another mistake is making false assumptions about how the scheduler works. Developers sometimes expect goroutines to be preempted fairly, but in CPU-bound loops without I/O or channel operations, goroutines might not yield control. This can lead to starvation or uneven workload distribution. Newer versions of Go have improved scheduling and preemption, but in rare cases you still need to explicitly yield with <code>runtime.Gosched</code> to let other goroutines run.</p><p>So overall, the issues I see are not usually about syntax&#8212;they&#8217;re about architecture. They come from assumptions about how Go handles concurrency and performance under the hood. The way to avoid them is by profiling continuously, testing under realistic loads, and building a solid mental model of how the Go runtime behaves at scale. In other words, learn the internals&#8212;don&#8217;t just assume.</p><p><em><strong>13: You&#8217;ve worked very close to the metal for many years now. How would you compare Go and Rust for systems programming, especially in terms of performance, safety, and maintainability?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> This is a question I get often. Go and Rust take very different approaches to systems programming, and choosing between them depends on the specific priorities of your project.</p><p>Rust gives you fine-grained control over memory and concurrency with zero-cost abstractions that can deliver exceptional performance. Its ownership model and borrow checker eliminate entire classes of bugs at compile time&#8212;things like data races or use-after-free errors. That makes Rust a great choice for low-level systems where correctness and reliability are absolutely critical&#8212;think operating system components, device drivers, or performance-sensitive networking.</p><p>But that level of control comes with a steep learning curve. Rust&#8217;s mental model&#8212;ownership, lifetimes, trait bounds&#8212;can slow teams down, especially if they&#8217;re new to the language. Refactoring or prototyping requires great care to satisfy the compiler. Rust&#8217;s tooling (Cargo, Clippy, Rust Analyzer) is excellent, but the language demands precision. That pays off in safety and performance, but it can be a barrier in fast-moving or exploratory environments.</p><p>Go, by contrast, is all about simplicity and development speed. Its concurrency model with goroutines and channels is approachable and powerful. The garbage collector handles memory management, so you don&#8217;t need to think about it most of the time. Go may not match Rust in raw performance for compute-heavy workloads, but its performance is consistent and more than good enough for most system-level use cases.</p><p>That predictability, combined with readability and minimalism, makes Go practical for building high-throughput services, container tools, infrastructure automation, and other backend-heavy systems. It&#8217;s also easier to onboard new developers, and because Go code tends to look the same across teams, long-term maintainability is a real strength.</p><p>On safety, Go doesn&#8217;t give you compile-time guarantees like Rust. It won&#8217;t catch data races before you write code, but it does offer good tools: the race detector, a solid testing framework, and a culture that values clarity and explicitness. Go avoids complexity by design&#8212;no macro-heavy DSLs, no surprising inference&#8212;so the code stays understandable even as systems grow.</p><p>To sum up: Rust is the right tool when performance and safety are top priorities and you&#8217;re ready to invest in upfront complexity. Go shines when development speed, operational simplicity, and long-term maintainability matter more.</p><p>As an example, we once evaluated Rust for a packet inspection engine but chose Go due to faster development time and easier team onboarding.</p><p>In the past few months, I&#8217;ve also had the chance to explore Zig. It sits closer to Rust in terms of low-level control, but it&#8217;s much easier to learn. Zig has no garbage collector&#8212;you manage memory manually&#8212;but it&#8217;s far simpler than Rust. It may be a sweet spot between Go and Rust when you want to go lower without Rust&#8217;s complexity.</p><p><em><strong>14: Looking ahead, what are you most excited about in Go&#8217;s evolution over the next few releases? Where do you see the ecosystem heading?</strong></em></p><p><strong>Mihalis Tsoukalos:</strong> What excites me most is how Go continues to evolve while staying true to its roots&#8212;pragmatic, simple, but increasingly powerful.</p><p>One area I&#8217;m watching closely is the ongoing evolution of generics. Since type parameters were introduced in Go 1.18, each release has built on that foundation, most recently with features like generic type aliases and iterator functions in Go 1.23. These aren&#8217;t flashy changes, but they&#8217;re meaningful. They enable more expressive and reusable code across the ecosystem&#8212;richer data structures, functional-style APIs, and cleaner abstractions in libraries. I look forward to seeing how the standard library and open-source projects embrace these tools to offer more composable, idiomatic patterns without losing Go&#8217;s clarity.</p><p>Performance tuning and runtime observability are also maturing quickly. Built-in fuzzing, profile-guided optimizations, and expanded runtime metrics are pushing Go beyond being just easy to use&#8212;it&#8217;s becoming easy to optimize too. For teams building high-performance systems, this is a big deal. I think profiling and performance tuning will become a routine part of development workflows, just like writing tests.</p><p>Concurrency is another area evolving. Go has always had a clean concurrency model, but with Go used increasingly in multicore, high-load environments&#8212;APIs, networking layers, real-time systems&#8212;there&#8217;s more attention on scheduler improvements, memory footprint reduction, and smarter resource usage. The recent fix to the goroutine loop variable capture bug is a good example: a small change, but it eliminates a long-standing issue and makes concurrent programming safer without adding complexity.</p><p>Beyond the language, the ecosystem is maturing fast. We&#8217;re seeing better libraries, stronger tooling for testing, static analysis, and cross-compilation, and an overall improved developer experience. Projects like TinyGo, Go Cloud, and Go&#8217;s growing presence in WebAssembly and embedded environments point to a future where Go isn&#8217;t just a server-side language&#8212;it&#8217;s part of a broader portable systems toolkit.</p><p>At the same time, community efforts around formal APIs, versioning best practices, and module proxy infrastructure show that Go is becoming more production-hardened and resilient.</p><p>So in short, I&#8217;m excited that Go is getting more powerful without becoming more complicated. That&#8217;s rare in programming languages. Go is investing in performance, safety, and tooling in a way that feels very Go-like: minimal, orthogonal, and deliberate. The future looks bright because Go isn&#8217;t chasing trends&#8212;it&#8217;s solving real problems with clarity and focus. I think we&#8217;ll see it used in even more places&#8212;cloud, systems, edge, maybe even mobile&#8212;while continuing to be a language teams can rely on for the long haul.</p><div><hr></div><p>To explore the ideas discussed in this conversation&#8212;including concurrency design patterns, profiling techniques, and Go&#8217;s evolving support for generics and fuzzing&#8212;check out <em><strong><a href="https://www.packtpub.com/en-ch/product/mastering-go-9781805122647">Mastering Go, Fourth Edition</a></strong></em> by Mihalis Tsoukalos, available from Packt. This 740-page comprehensive guide dives deep into advanced Go concepts such as RESTful servers, memory management, the garbage collector, TCP/IP, and observability.</p><p>Fully updated with coverage of Go generics, fuzz testing, Docker integration, and performance optimization, the book combines detailed explanations with real-world exercises. Readers build high-performance servers, develop robust command-line utilities, work with JSON and databases, and refine their understanding of Go&#8217;s internals. Each chapter is designed to strengthen both conceptual mastery and hands-on practice, from error handling and data types to concurrency, profiling, and advanced testing.</p><p>Whether you&#8217;re building network systems, optimizing cloud-native applications, or simply aiming to deepen your Go expertise, <em>Mastering Go</em> provides a practical foundation for writing professional, production-grade software.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://www.packtpub.com/en-ch/product/mastering-go-9781805122647" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!VBef!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!VBef!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775" width="422" height="520.5439560439561" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1796,&quot;width&quot;:1456,&quot;resizeWidth&quot;:422,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Mastering Go&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:&quot;https://www.packtpub.com/en-ch/product/mastering-go-9781805122647&quot;,&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="Mastering Go" title="Mastering Go" srcset="https://substackcdn.com/image/fetch/$s_!VBef!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 424w, https://substackcdn.com/image/fetch/$s_!VBef!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 848w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 1272w, https://substackcdn.com/image/fetch/$s_!VBef!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F84ede4bc-d8fb-4c67-a46b-0855efef6779_2250x2775 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>Here is what some readers have said:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o3Vt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o3Vt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 424w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 848w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png" width="1067" height="341" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:341,&quot;width&quot;:1067,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:72150,&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://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.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_!o3Vt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 424w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 848w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.png 1272w, https://substackcdn.com/image/fetch/$s_!o3Vt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6b5d82c7-07a2-4fc2-9ed0-1de68158d324_1067x341.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6xEb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6xEb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 424w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 848w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1272w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png" width="1073" height="435" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64b48c91-d866-4a96-befe-521fea38005c_1073x435.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:435,&quot;width&quot;:1073,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98847,&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://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.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_!6xEb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 424w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 848w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.png 1272w, https://substackcdn.com/image/fetch/$s_!6xEb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b48c91-d866-4a96-befe-521fea38005c_1073x435.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><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!f0qJ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!f0qJ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 424w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 848w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1272w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png" width="1072" height="375" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:375,&quot;width&quot;:1072,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82503,&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://deepengineering.substack.com/i/171441031?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.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_!f0qJ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 424w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 848w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.png 1272w, https://substackcdn.com/image/fetch/$s_!f0qJ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F13515eea-ec51-4399-b530-4e2f69c3c44c_1072x375.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></p>]]></content:encoded></item><item><title><![CDATA[Designing for Decades: A Conversation with Alexander Kushnir on Longevity, Maintainability, and Embedded Systems at Scale]]></title><description><![CDATA[A MedTech systems engineer unpacks what it means to build software that must survive regulatory cycles, hardware obsolescence, and engineering turnover.]]></description><link>https://deepengineering.net/p/designing-for-decades-a-conversation</link><guid isPermaLink="false">https://deepengineering.net/p/designing-for-decades-a-conversation</guid><dc:creator><![CDATA[Divya Anne Selvaraj]]></dc:creator><pubDate>Tue, 12 Aug 2025 11:22:40 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1fe6b484-b1fd-47de-9026-747e11c38330_1024x1024.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In safety-critical domains, code longevity isn&#8217;t a nice-to-have&#8212;it&#8217;s a baseline constraint. Software must coexist with hardware for ten years or more, while withstanding evolving standards, team turnover, and limited upgrade paths. In this Deep Engineering Q&amp;A, we ask industry veteran <strong><a href="https://www.linkedin.com/in/alexander-kushnir-1852454/">Alexander Kushnir</a></strong> about the realities of building and maintaining embedded systems that endure. We explore long-term technical debt, the discipline of software rejuvenation, and why modern C++ idioms are reshaping how engineers think about embedded maintainability.</p><p>Alexander Kushnir is a principal software engineer at Johnson &amp; Johnson MedTech, specializing in electrophysiology systems. With about 20 years of experience across medical devices, industrial controllers, and networked embedded platforms, he has worked on everything from motion control firmware and network switches to VoIP and medical devices software . His core expertise lies in embedded Linux, modern C++, cross-platform development, and HW/SW integration. He has also built and lead a 2-day workshop related to CMake.</p><p><em><strong>1: How do you approach the challenge of managing architectural technical debt in systems with 10+ year hardware lifecycles, especially in regulated environments where major refactoring or redesign is costly and risky?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Technical debt is actually a real problem. However, we can follow several strategies to mitigate the issue:</p><ol><li><p><strong>Build modular software:</strong> This strategy pays off again and again. It helps us to isolate a specific functionality, which makes the task of &#8220;replacing the wheel in a moving car&#8221; easier.</p></li><li><p><strong>&#8220;Divide and conquer&#8221;:</strong> Separate your application logic from the hardware-dependent logic. You will benefit from that by being able to run the logic not dependent on the hardware (for instance in a simulator or using software mocks that simulate hardware behavior).</p></li><li><p><strong>Test, test, test:</strong> If you follow the previous advice, you should be able to test the logic on your development PC, not just on your target. Why is that good? You can write and run your unit tests with much shorter cycles (think - compiling, loading, debugging&#8230;all this on your PC instead of the device).</p></li><li><p><strong>Use industry-standard and up-to-date tools:</strong> Even though it is not a hard requirement, tools keep evolving, and if you fall too far behind, then when you eventually need to investigate an issue in the field, you may find yourself forced to use newer tools you&#8217;ve never worked with&#8212;leaving you at a disadvantage.</p></li></ol><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><p><em><strong>2: What strategies do you use to mitigate hardware obsolescence in long-lived systems?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Of course. It is not exactly my responsibility, but I am in the loop. When designing a hardware platform, the engineer must ensure that the components he chooses have a &#8220;long-term support&#8221;. Having said that, I prefer to use off-the-shelf System-on-Module (SOM) integrated on a custom board, rather than developing a board with the same CPU (or FPGA) and having to address most basic interfaces such as memory or a flash storage during the board bring-up. This reduces the complexity of board bring-up and makes it easier to handle hardware obsolescence, because the SOM vendor typically manages low-level design, interface validation, and long-term component sourcing.</p><p><em><strong>3: How do you reconcile the need for regular updates (e.g. for security patches or feature improvements) with the need to minimize disruption and regulatory overhead?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Every change needs to be justified.</p><p>One of the projects I am most proud of was adding a firmware update capability to a device my team was developing.</p><p>However, the regulatory burden remains &#8212; any update that could affect safety or compliance still requires formal review and, if necessary, re-certification. In practice, we minimize disruption by:</p><ul><li><p>Separating safety-critical functions into a stable, validated firmware baseline that is rarely touched.</p></li><li><p>Isolating updatable modules (non-critical logic, UI features, analytics, etc.) so they can evolve without impacting certified components.</p></li><li><p>Using risk-based change management to decide when an update is worth the cost of triggering the regulatory process &#8212; for example, prioritizing security patches and critical bug fixes, while bundling minor enhancements into larger, less frequent releases.</p></li></ul><p>In this way, the need to keep embedded software up to date becomes operationally similar to maintaining conventional PC or cloud-based software, but with the extra discipline required for regulated environments.</p><p><em><strong>4: What architectural patterns help maintain software flexibility in these conditions? For instance, have you used hardware abstraction layers, multi-process architectures, or IPC frameworks to decouple software from specific hardware so you can update or add features without a full redesign? How effective have these methods been in extending the usable life of older platforms in your experience?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Abstract all you can. Whether one is taking the OOP approach (C++, my love), or a procedural one, abstraction and modularity must be applied. Hardware Abstraction Layer (HAL) is an excellent example of abstraction, as the application logic is not aware of the hardware (for example Linux paradigm took abstraction to the edge - everything is a file, whether it is a network connection, hardware device, or a real file - the user reads from and writes to a file).</p><p>Multi-process architecture makes sense when the software has many functionalities, and if one of the functionalities has malfunctioned, it won&#8217;t affect other ones. For instance, once I worked on an infrastructure that included a terminal (CLI), database engine, and several more features. So, if the DB engine crashed, the terminal would continue running unaffected thanks to the isolation between processes.</p><p>Another tricky multi-process architecture usage is when a programmer needs to utilize a GPL-licensed library in a proprietary environment and is not interested in exposing the code. In such a case they can create a process that links with the GPL-licensed library, and communicates with the main software using a well-defined interface such as pipe, socket or shared memory.</p><p>I will repeat myself - abstract all you can. However, you must pay attention to the cost of these abstractions. For example, if you use runtime polymorphism, you&#8217;ll need to profile your virtual dispatches to verify that they create no bottleneck in your critical path.</p><p><em><strong>5: How do you decide what to keep backward compatible versus when to break from legacy constraints? Are there lessons from enduring platforms (for example, the VMEbus standard stayed relevant for 40+ years by emphasizing modularity and backward compatibility) that you apply to provide a clear migration path for long-term customers?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Well, that&#8217;s a tough question. If the device interfaces with the outer world, changing that interface will always be the last priority. However, if changes are inevitable, they can be mitigated. For example, if you think ahead when designing the protocol, you can add versioning so that new features or changes do not affect older generations of devices. In some cases, you can run multiple versions in parallel or provide adapters to bridge old and new systems, giving customers a clear migration path. This approach is similar to what made platforms like VMEbus last for decades&#8212;keep the external contracts stable, design for modularity, and plan for evolution without forcing everyone to upgrade at once.</p><p><em><strong>6: In a system meant to last a decade or more, how do you design for maintainability to slow down software aging? Can you share practices you use to avoid &#8220;bit rot&#8221; that ensure the codebase remains clean and adaptable to new requirements over time?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>All principles mentioned in my answer to the first question apply here. You can&#8217;t avoid software aging, as the ecosystem moves quickly. However, if your system is modular enough, the changes can be rolled out gradually, for instance, refactoring module by module, after testing each one thoroughly.</p><p>Additionally, CI tests are a must. I would even say that every pull request should be gated, i.e. only if the pull request passes all the tests, should it be merged. Many developers don&#8217;t like writing tests, but as a matter of fact, the tests protect them, and provide developers the confidence to make major changes without breaking things.</p><p><em><strong>7: Have you observed issues like memory leaks, data corruption, or performance degradation creeping in over long uptimes in embedded systems? If so, what proactive fault-tolerance techniques do you recommend to address this?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>I don&#8217;t believe in regular restarts or &#8220;scheduled maintenance&#8221; where the only action is a reboot. If there&#8217;s a problem like a memory leak, it should be fixed&#8212;not hidden&#8212;especially on a resource-tight device.</p><p>Memory leaks are possible, of course, but they can be avoided. In modern C++, for example, using smart pointers eliminates most manual memory management errors. During development, I also recommend dynamic memory analysis tools such as Valgrind, which is still underrated in pre-release testing. Combined with thorough code reviews and targeted stress tests, these measures catch leaks and other resource issues before deployment, reducing the need for reactive &#8220;rejuvenation&#8221; in the field. </p><p><em><strong>8: What fault-tolerance strategies do you build in to ensure long-term reliability? Can you share how you determine the right level of redundancy or self-diagnostic capability for a design that needs to last a decade?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>All the systems I&#8217;ve built have interacted with a human at some point&#8212;whether an operator, a technician, or an end user. In such cases, the most practical solution is a periodic health check, or Built-In Test (BIT), that monitors critical components and manages system state when a fault is detected. Typically, this means indicating the issue to the user&#8212;via an LED, buzzer, or display&#8212;so corrective action can be taken.</p><p>The specifics depend on the criticality of the system. For non-safety-critical designs, the goal is early detection and clear reporting so the failure can be fixed before it escalates. For higher-reliability requirements, BIT can be combined with fault isolation, allowing unaffected subsystems to keep running, or with limited redundancy (e.g., a backup sensor or communication path) to maintain partial functionality. The &#8220;right&#8221; level of redundancy or self-diagnostics is always a trade-off between cost, power, size, and the consequences of downtime&#8212;but even in minimal designs, proactive monitoring and clear fault signaling are essential for long-term reliability.</p><p><em><strong>9: How do you ensure that devices you design today can be kept secure 10+ years down the line?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>Like I&#8217;ve mentioned before, one of the features I&#8217;m most proud of is the firmware update capability we built into one of the devices I worked on. I think this is a crucial capability&#8212;not just for delivering new functionality, but also for applying OS and security patches over the device&#8217;s entire lifetime.</p><p>To keep a system secure for 10+ years, the update mechanism itself must be secure: signed and verified updates, encrypted transport, and a rollback option in case an update fails. In regulated environments, it also needs to integrate with compliance workflows so updates can be deployed without breaking certification. In some cases, it&#8217;s wise to design for network segmentation or controlled update channels, so that only trusted endpoints can initiate the process. Without this foundation from day one, long-term patching becomes either risky or impossible.</p><p><em><strong>10: Are there insights or practices&#8212;whether from automotive, avionics, or industrial IoT&#8212;that you find relevant or transferable to your work? Are there philosophies or practices from other domains that you think MedTech could borrow&#8212;or should avoid?</strong></em></p><p><strong>Alexander Kushnir:</strong></p><p>I think the processes in MedTech are good, but slow. Code review, documentation, testing&#8212;these all have a clear purpose, and they exist for good reasons. But no process has to be sanctified. Code review isn&#8217;t done just because &#8220;that&#8217;s the rule&#8221;; it&#8217;s done to catch defects and improve design. The same goes for documentation and tests&#8212;they&#8217;re tools, not rituals.</p><p>That&#8217;s something I see in other industries as well. Automotive has learned to speed up iterations without skipping the essentials, especially with OTA updates. Avionics shows how you can lock down safety-critical code while still evolving peripheral systems. From these, I think MedTech can borrow the idea of tailoring process intensity to the context&#8212;keeping rigorous control where safety demands it, but streamlining where it doesn&#8217;t. The key is to always ask: how crucial is this step at the stage we&#8217;re in right now?</p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://deepengineering.net/p/designing-for-decades-a-conversation?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://deepengineering.net/p/designing-for-decades-a-conversation?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p>]]></content:encoded></item></channel></rss>