<?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[Numeric Engineering]]></title><description><![CDATA[Writings from Numeric's engineering org]]></description><link>https://numeric.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!sTNt!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f657f4a-94c8-4476-90fc-b6bd24795302_664x664.png</url><title>Numeric Engineering</title><link>https://numeric.substack.com</link></image><generator>Substack</generator><lastBuildDate>Thu, 14 May 2026 19:47:08 GMT</lastBuildDate><atom:link href="https://numeric.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Numeric]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[numeric@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[numeric@substack.com]]></itunes:email><itunes:name><![CDATA[Andrew Bihl]]></itunes:name></itunes:owner><itunes:author><![CDATA[Andrew Bihl]]></itunes:author><googleplay:owner><![CDATA[numeric@substack.com]]></googleplay:owner><googleplay:email><![CDATA[numeric@substack.com]]></googleplay:email><googleplay:author><![CDATA[Andrew Bihl]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Engineering Spotlight: Nick Dupoux]]></title><description><![CDATA[Meet Nick: From Startup Engineer to Data-Heavy Craftsman at Numeric]]></description><link>https://numeric.substack.com/p/engineering-spotlight-nick-dupoux</link><guid isPermaLink="false">https://numeric.substack.com/p/engineering-spotlight-nick-dupoux</guid><dc:creator><![CDATA[Ben Baker]]></dc:creator><pubDate>Wed, 06 May 2026 15:06:03 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uH7d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uH7d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uH7d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uH7d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:760437,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/189719209?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.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_!uH7d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!uH7d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1547e0e2-7b60-463d-9f65-0ca5c5a4dee2_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Nick joined Numeric with nearly nine years of startup experience behind him. He started his career at Addepar as a full-stack engineer building financial software for asset management, gravitated over time toward backend development and architecture, did a brief and decisive stint in grad school (&#8221;left very quickly, was not for me&#8221;), and spent time at Attentive on their data platform team before landing at Numeric. Data-heavy work has been the running theme ever since, and it&#8217;s where he is most at home.</p><p>In this interview, Nick talks about what it means to engineer for sophisticated users, why he stopped worrying about not having a roadmap, and what on-sites taught him about who the real systems thinkers are.</p><div><hr></div><p><strong>1. What drew you to Numeric, and how has working here shaped your approach to engineering?</strong></p><blockquote><p>I think my answer is very focused on engineering culture and mindset. I really describe myself as a craftsman. I care about software as a craft and the output of software itself being high quality. I&#8217;ve always felt that the production of high quality software is an end in itself. Even if the goal is pushing out impact and value into users&#8217; hands, the best way to go about it is to also care about the details of the software.</p><p>I very much felt that sense, that intuitive feeling, from everyone I interviewed with at Numeric. Everyone seemed like they had a similar care of craft. Very crucially, though, it was balanced by a strong motivation to deliver good product at high velocity. It&#8217;s really easy to end up working on a team that cares about software but doesn&#8217;t invest enough into pushing out the right product decisions. Numeric balances that very well. It hit that hard-to-balance niche: a place where people care about the software they write, but are also pushing out product that&#8217;s actually very impactful.</p></blockquote><p><strong>2. What was the biggest mindset shift when building software for accounting teams?</strong></p><blockquote><p>Accountants are very sophisticated users, and very opinionated in the best ways possible. I took for granted just how much sophistication I can expect from them, in terms of how they do their workflows today and their sense of what good solutions look like. I have been continuously impressed with the way that accountants have solved their own problems peppered over suboptimal tooling.</p><p>As a consequence, I don&#8217;t feel afraid, and I don&#8217;t feel like I need to handhold in pushing out a solution I think is complex. In fact, sometimes what I&#8217;ve found is that my natural instinct, the voice that says &#8220;users are going to get confused about this,&#8221; is actually detrimental. It&#8217;s often better to push something in front of users that I feel is too complicated, because they&#8217;ll surprise me and grok it immediately. The more constrained solution actually limits them and pisses them off. Like: why won&#8217;t you let me do X?</p></blockquote><p><strong>3. Can you share an example of how real accounting workflows changed your technical approach?</strong></p><blockquote><p>Going on-site and watching clients close their books was a big one. I saw someone show off a Python script they&#8217;d written to automate data aggregation for a monthly email they send to their CFO. Something very specific that they solved in a very technical way. And I found that kind of finely grained, pinpointed solution is quite common. Teams have bespoke processes because of the nature of their business. They come up with unique solutions to unique problems.</p><p>That directly informs how I think now. I&#8217;m not shying away from saying we&#8217;re going to have a big configuration page where users are technically configuring conditional logic that could be quite complex. With the right UI, just handing them a big box to play with, I trust that our users can handle it. That&#8217;s directly informed by what I&#8217;ve observed from them.</p></blockquote><p><strong>4. Since joining Numeric, has your perspective on good engineering changed?</strong></p><blockquote><p>One thing I&#8217;ve very much over-indexed on in the past is having a roadmap, or at least a clear-cut North Star to inform my immediate decisions. I&#8217;ve leaned very platform-heavy, backend-heavy, critical-systems-heavy, and I used to have a lot of anxiety about the unknowns. If I didn&#8217;t have a clear sense of where something was going to grow, what the difficulties were going to be, I felt anxious.</p><p>At Numeric, it&#8217;s been nice to just let that go and become better calibrated at considering what matters in the moment. Most problems we&#8217;re trying to tackle, we won&#8217;t find the right solutions until we&#8217;re facing them. So it actually behooves us to build out software, push it out there, see what falls over, and then respond to it as it does. We&#8217;re not manning the space shuttle. If my code goes down, I don&#8217;t have to worry about the accounting team blowing up in a fiery explosion.</p></blockquote><p><strong>5. How have interactions with customers at on-sites changed your thinking as an engineer?</strong></p><blockquote><p>I think I had an unstated bias that systems thinkers are primarily found in engineering. I thought of it as: we are the computing experts coming in, you are the expert in your domain, let&#8217;s see how my expertise can help empower yours. I took for granted how much that concept of a systems thinker applies across all kinds of domains.</p><p>I no longer think engineering is where you find the systems thinkers. Accountants are systems thinkers. They work with sophisticated systems all the time. Some of our solutions team members who are former accountants are among the best systems thinkers I&#8217;ve encountered in my career. That&#8217;s a phrase I never would have said before about someone outside of engineering.</p><p>On-site, I&#8217;ve seen that play out. Someone automating a monthly email had to interact with six or seven different data systems to pull everything the CFO needs for a four-sentence email every month. That requires juggling a lot in your head. Beyond just the accounting, there&#8217;s a random constraint from an Excel spreadsheet over here, and all of that has to fit together. It was genuinely eye-opening.</p></blockquote><p><strong>6. What advice would you give engineers new to Numeric or to building products for financial systems?</strong></p><blockquote><p>Go on-site. I would be making worse software right now if I hadn&#8217;t gone on-site early on to really burst my bubble and learn more about how accountants think, and to challenge those implicit biases about what it means to deliver good software and what it even means for my users to need my help.</p><p>And then lean in heavily to your area of expertise, knowing that no matter where you choose, you&#8217;ll have interesting problems to solve. I don&#8217;t think there&#8217;s a single boilerplate, cookie-cutter area of the stack here. In extracting data, we&#8217;ve applied interesting algorithms to deal with the constraints of accounting data. In designing user workflows, there are interesting constraints around how accountants think about things and how reports have to be formatted. At Attentive, I never really had to think about the fact that I was working with marketing data. It was just blobs of data going from here to there. I don&#8217;t think that&#8217;s the case with our work. Accounting knowledge bleeds its way in everywhere.</p></blockquote><p></p>]]></content:encoded></item><item><title><![CDATA[Engineering Spotlight ]]></title><description><![CDATA[Meet Kyle: From Big&#8209;Tech Builder to Product&#8209;First Engineer at Numeric]]></description><link>https://numeric.substack.com/p/engineering-spotlight-kyle-ny</link><guid isPermaLink="false">https://numeric.substack.com/p/engineering-spotlight-kyle-ny</guid><dc:creator><![CDATA[Ben Baker]]></dc:creator><pubDate>Wed, 01 Apr 2026 19:45:44 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!CFsy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CFsy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CFsy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CFsy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:481473,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/189812082?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.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_!CFsy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 424w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 848w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!CFsy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff9643070-875d-4301-bd03-d9544e5383ad_1920x1080.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Kyle joined Numeric about a year ago, arriving by way of Amazon and then Opendoor, a mid-sized real estate tech company. Each move was deliberate. He was chasing something specific: more product thinking, more proximity to the customer, more of the work that actually ships.</p><p>At Numeric, he found it. Kyle has spent the majority of his time here building the cash matching product from the ground up. The team started building in May, shortly after he joined, and went live in December. It was genuine zero to one work in a new product space. </p><p>In this interview, Kyle shares what it actually looked like to build a brand new product from the ground up, how working directly with customers reshaped his thinking as an engineer, and what he would tell an engineer just getting started at Numeric.<br></p><ol><li><p><strong>What drew you to Numeric, and how has working here shaped the way you approach engineering problems?</strong></p><blockquote><p>What I&#8217;ve learned is that I really enjoy thinking about problems from a product lens, wearing that product hat, standing at a 30,000 foot view and figuring out what we want and what we should actually be doing. It&#8217;s a skill I both enjoyed and wanted to get better at.<br><br></p><p>I came across Numeric and it checked a lot of the boxes in terms of what we care about and how we think about product, which was kind of the foot in the door. As I learned more about the space, it started to make a whole lot more sense. I didn&#8217;t know a ton about accounting at all, but through talking to Numeric, I realized there&#8217;s this really gigantic gap between how I expected accounting to work and how it actually does.<br><br></p><p>In a way, there are a lot of parallels to how software has evolved over the years. Accounting today kind of works the same way it did back in the 90s. It&#8217;s just a bunch of unstructured data that people put all over the place and hope that it ties out at the end of the day. These teams can get pretty big and it gets very complex. There are parallels to typed programming languages, where suddenly code becomes a lot more stable when you enforce strict typing around your data and how information should flow through the system. Enforce better change management. As code changes, people review it, people understand it, and think of things more from a systems point of view instead of shifting random lines of data here and there.<br><br></p><p>That gap between how I think accounting should work and how it does today really drew me to the problem space. And similarly, nobody&#8217;s really doing anything about it yet. It seemed like a juicy problem to chew on, one that we could do a lot with, and that&#8217;s why I&#8217;m here.</p></blockquote></li><li><p><strong>What was the biggest mindset shift you experienced when building software for accounting teams versus other types of products?</strong></p><blockquote><p>One of the interesting things at Numeric is designing for the power user, which I had not really done before. Previously, the products I&#8217;d built came more from a B2C lens. When you&#8217;re designing those kinds of products, the goal is really to make things as simple as possible for the end user. Don&#8217;t make it complicated. Build things that let them easily understand what they&#8217;re trying to do.<br><br></p><p>When you&#8217;re building for accountants, it&#8217;s pretty different. These are people who are very knowledgeable about what they&#8217;re trying to do and they don&#8217;t want smooth workflows with big padding and pleasant looking things. A lot of accountants are power users who are used to working in very dense spreadsheets with a lot of information packed into one place and making decisions from there. <br><br>Correspondingly, there are a lot of really complex accounting workflows that necessitate very strong and powerful tools to get those things done. What we build at Numeric is designed for somebody who really wants strong control of what they&#8217;re doing and dense information in one place. Which is different from how I&#8217;ve thought about tools before.</p></blockquote></li><li><p><strong>Can you share an example of how real accounting workflows or user behavior changed your technical approach to problem solving?</strong></p><blockquote><p>As we were building out cash, a lot of what we were trying to understand is the line between configurability and simplicity. It&#8217;s related to this idea of designing for the power user. In cash, there&#8217;s a set of reconciliations you have to do and a set of rules you have to configure. Finding the right abstraction in that balance of complexity versus understandability has been an interesting journey for us.<br><br></p><p>Thinking through real accounting workflows, let&#8217;s say you need to create a many-to-many match between two sets of lines, and those groupings can be defined in abstract ways across data or found dynamically. There&#8217;s a lot of complexity there that made us walk this fine line between product experience and configurability. Going through the cash workflow has been really interesting in figuring out where that line is. The takeaway for problem solving specifically is to start with the simple use cases, then keep stress testing against more and more complexity, building it out, seeing where it breaks, fixing what breaks, and going from there. Really just starting simple and building out the scope from there.<br><br></p><p>What really helps is starting with a design partner early on. We worked with Brex from day one. We found them because their matching workflows were representative of the people we talked to, but not so complex that it would make it impossible to figure out where we were going.<br></p><p><br>Starting from that simpler but generalizable case, building for them, adding a few more people, building for those people too, seeing where our previous assumptions broke, and going from there has been instrumental in getting the cash product off the ground as smoothly as it did. That type of problem solving is really important in a space like accounting where you can go deeper and deeper into the weeds. It&#8217;s important not to bite off too much in the beginning.</p></blockquote></li><li><p><strong>Since joining Numeric, has your perspective on good engineering changed? If so, how?</strong></p><blockquote><p>My perspective coming in was shaped by larger companies where things move a little more slowly and carefully. Back then I thought that was just how you did engineering. You ship things that are correct and figured out already, and you continue to make them more correct over time.<br><br></p><p>Coming to Numeric, that perspective has shifted quite a bit. A very concrete example is the feature flag. At bigger companies you&#8217;ll often put things behind a feature flag, expose it to a couple users, and figure it out from there. That can work in places where it&#8217;s really important that things aren&#8217;t broken. But one thing we actually do at Numeric, especially earlier in the product life cycle, is intentionally discourage feature flags.<br><br></p><p>My point of view has shifted considerably on this. I&#8217;m now of the opinion to just get things in front of people as fast as you can. Even if it&#8217;s not perfect, get their feedback right away, because there&#8217;s always going to be a misalignment between what you built and what they were expecting. Those two things can converge if you just put it in front of them, get their feedback, and figure it out from there.<br><br></p><p>That&#8217;s representative of how we operate at Numeric. Build things with urgency and get them in front of people as fast as possible. You don&#8217;t necessarily have to be super careful about that. Don&#8217;t break things for people that matter, obviously, and once you have a lot of people depending on a product that workflow changes. But as you&#8217;re trying to figure out a problem space, there are a lot of learnings that come just from going and building it. Learnings for the engineer, learnings for the customer. When you put something in front of them, it kind of changes their perspective on what they thought they wanted in an interesting way. Proceeding with less fear and more urgency has been really important.</p></blockquote></li><li><p><strong>How have your interactions with customers either at onsites or elsewhere changed your thinking as an engineer?</strong></p><blockquote><p> It&#8217;s been really cool. We have New York clients at Numeric and I&#8217;ve been to four or five different companies for on-sites. With Brex, as we were building cash, we went on-site several times over the course of several months.<br><br></p><p>In terms of how it&#8217;s changed my thinking as an engineer, I wouldn&#8217;t say it&#8217;s materially changed how I think about a product and getting it in front of people. I think that&#8217;s always been the case. But what has changed is my motivation. It&#8217;s really energizing as an engineer to go on-site with a customer, hear what they&#8217;re frustrated about, and then go fix it for them either the same day or the next, and see their trust in you change significantly.<br><br></p><p>That&#8217;s both energizing and really awesome in terms of making someone&#8217;s day or fixing their problems. But there&#8217;s also a ton of value in just getting face time with a customer. Over a call you might have this barrier of corporate speak. Someone using your product might be scared to tell you the truth about what they think. But getting on-site with them builds rapport. You work through that barrier and suddenly they&#8217;re telling you a bunch of things they never communicated before because they trust you more and you&#8217;re there to listen.<br><br></p><p>As you go through that loop of fixing things for them as they bring them up and building that trust, you learn a lot more about your product than you thought you knew beforehand.</p></blockquote></li><li><p><strong>What advice would you give engineers new to Numeric or to building products for financial systems?</strong></p><blockquote><p>One piece of advice: you&#8217;re not really going to understand the problem until you go and build it yourself. That&#8217;s certainly been true for cash, where so much of the learning has come from just building it out.<br><br></p><p>Initially we thought cash was going to be a pretty quick project. Justin, the other engineer I worked on it with, famously said &#8220;I can build this in two weeks.&#8221; As we got into the weeds we learned more and more about the complexities of the space. It turned out to be much more complicated.<br></p><p><br>So really just go and build it. Get your hands on the data, test your assumptions, and keep going through that process over and over again. Your mental model of what the problem space actually is will get bigger and bigger. Put it in front of people and see what happens. Those learnings will build up and compound over time.</p></blockquote><p>Kyle's path from Amazon to Opendoor to Numeric reflects a deliberate pursuit of something most large companies make hard to find: the chance to build something from nothing, stay close to the customer, and feel the direct impact of your work. Building cash from the ground up gave him all of that, and then some. What comes through in his answers is an engineer who has genuinely shifted his thinking, not just about how to ship faster, but about what good engineering actually means when real users are depending on what you build. That orientation toward the customer, toward urgency, and toward learning by doing is exactly the kind of engineering culture we are building at Numeric.</p></li></ol><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Money in Postgres]]></title><description><![CDATA[Once you have a Money object in your code, the next challenge is storing it in a database without losing its structure.]]></description><link>https://numeric.substack.com/p/money-in-postgres</link><guid isPermaLink="false">https://numeric.substack.com/p/money-in-postgres</guid><dc:creator><![CDATA[Enoch Chau]]></dc:creator><pubDate>Tue, 24 Feb 2026 15:00:49 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!GiyB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Once you have a Money object in your code, the next challenge is storing it in a database without losing its structure. This guide compares common storage methods and lands on <strong>Postgres custom composite types</strong> as the superior choice. <br><br>This approach allows you to keep your amount and currency bundled together at the database level, ensuring data integrity while using <strong>Drizzle ORM</strong> to automatically map those database records into clean, type-safe TypeScript objects.</p></blockquote><p>At Numeric, we've been <a href="https://numeric.substack.com/p/designing-a-money-library-and-money?r=45uggu">designing how our code interacts with money</a> using <a href="https://martinfowler.com/eaaCatalog/money.html">Martin Fowler's money pattern</a>. As part of this effort, we want to use an integer, lowest denomination representation of money in both our application code, as well as our database.</p><p>In our previous post, we discussed modeling money using <code>BigInt</code> in JavaScript. In Postgres, we have <code>bigint</code> which isn't exactly the same as JavaScript's <code>BigInt</code> but what matters is that any valid Postgres <code>bigint</code> can be deserialized into a JavaScript <code>BigInt</code>. We could handle this bridge ourselves in application code but it's much simpler to leverage an existing ORM or query builder such as <code>drizzle-orm</code>. While it has "ORM" in its name, Drizzle is just a query builder that provides a thin, type safe layer above SQL. This helps us catch errors without abstracting away the most powerful features of SQL.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>When storing money, we have two components, the currency code and the amount. Traditionally, we would store these two values in two separate columns. This is fine, but when interacting with a money object that has amount and currency bundled together, it requires extra effort from the developer to break down the JavaScript fields into the Postgres columns. For example, let's try inserting a transaction record using <code>drizzle-orm</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GiyB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GiyB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 424w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 848w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 1272w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GiyB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png" width="1178" height="974" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:974,&quot;width&quot;:1178,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:197748,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.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_!GiyB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 424w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 848w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 1272w, https://substackcdn.com/image/fetch/$s_!GiyB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9ca304d4-5b24-42e1-bbc8-5745ac46fe6c_1178x974.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Then when we read our data back from the database, we have to reconstruct our money type.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9DNI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9DNI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 424w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 848w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 1272w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9DNI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png" width="1192" height="334" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:334,&quot;width&quot;:1192,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:54706,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.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_!9DNI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 424w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 848w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 1272w, https://substackcdn.com/image/fetch/$s_!9DNI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F302780fd-52e3-435e-a50e-a6d53d09acf0_1192x334.png 1456w" sizes="100vw"></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>This creates extra friction whenever querying the database or inserting records. When changing developer workflow across a large team, we want to make the transition as frictionless as possible in order to drive adoption. We should increase velocity, not slow ourselves down. Luckily, we can have <code>drizzle</code> handle serialization on our Money object using <a href="https://orm.drizzle.team/docs/custom-types">custom types</a>. Unfortunately, we run into the limitation that custom types cannot span across two separate columns; we would need to put both amount and currency in the same column. Traditionally, SQL doesn't allow you to do this, but with Postgres, we can use JSONB to combine both fields together.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!H2UN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!H2UN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 424w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 848w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!H2UN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png" width="878" height="1228" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1228,&quot;width&quot;:878,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:220651,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.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_!H2UN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 424w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 848w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.png 1272w, https://substackcdn.com/image/fetch/$s_!H2UN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F49e37067-44eb-4e06-9566-60a484b3af02_878x1228.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>This is fine, but we lose some restrictions that we had before. We can no longer validate that the amount is an integer and we can no longer validate that the currency has a length of three. In addition, the query syntax for JSONB deviates from regular SQL syntax. For example, if I wanted to sum all my transactions, our original two-column approach would be easier to understand.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eMAK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eMAK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 424w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 848w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 1272w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eMAK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png" width="1326" height="410" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:410,&quot;width&quot;:1326,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82786,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.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_!eMAK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 424w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 848w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.png 1272w, https://substackcdn.com/image/fetch/$s_!eMAK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25ffba6c-6fce-44c5-b44f-11321fb48729_1326x410.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>But what if we could have database-level validation and an approachable SQL syntax at the same time? Maybe we can use Postgres custom types! Using Postgres custom types, we can enforce that the amount is a <code>bigint</code> and our currency code as being three characters.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OtJM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OtJM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 424w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 848w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 1272w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OtJM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png" width="1316" height="748" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:748,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120025,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.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_!OtJM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 424w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 848w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.png 1272w, https://substackcdn.com/image/fetch/$s_!OtJM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7f43d6e2-f044-49a9-b9c0-1c52b0c176c2_1316x748.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>When summing the <code>money_with_currency</code> column, the SQL query looks pretty similar to our two-column approach.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!QhN8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!QhN8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 424w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 848w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 1272w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!QhN8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png" width="1322" height="164" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:164,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31428,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.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_!QhN8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 424w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 848w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 1272w, https://substackcdn.com/image/fetch/$s_!QhN8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F02a1b12c-c9a0-4c90-a819-ee3130de8afa_1322x164.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>In addition, a custom type defines the type for just one column, so it maps neatly to Drizzle's custom type. We can turn our JavaScript Money object into a composite type string that Postgres expects. We can also parse back Postgres' custom type string into our JavaScript Money object.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!43dc!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!43dc!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 424w, https://substackcdn.com/image/fetch/$s_!43dc!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 848w, https://substackcdn.com/image/fetch/$s_!43dc!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!43dc!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!43dc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png" width="1316" height="1122" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1122,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:224865,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.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_!43dc!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 424w, https://substackcdn.com/image/fetch/$s_!43dc!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 848w, https://substackcdn.com/image/fetch/$s_!43dc!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.png 1272w, https://substackcdn.com/image/fetch/$s_!43dc!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe8d84e80-4b02-4c4f-ae04-225c6d8fa71e_1316x1122.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>When using this custom type in our application code, <code>drizzle</code> will handle converting our Postgres custom type into our JavaScript Money object.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1QVx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1QVx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 424w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 848w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 1272w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1QVx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png" width="1316" height="714" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:714,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:164561,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.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_!1QVx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 424w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 848w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.png 1272w, https://substackcdn.com/image/fetch/$s_!1QVx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9b6b091-cf2a-4444-8401-e5e42d4b3a64_1316x714.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>With our Postgres custom <code>money_with_currency</code> type, we get database-level validation, a simple, familiar SQL query syntax, and we get clean serialization and deserialization of our JavaScript Money object when interacting with the database.</p><p>Custom types are not supported with Drizzle&#8217;s default query helpers like <code>eq</code> and <code>inArray</code>, so we can create helpers to access our amount and currency fields.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0bZ5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0bZ5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 424w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 848w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 1272w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0bZ5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png" width="1322" height="284" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:284,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:60999,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.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_!0bZ5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 424w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 848w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 1272w, https://substackcdn.com/image/fetch/$s_!0bZ5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd21c53d5-17ab-41fd-9fe2-15bed4a72586_1322x284.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>And we can continue extending this idea to create other complex SQL string builders. For example, we could create a custom <code>moneyEq</code> helper that can check equivalency on both amount and currency.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!XlHD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!XlHD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 424w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 848w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 1272w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!XlHD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png" width="1318" height="496" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:496,&quot;width&quot;:1318,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:124461,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.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_!XlHD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 424w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 848w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.png 1272w, https://substackcdn.com/image/fetch/$s_!XlHD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6a386ee0-8a4c-4a9b-b593-a843b7236ff8_1318x496.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>This is a simple example where we can only compare a column against a JavaScript Money object, but you can extend the query helper to also be compatible with checking equivalency between <code>money_with_currency</code> columns on different tables.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BetD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BetD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 424w, https://substackcdn.com/image/fetch/$s_!BetD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 848w, https://substackcdn.com/image/fetch/$s_!BetD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 1272w, https://substackcdn.com/image/fetch/$s_!BetD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BetD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png" width="1316" height="624" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:624,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:148257,&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://numeric.substack.com/i/188457699?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.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_!BetD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 424w, https://substackcdn.com/image/fetch/$s_!BetD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 848w, https://substackcdn.com/image/fetch/$s_!BetD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.png 1272w, https://substackcdn.com/image/fetch/$s_!BetD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F18dc4648-d29d-4105-8895-c0565f5bd6e3_1316x624.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>Providing a set of well-tested query helpers that are equivalent to Drizzle&#8217;s query helpers can reduce errors from hand-writing SQL template strings and also take care of SQL edge cases that downstream developers may forget to consider.</p><p>In conclusion, using a Postgres custom type allows us to cleanly map our JavaScript money representation into the database. In comparison with our two-column approach, we remove the need to manually map queried data into our JavaScript money object. When comparing to our JSONB approach, we get database-level data validation and a simpler query syntax. Using our <code>money_with_currency</code> type provides a clean interface when moving between JavaScript and Postgres.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Designing a Money Library]]></title><description><![CDATA[Standard decimals and floats often lead to &#8220;disappearing cents&#8221; due to rounding errors.]]></description><link>https://numeric.substack.com/p/designing-a-money-library-and-money</link><guid isPermaLink="false">https://numeric.substack.com/p/designing-a-money-library-and-money</guid><dc:creator><![CDATA[Enoch Chau]]></dc:creator><pubDate>Wed, 11 Feb 2026 19:01:26 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!oDPa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p>Standard decimals and floats often lead to &#8220;disappearing cents&#8221; due to rounding errors. This post explains why you should treat money as a <strong>composite object</strong>&#8212;pairing a currency code with a <strong>BigInt</strong> (the smallest unit, like cents) instead of a decimal. By using specific algorithms for splitting and allocating funds, you can ensure that every penny is accounted for across your application, while leveraging modern browser APIs to handle complex international formatting.</p></blockquote><p>When we think about money, it&#8217;s easy to think of modeling it as a decimal number. After all, we have dollar amounts for our integer component and cents for our decimal component. If you&#8217;ve used decimal numbers in JavaScript, you&#8217;ve inevitably run into its bizarre division behavior.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!TYE_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!TYE_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 424w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 848w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 1272w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!TYE_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png" width="1316" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:31352,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.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_!TYE_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 424w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 848w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 1272w, https://substackcdn.com/image/fetch/$s_!TYE_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21fe73b3-7f93-49c5-9fc0-da32f04680a1_1316x196.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>This is a real issue with modeling money since first, $0.000000000000000004 does not exist and second, the math isn't even being computed correctly. Incorrect computations are solvable with a big decimal library such as <a href="https://www.npmjs.com/package/big.js?activeTab=readme">big.js</a> that supports arbitrary and accurate decimal multiplication and division. So let's try that out with a new example.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Xy52!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Xy52!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 424w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 848w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 1272w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Xy52!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png" width="1312" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:39475,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.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_!Xy52!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 424w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 848w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 1272w, https://substackcdn.com/image/fetch/$s_!Xy52!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7c0f223a-2ba0-40dd-865d-1d712e4124c5_1312x196.png 1456w" sizes="100vw"></picture><div></div></div></a></figure></div><p>Well, $1.66666666666666666667 is still not quite a valid value for US Dollars. We have to treat money as a separate data type that doesn&#8217;t behave like a regular decimal number.</p><p>This is exactly the problem we hit at Numeric. We were using a variant of <a href="https://www.npmjs.com/package/big.js?activeTab=readme">big.js</a> to handle money, modeling it as an arbitrary-precision decimal. We&#8217;ve since decided to adopt the <a href="https://martinfowler.com/eaaCatalog/money.html">Martin Fowler Money pattern</a>. The basic idea is that unlike regular numbers, money is a real thing that has a smallest denomination.</p><p>Treating money as a decimal number is straightforward; what you see is what you get: a 10.99 decimal is $10.99 USD. The issue arises when dealing with rounding. Without good primitives, it&#8217;s up to each developer to handle rounding themselves. For example, splitting a $1,000 contract over three months results in three invoices of $333.333... The problem is that three-tenths of a cent doesn&#8217;t exist in the real world, much less 33 hundredths or 333 thousandths.</p><p>So, what are the rounding rules here? We can&#8217;t just round down; otherwise, the sum would become $999.99 and we&#8217;d be missing a cent! Our books won&#8217;t add up during the month-end close. But instead of discussing remainders and long division, perhaps we should answer a different question: How can we model a real-world value like money in code?</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5Uvu!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5Uvu!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 424w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 848w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 1272w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5Uvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png" width="1314" height="72" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:72,&quot;width&quot;:1314,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:19808,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.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_!5Uvu!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 424w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 848w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 1272w, https://substackcdn.com/image/fetch/$s_!5Uvu!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb47d1ba2-6fc0-424f-9f7c-27539f695d5e_1314x72.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>In the past, JavaScript was limited by its data types, supporting only floats as the <code>number</code> type. <a href="https://github.com/dinerojs/dinero.js/blob/b4cdbebbc143f07f1b83f82363cfeee9e02ee6de/packages/dinero.js/src/dinero.ts#L24-L25">Some libraries</a> solved this issue by checking <code>Number.isInteger</code>, but nowadays, we can instead use <code>BigInt</code> to enforce this at the data type level. Finally, a worthy integer type. Of course, we can't ignore the runtime penalty of <code>BigInt</code> compared to <code>number</code> when doing math operations like <code>+</code> or <code>-</code>; however it's a worthy trade-off since there is the additional benefit of avoiding number overflows when attempting to represent really large amounts of money. With this in mind, our example becomes as follows.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dvn1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dvn1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 424w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 848w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 1272w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dvn1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png" width="1316" height="158" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:158,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:38241,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.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_!dvn1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 424w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 848w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 1272w, https://substackcdn.com/image/fetch/$s_!dvn1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F620fc39e-a910-4508-9dc0-f16b0d4119f1_1316x158.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>With that in place, we must also consider the currency component of money. We can start with a simple example of adding together money of two different currencies.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mHZz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mHZz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 424w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 848w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 1272w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mHZz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png" width="1316" height="200" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:200,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:35963,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.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_!mHZz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 424w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 848w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 1272w, https://substackcdn.com/image/fetch/$s_!mHZz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffc9360b7-ba8d-42af-b9ba-d3f51e22a286_1316x200.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>If I handed you a ten US dollar bill and a ten Euro bill, you wouldn't be able to buy a burrito worth twenty dollars in either currency. Money operates on the principle of currency. We can't add money together unless we know we have the same currency. This is easy in the real world, since Euros and Dollars look different. It's also easy in JavaScript. Let's add a currency component to our money representation.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mvuy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mvuy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 424w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 848w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 1272w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mvuy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png" width="1322" height="158" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:158,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:50163,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.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_!mvuy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 424w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 848w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 1272w, https://substackcdn.com/image/fetch/$s_!mvuy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F48e637bc-07e0-460a-86ec-b3d8d36a8ab8_1322x158.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>And let's add some checks before we add our money.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oDPa!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oDPa!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 424w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 848w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 1272w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oDPa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png" width="1318" height="532" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:532,&quot;width&quot;:1318,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:116092,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.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_!oDPa!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 424w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 848w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.png 1272w, https://substackcdn.com/image/fetch/$s_!oDPa!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34a53a44-2c4f-4949-b652-eed461afbd28_1318x532.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>This is a good start to building a money library. Using the principle of currency comparison before operation, we can build functions for many common mathematical operations such as comparison operators (greater than, less than, equal to, etc.), addition and subtraction.</p><p>The main issue with this approach is that we must do explicit currency comparison before we operate in order to avoid runtime exceptions.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!idQ8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!idQ8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 424w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 848w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 1272w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!idQ8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png" width="1314" height="368" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:368,&quot;width&quot;:1314,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:86050,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.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_!idQ8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 424w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 848w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.png 1272w, https://substackcdn.com/image/fetch/$s_!idQ8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F261c82c3-6e3a-4f60-b198-42ce5dae0695_1314x368.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><h2>Leveraging the Type System</h2><p>So what if we could leverage TypeScript&#8217;s type system to enforce the same currency rule during compile time? It might look something like this.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Kytr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Kytr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 424w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 848w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 1272w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Kytr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png" width="1316" height="872" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:872,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:164144,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.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_!Kytr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 424w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 848w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 1272w, https://substackcdn.com/image/fetch/$s_!Kytr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff8721123-80b7-4078-a1aa-70dd5e83d1dd_1316x872.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The compiler can actually catch that we are operating on two different currencies! This is a <a href="https://deque.blog/2017/08/17/a-study-of-4-money-class-designs-featuring-martin-fowler-kent-beck-and-ward-cunningham-implementations/">fun idea</a> coming from Haskell's type system. Unfortunately, the compiler can't see into the real world. When we receive money over the wire from an API call or from a database query, we still need to have our runtime checks to enforce currency compatibility.</p><h2>Division</h2><p>Now that we can add money together, the next most useful thing is dividing money. We have a lot of accountants at Numeric and they do love to divide money into different buckets. Division is not as simple as addition; we must take special care when implementing operators that can produce remainders. Remember, money is a real thing, not an arbitrary floating point number.</p><p>Let&#8217;s start with a simple example. Andrew, Parker, and Anthony go to lunch. The total for lunch comes out to one hundred dollars and, since they&#8217;re good friends, they decide to split the bill evenly in thirds.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HS3H!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HS3H!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 424w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 848w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 1272w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HS3H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png" width="1314" height="372" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:372,&quot;width&quot;:1314,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89039,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.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_!HS3H!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 424w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 848w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.png 1272w, https://substackcdn.com/image/fetch/$s_!HS3H!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22b48e35-2fed-48b9-8648-6d591a5aefef_1314x372.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>Where did the last cent go? If you&#8217;ve used Venmo to split a bill before, you may recognize this scenario: one person ends up having to pay the remaining 1 cent. In actuality, Andrew might pay $33.33, Parker pays $33.33, and Anthony pays $33.34.</p><p>We can express this as a function called <code>split</code>. You can think of this like integer division that automatically takes care of the remainder.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!3cKK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!3cKK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 424w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 848w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 1272w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!3cKK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png" width="1320" height="204" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:204,&quot;width&quot;:1320,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:55791,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.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_!3cKK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 424w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 848w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 1272w, https://substackcdn.com/image/fetch/$s_!3cKK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F69f90fab-c7cd-4a4d-ad3f-ad7c94e54711_1320x204.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>It's the next day now, and Andrew, Parker, and Anthony go out to lunch again. Once again, they spend $100 on lunch. Let's pretend Andrew decided to get an entire bowl of calamari for himself; Parker and Anthony aren't happy he didn't share. They no longer want to split the bill evenly. If Andrew's share of the bill was $70.00 then Parker and Anthony only owe $15 each, then we can express this with the <code>allocate</code> function. <code>allocate</code> is similar to <code>split</code>, the difference being that we can define our percentages.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!1zZY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!1zZY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 424w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 848w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 1272w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!1zZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png" width="1316" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/df224923-a083-4526-b6ad-e72293276f6b_1316x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:58046,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.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_!1zZY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 424w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 848w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 1272w, https://substackcdn.com/image/fetch/$s_!1zZY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fdf224923-a083-4526-b6ad-e72293276f6b_1316x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>While these examples seem straightforward, with arbitrary amounts of money, <code>split</code> and <code>allocate</code> can help prevent rounding errors resulting from division. Multiplication, on the other hand, often doesn't result in an even split.</p><h2>Multiplication</h2><p>In finance, we commonly have to multiply by percentages in situations such as taxes and interest. For example, our sales tax in San Francisco is 8%. How can we express this using integers? If we try to multiply our <code>BigInt</code> amount with a decimal number, we get an error.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cMMo!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cMMo!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 424w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 848w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 1272w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cMMo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png" width="1326" height="196" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:196,&quot;width&quot;:1326,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:59056,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.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_!cMMo!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 424w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 848w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 1272w, https://substackcdn.com/image/fetch/$s_!cMMo!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb98e1bc-1ef3-48e3-9b25-f21feb8b6158_1326x196.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>If we only use numbers for multiplication, then JavaScript doesn't complain of a type mismatch. In this case, we end up with a tax amount of $7.9992.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-RWW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-RWW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 424w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 848w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 1272w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-RWW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png" width="1322" height="162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:162,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:30205,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.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_!-RWW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 424w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 848w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 1272w, https://substackcdn.com/image/fetch/$s_!-RWW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F21b6bfdc-47ae-4984-88bf-33b62d8bced7_1322x162.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>When we think about percentages, there are actually two components we are dealing with. First is the <code>multiplier</code>, in our case, it is 8 since our tax is 8%. Then we have our <code>scale</code> which is the "percentage" or parts of 100, so our scale is 100. Both <code>multiplier</code> and <code>scale</code> are integers.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LK_D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LK_D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 424w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 848w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 1272w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LK_D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png" width="1322" height="288" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:288,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:74896,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.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_!LK_D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 424w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 848w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 1272w, https://substackcdn.com/image/fetch/$s_!LK_D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F59da6f9a-45e7-4233-b00b-7529f4967f21_1322x288.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>What are we supposed to do with 92 hundredths of a cent? In the real world, for sales tax, different countries, states, and counties can all implement different <a href="https://en.wikipedia.org/wiki/Rounding">rounding methods</a> in their local laws. For example, in San Francisco we currently round up to the nearest cent, but there have been proposals to round to the nearest nickel.</p><p>So we have to force ourselves to deal with that remainder!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Q5sA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Q5sA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 424w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 848w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Q5sA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png" width="1316" height="788" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:788,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:154293,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.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_!Q5sA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 424w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 848w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.png 1272w, https://substackcdn.com/image/fetch/$s_!Q5sA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6472328a-7bdb-4458-b6c1-a37b3e8b57c1_1316x788.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>With division and multiplication complete, we can continue building our library by adding helper functions such as <code>sum</code>.</p><h2>Sum</h2><p>When summing money, we can start with a simple reduce.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Tkav!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Tkav!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 424w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 848w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 1272w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Tkav!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png" width="1318" height="754" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:754,&quot;width&quot;:1318,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:118729,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.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_!Tkav!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 424w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 848w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.png 1272w, https://substackcdn.com/image/fetch/$s_!Tkav!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F12568567-5350-492f-9424-4e5cc16b0f5b_1318x754.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>This works fine, but what if we are presented with an array of money with different currencies? Should we simply allow our <code>add</code> function to throw an error? We could convert all our money into the same currency but then we would be losing some currency data. That is one valid strategy; but let's think about real life again. If I handed you a bag of money with mixed currencies, in order to spend the money, you'd need to sort it into piles of cash of the same currency. When we bag sum, we compare currencies before adding so we can avoid mismatched currency errors.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!POzb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!POzb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 424w, https://substackcdn.com/image/fetch/$s_!POzb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 848w, https://substackcdn.com/image/fetch/$s_!POzb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 1272w, https://substackcdn.com/image/fetch/$s_!POzb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!POzb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png" width="1316" height="952" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:952,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:161514,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.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_!POzb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 424w, https://substackcdn.com/image/fetch/$s_!POzb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 848w, https://substackcdn.com/image/fetch/$s_!POzb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.png 1272w, https://substackcdn.com/image/fetch/$s_!POzb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F34375e3a-2021-42aa-ae83-d1a2f6c4eef2_1316x952.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>Then, if we still want to, we can handle currency conversion on the resulting bags of money. Currency conversion is out of scope for this post, however it&#8217;s worth noting that some money is always lost during conversion due to rounding. When we bag sum before converting currencies we reduce the amount of error since we are only doing currency conversion on the resulting bag rather than the individual amounts in the bag.</p><p>We can reuse this strategy for other operations like finding mean, median or average. With these concepts, we can build the majority of mathematical expressions a developer might expect out of a money library.</p><h2>Display</h2><p>Of course, money is a real thing that people have to look at, whether that be your wallet, a checkbook, or online banking. Thankfully, the good people at the <a href="https://www.w3.org/about/">W3C</a> have already thought about this and graciously gave us <code>Intl.NumberFormat</code>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yRTw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yRTw!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 424w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 848w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 1272w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yRTw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png" width="1322" height="494" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:494,&quot;width&quot;:1322,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:102238,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.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_!yRTw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 424w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 848w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.png 1272w, https://substackcdn.com/image/fetch/$s_!yRTw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb4985ea-2a43-4d2e-9268-53243f700d7b_1322x494.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>Wait what? We ended up getting <code>$1,000.00</code>, not what we expected! We should be seeing <code>$10.00</code>. Unfortunately, the good people at the W3C didn&#8217;t consider that we were making an integer-based Money library.</p><p>To get formatting to work properly, we have to first scale down our <code>BigInt</code> amount into its decimal string components. For US dollars, we would scale our money by a precision of two since we have two decimal places to represent cents. For other currencies like Japanese Yen, there is no precision since there is no concept of cents.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a> Let&#8217;s update our currency type.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yHti!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yHti!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 424w, https://substackcdn.com/image/fetch/$s_!yHti!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 848w, https://substackcdn.com/image/fetch/$s_!yHti!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 1272w, https://substackcdn.com/image/fetch/$s_!yHti!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yHti!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png" width="1316" height="244" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:244,&quot;width&quot;:1316,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:64567,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.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_!yHti!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 424w, https://substackcdn.com/image/fetch/$s_!yHti!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 848w, https://substackcdn.com/image/fetch/$s_!yHti!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 1272w, https://substackcdn.com/image/fetch/$s_!yHti!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feb89dc2b-5d8f-472f-8348-2497c36fa61f_1316x244.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Then we can augment our format function with our new currency type, making sure to break down our integer into its real dollar and cent parts.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G89R!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G89R!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 424w, https://substackcdn.com/image/fetch/$s_!G89R!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 848w, https://substackcdn.com/image/fetch/$s_!G89R!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 1272w, https://substackcdn.com/image/fetch/$s_!G89R!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G89R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png" width="1184" height="1318" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1318,&quot;width&quot;:1184,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:249418,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.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_!G89R!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 424w, https://substackcdn.com/image/fetch/$s_!G89R!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 848w, https://substackcdn.com/image/fetch/$s_!G89R!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.png 1272w, https://substackcdn.com/image/fetch/$s_!G89R!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd18f43ed-913f-4ca0-9ac3-74571ee9024a_1184x1318.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>With formatting complete, we've covered most of the functionality needed to build on the web. From here, our library could take different shapes, so far, we've been writing examples using a functional pattern but we could also do class based chaining.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Ktyd!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Ktyd!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 424w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 848w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 1272w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Ktyd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png" width="1186" height="252" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:252,&quot;width&quot;:1186,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:42935,&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://numeric.substack.com/i/187602168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.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_!Ktyd!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 424w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 848w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 1272w, https://substackcdn.com/image/fetch/$s_!Ktyd!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fea170fb5-84d6-4d02-bfa9-ffc1c895dba2_1186x252.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Whatever ergonomics you prefer, just keep in mind that money is real! You can use it to buy things, and it has physical properties in the form of nickels and quarters and stacks of hundred dollar bills.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Almost all currencies are base ten, the ones that aren't are out of scope for this blog post.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Upgrading DrizzleORM Logging with AsyncLocalStorage]]></title><description><![CDATA[Filling in the missing half of Drizzle&#8217;s query logs]]></description><link>https://numeric.substack.com/p/upgrading-drizzleorm-logging-with</link><guid isPermaLink="false">https://numeric.substack.com/p/upgrading-drizzleorm-logging-with</guid><dc:creator><![CDATA[Justin Chang]]></dc:creator><pubDate>Tue, 13 Jan 2026 18:02:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZttX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Every few months, you probably read about some obscure technique, technology, or language feature and think, <em>&#8220;I should remember this exists.&#8221;</em> Most of the time, that knowledge just sits there, taking up mental real estate. Occasionally, though, you hit a problem and realize you&#8217;ve been carrying around the exact solution for months without knowing it.</p><p>That happened to me with Node.js AsyncLocalStorage. I remember reading a blog post about it a while back and thinking, <em>&#8220;That sounds useful; I have absolutely no use case for it.&#8221;</em> Months went by, and that assessment held true&#8212;AsyncLocalStorage was a neat tool with nothing to unlock.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Then DrizzleORM&#8217;s logging limitations finally gave me the excuse I&#8217;d been waiting for.</p><h2>The Problem</h2><p>At Numeric, we use <a href="https://orm.drizzle.team/">Drizzle</a> as our fully-typed Postgres query builder. I&#8217;ll save the deeper discussion of why we chose Drizzle and how we think about our tools for another post, but long story short, for our core technologies, we like tools that don&#8217;t abstract away the thing they&#8217;re wrapping. One of our core engineering values is taking pride in extremely deep knowledge of our tools, and we consider SQL to be among our most critical. Drizzle&#8217;s query builder creates queries that are immediately intelligible to anyone who writes SQL, which is exactly what we want.</p><p>But Drizzle is still an early, beta product. That comes with some blind spots. Custom query logging is one of them.</p><p>For every database query, we need a canonical log line that includes:</p><ul><li><p>A unique query key (for tracking specific queries across our codebase)</p></li><li><p>Execution time in milliseconds</p></li><li><p>The sanitized SQL query</p></li><li><p>Argument count and the sanitized arg values</p></li><li><p>Row count from the results</p></li></ul><p>We&#8217;ve relied on these logs for years with our old Postgres client (slonik) for monitoring, optimization, and debugging in Datadog. When we switched to Drizzle, I needed to maintain that same logging capability.</p><p>Drizzle&#8217;s logging story is... minimal. They give you one logger override that exposes a single handler function. This handler gets called <em>before</em> execution and only gives you access to the query and arguments. That&#8217;s it. You can&#8217;t benchmark execution time because you never see when the query finishes. You can&#8217;t log row counts because you never see the results. And they <a href="https://orm.drizzle.team/docs/goodies#logging">document this under the &#8220;Goodies&#8221; section</a> (oof).</p><p>The most upvoted solution in <a href="https://github.com/drizzle-team/drizzle-orm/issues/2605">this GitHub issue</a> involves JavaScript prototype manipulation to override node-postgres internals. Prototype hacks work until they break spectacularly, and you&#8217;re now coupled to library implementation details. Knowing how painful that failure mode is, I was happy to save myself the trouble and keep looking.</p><h2>The Answer in the Attic: Async LocalStorage</h2><p>AsyncLocalStorage is one of those Node.js features that sounds almost too good to be true. It lets you maintain coherent context data throughout an entire async call stack. If you come from React, think of it like <code>useContext</code> but for async operations. If you come from languages with threading, it&#8217;s the async equivalent of thread-local storage.</p><p>Under the hood, it works because Node.js tracks every async operation with an ID and maintains parent-child relationships between operations. When you call <code>AsyncLocalStorage.run()</code>, Node associates your context with that async ID. When you spawn child async operations, Node links them to their parents. Later, when you call <code>getStore()</code>, Node walks up this chain to find your context.</p><p>From the <a href="https://nodejs.org/api/async_context.html">Node.js docs</a>:</p><blockquote><p>These classes are used to associate state and propagate it throughout callbacks and promise chains. They allow storing data throughout the lifetime of a web request or any other asynchronous duration.</p></blockquote><p>Brilliant&#8212;this was exactly what we needed. We could create a context at the top of our database call stack, let Drizzle&#8217;s limited logger populate what it could access, and then complete the log line when we got back to the top with the full results.</p><h2>The Solution</h2><p>The implementation has three parts working together to produce complete, post-execution query logs using Drizzle and AsyncLocalStorage:</p><h3>1. Set up AsyncLocalStorage to hold query context</h3><p>First, create a context store that will hold query metadata throughout the async call stack:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZttX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZttX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 424w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 848w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 1272w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZttX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png" width="1400" height="1424" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/eab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1424,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:228424,&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://numeric.substack.com/i/184452888?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.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_!ZttX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 424w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 848w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 1272w, https://substackcdn.com/image/fetch/$s_!ZttX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Feab206ed-1219-49a3-8711-5ed71e389938_1400x1424.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The <code>wrapQuery</code> function creates a new context with initial data (query key and start time) and runs the provided function within that context. Anything called within <code>fn</code> can access or modify this context.</p><h3>2. Use Drizzle&#8217;s logger to capture query details</h3><p>Drizzle&#8217;s custom logger runs before query execution. We use it to push query details into the context we created:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lmEZ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lmEZ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 424w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 848w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 1272w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lmEZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png" width="1394" height="616" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:616,&quot;width&quot;:1394,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:120584,&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://numeric.substack.com/i/184452888?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.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_!lmEZ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 424w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 848w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 1272w, https://substackcdn.com/image/fetch/$s_!lmEZ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5be5d886-d1c9-4c3f-a049-c418c575683e_1394x616.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Now when Drizzle executes a query and calls our logger, it automatically adds the SQL and parameters to whatever context is currently active.</p><h3>3. Wrap query execution to complete the log</h3><p>Finally, wrap Drizzle queries with the context and emit complete log lines:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Sr00!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Sr00!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 424w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 848w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 1272w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Sr00!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png" width="1260" height="1432" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1432,&quot;width&quot;:1260,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:232320,&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://numeric.substack.com/i/184452888?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.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_!Sr00!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 424w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 848w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 1272w, https://substackcdn.com/image/fetch/$s_!Sr00!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F51865cb7-43e3-44d5-9024-db79a738084d_1260x1432.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The flow:</p><ol><li><p><code>wrapQuery</code> creates a context with the query key and start time.</p></li><li><p>Drizzle executes the query and calls our logger, which adds SQL and params to that context.</p></li><li><p>Back at the top, we grab the context to get all the pieces and emit a complete log line.</p></li></ol><p>Everything flows through AsyncLocalStorage automatically. No manual context passing, no prototype manipulation, no hooks into library internals.</p><h2>Conclusion</h2><p>With this implementation, every database query now produces a complete, structured log line:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dv0b!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dv0b!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 424w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 848w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 1272w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dv0b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png" width="1262" height="516" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:516,&quot;width&quot;:1262,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:83675,&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://numeric.substack.com/i/184452888?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.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_!dv0b!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 424w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 848w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 1272w, https://substackcdn.com/image/fetch/$s_!dv0b!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25555108-f46c-412f-8ad8-415ac0d1f8c6_1262x516.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The AsyncLocalStorage approach also gives us type safety and no runtime overhead beyond what we&#8217;d already have with any logging solution. Most importantly, we didn&#8217;t have to touch JavaScript prototypes or mess with library internals.</p><p>The pattern shows up in more places than you&#8217;d think. OpenTelemetry uses AsyncLocalStorage for trace propagation. Sentry uses it to maintain error context across async boundaries. A bunch of logging libraries use it to attach request IDs to all logs within a request. Once you know the pattern exists, you start seeing it everywhere. Maybe this will be the blog post that you remember in years once you too have a reason for AsyncLocalStorage!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Accounting has only CRUD APIs]]></title><description><![CDATA[History doesn't repeat itself but it often rhymes.]]></description><link>https://numeric.substack.com/p/accounting-will-retrace-the-path</link><guid isPermaLink="false">https://numeric.substack.com/p/accounting-will-retrace-the-path</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Mon, 10 Nov 2025 03:29:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!SV_r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Accounting is soon going to speed-run decades of software development learnings and technological progress. The field is stretched to a breaking point and must evolve.</p><p>A company&#8217;s accounting department is responsible for the financial data set of the business. Accounting itself is the process of collecting, interpreting, and verifying an entity&#8217;s financial activity; it&#8217;s ultimately about translating the infinite unique complexities of businesses into a common language. This information not only prevents catastrophic mistakes and fraud, but also serves as a critical window into the business to support strategic decision-making around budgeting, investment, risk, and planning.</p><p>Software engineers have experience managing complex systems of data. The field evolved as a consequence of growing data volume and complexity&#8212;tools that we take for granted from the last 60 years like relational databases, version control, unit testing, cloud computing, observability, and more emerged out of necessity. As computers became ubiquitous, the volume of data and the demand for software exploded; engineering simply had to change to keep up. Practices evolved as well. Engineers now hold the correctness and consistency of application data as paramount. We treat bugs producing bad data as critical, and understand statefulness to be one of the foundational challenges of our work.</p><p>The practice of accounting has historically been comparably modest in its data volume and complexity. Consequently, it didn&#8217;t require the degree of evolution and sophistication around data that software development had. Over the last 25 years, however, the volume and complexity have ballooned, revealing the shortcomings of today&#8217;s tools and practices.</p><p>Accounting now faces a path for change similar to the one that software engineering took as the world digitized.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SV_r!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SV_r!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SV_r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg" width="1400" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:374750,&quot;alt&quot;:&quot;Image sourced from the Public Domain Image Archive / Wellcome Collection: https://pdimagearchive.org/images/2e24636b-0f15-4e61-9e38-a510ea5abeda&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/178371656?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Image sourced from the Public Domain Image Archive / Wellcome Collection: https://pdimagearchive.org/images/2e24636b-0f15-4e61-9e38-a510ea5abeda" title="Image sourced from the Public Domain Image Archive / Wellcome Collection: https://pdimagearchive.org/images/2e24636b-0f15-4e61-9e38-a510ea5abeda" srcset="https://substackcdn.com/image/fetch/$s_!SV_r!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SV_r!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0024ef80-2596-4f1a-93e3-12c2a7a8207f_1400x760.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><strong>Abstraction, interfaces, and CRUD APIs</strong></p><p>A first step in this journey is to move beyond the world of simple CRUD interfaces. To understand the evolution from basic practices to robust systems, consider the progression of a small software application into a mature product. If a bug caused a failed user sign-up, a developer of a small hobby project may find it perfectly natural to simply insert a user record to the database. He or she may not even need an endpoint&#8212;simply connecting to the database and executing a query could get the job done.</p><p>If the application grows, however, our developer will encounter challenges. A single operation can cascade multiple downstream effects, like updating various data stores, firing requests to 3rd-party services, sending emails, etc. This is when database transactions become essential to avoid writing bad data as the application scales. When multiple operations can trigger writes on the same tables, the difficulty of ensuring consistent behavior increases. And as more engineers join the team, observability and history become critical for debugging.</p><p>So at a small enough scale, it&#8217;s reasonable for a developer to connect to the database and fire off an ad-hoc query to insert the user record. But for an engineer at Facebook, the same behavior would be borderline insane. Who knows how many events and side effects occur when a user is created in Facebook? How many layers of validation, permissions, and logic have been skipped? Additionally, ad-hoc write queries create a nightmare for reproducibility and debugging down the line.</p><p>This direct insert query is a &#8220;CRUD&#8221; operation. The term is used to describe interfaces which contain very little logic between the inputs and the ultimate interaction with the database. CRUD is simple, imperative, and un-abstracted.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Complex applications require abstraction to unify and manage more nuanced needs and logic. In a mature system like Facebook&#8217;s application, few write operations would hit CRUD-like interfaces directly. Instead, we build and iterate on higher level abstractions to manage complexity in the face of a growing scope and size of system. This is what allows us to build progressively atop our systems while ensuring data consistency.</p><p>In short, our small application grows, and the viability of ad-hoc queries and scripts diminishes. So we must invest in a more structured approach by developing proper APIs and systems to support abstraction and to obviate the practice of directly manipulating the data.</p><p><strong>Accounting has only CRUD</strong></p><p>Accounting in enterprises today has reached this very inflection point. The ad-hoc tooling has stretched too far and it&#8217;s time for a better system. Day-to-day work is a painful game of constant catch-up because errors abound, testing is done after the fact, and a huge proportion of the work is reviewing and correcting for the mistakes and holes.</p><p>In spite of new scale and complexity, accounting gained little in the way of new tools over the last 25 years. In that time, the number of digitized sources, the volume and granularity of data, the expectations for liveness and richness of information, the complexity of accounting standards (especially post-2001 and post-2008), and the ubiquity of multi-national and multi-currency businesses have all come knocking at accounting&#8217;s door. The software which is expected to handle all of this complexity still looks suited to a world of manual data entry, yet the problems at hand more closely resemble modern data engineering challenges.</p><p>Much of the blame falls on the general ledger. It is the system of record&#8212;every sale, refund, purchase, loan, payroll run, and more must be reflected because your general ledger must be comprehensive.  Yet despite being the essential system underlying accounting and finance, it offers only CRUD-like primitives. Its interface for mutation is to create, update, or delete (yes, delete) transactions which contain credits &amp; debits that increase and decrease the various account balances.</p><p>This CRUD interface and the lack of higher-order primitives prevent the work from evolving.</p><p>Consider an example: a refund to a customer. Imagine we are a software company and our application has gone offline for multiple days, so we issue a refund for part of our customer&#8217;s contract. How do we account for this situation? The naive solution would be to directly reduce the amount of revenue we&#8217;ve earned in tandem with the reduction in funds. A simple &#8220;insert&#8221; operation introduces a transaction which reduces the revenue account (and our bank account accordingly).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8qOH!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8qOH!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 424w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 848w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 1272w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8qOH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png" width="481" height="304.44904214559386" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:826,&quot;width&quot;:1305,&quot;resizeWidth&quot;:481,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8qOH!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 424w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 848w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.png 1272w, https://substackcdn.com/image/fetch/$s_!8qOH!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F085d9dc0-63ff-4286-a858-b0e223c93231_1305x826.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><figcaption class="image-caption">An oversimplified depiction of accounting for a refund.</figcaption></figure></div><p>Things get more complex, however, when you consider that the revenue may need to be reduced <em>proportional </em>to the various products that make up the customer&#8217;s overall contract in order to maintain visibility of revenue earned by product line. Additionally, the original records should have metadata to associate them to the source object from Salesforce&#8212;will the negating refund record share the same metadata? The refund might also be a credit against a future invoice rather than a direct cash transfer; did we associate the loss of revenue to the appropriate source? This business logic isn&#8217;t reproduced via the naive approach, and so the need for some higher level operation is evident.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cyCf!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cyCf!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 424w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 848w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 1272w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cyCf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png" width="513" height="342" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:800,&quot;width&quot;:1200,&quot;resizeWidth&quot;:513,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!cyCf!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 424w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 848w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.png 1272w, https://substackcdn.com/image/fetch/$s_!cyCf!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5b612d2c-ce11-42ef-bb9e-d6f951d4345a_1200x800.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><figcaption class="image-caption">A slightly less simplified representation of a revenue transaction. Refund not pictured.</figcaption></figure></div><p></p><p><strong>The role of the spreadsheet</strong></p><p>The state and logic to handle at least some of these complexities must nonetheless live somewhere, and that place is the spreadsheet. <a href="https://numeric.substack.com/p/accountings-data-problem">Excel is the only available tool for an accountant to define higher-level abstractions so it&#8217;s what they use</a>, but it cannot offer the degree of robustness and abstraction necessary for accounting to apply nuanced logic consistently nor to support growing complexity reliably. The spreadsheets are the logical equivalent of a script containing CRUD-like database queries; they help to ameliorate the immediate situation but have a very low ceiling for progress.</p><p>In some cases, point solutions have emerged to take the place of the spreadsheet; these softwares help to address the logic required to manage a slice of your accounting. They nonetheless lack the robustness of an integrated system, meaning data integrity, traceability, and testability are lacking. They&#8217;re glorified spreadsheets, and call for the same level of double-checking and manual review as our ad-hoc spreadsheets require. In some ways, they exacerbate the problem by sprawling the number of systems to be managed.</p><p>Ultimately, accounting departments are left responsible for resolving the activities of the business into CRUD mutations of financial data. They&#8217;re quite good at it too; some accountants are downright experts in reasoning about their data and the impact of changes to the resulting financial reports. The cost, however, is immense. Hundreds of hours are spent on post-hoc review.<em> </em>Unsurprisingly, these ad-hoc behaviors produce a steady leak of errors and subtle gaps in the information. As a consequence, a great deal&#8212;even the majority&#8212;of the labor consists of checking and reconciling the outputs for correctness.</p><p>This isn&#8217;t how software engineers work. We assume manual work begets errors and bad data. We build our systems <em>ahead</em> of the data, testing and verifying behavior prior to deployment, and then allow the code to encapsulate logic and execute state updates. The other direction&#8212;modifying data and verifying it after the fact&#8212;would seem crazy to us today.</p><p><strong>The transformation ahead</strong></p><p>The office of the CFO is facing a transformation.</p><p>The field of accounting is losing people. The work to keep up is draining, even existentially so&#8212;I believe many accountants have a gnawing sense that the current approach is <em>not scaling.</em> Like engineers, they feel a great responsibility to do things right while also wishing to build for the future. But they&#8217;re only playing catch-up and falling behind. To break out of this situation means not only to reduce the pain for those involved but also to raise the bar for transparency, clarity, and strategy in companies.</p><p>By evolving towards a better system for abstraction and automation, we&#8217;ll open the floodgates for the field to advance. The relationship of accountants to financial data should shift, and a new role will emerge: an engineer for accounting. They&#8217;ll adopt a function similar to that of software developers with application databases. Instead of directly manipulating records, they&#8217;ll design, test, and execute the software system for financial data.  This shift is what will unlock a new tier of execution, trust, and speed for business.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[10 Things I Learned as a SWE Intern at Numeric]]></title><description><![CDATA[In school, you're taught to solve problems in isolation.]]></description><link>https://numeric.substack.com/p/10-things-i-learned-as-a-swe-intern</link><guid isPermaLink="false">https://numeric.substack.com/p/10-things-i-learned-as-a-swe-intern</guid><dc:creator><![CDATA[Rhea Malik]]></dc:creator><pubDate>Tue, 26 Aug 2025 15:32:01 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/b59ada38-83c4-4674-aac8-512cc863eec9_4284x5712.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In school, you're taught to solve problems in isolation. But you're rarely taught to think about the broader context of the problem - how your code will scale, how it aligns with long-term goals, or what it means for someone else to pick it up cold. At Numeric, I quickly learned that solving the problem is just the start.</p><p>I remember one PR I made to fix a small bug &#8211; fewer than ten lines of code. I submitted it thinking it would be a quick review. Instead, I got a long, thoughtful review challenging if the fix addressed the reason the bug was created in the first place. The bug wasn't just a bad line of code, it was rooted in deeper assumptions about our data; the real solution meant addressing those, not just patching the symptom.</p><p>That was when I realized that good code isn't just about correctness. It's about clarity. It's about the future engineer who will come in with zero context and still need to understand, debug, and extend your work. This mindset has changed how I approach every line of code since.</p><p>That learning was one of many I took away while interning at Numeric, where I spent my summer helping build out our core platform infrastructure - everything from data pipeline optimization and API integrations to workflow automation and system monitoring. Here are ten lessons from the internship that changed how I think about engineering, startups, and life.<br><br><strong>1. Conviction &gt; Perfection</strong><br>When you're new to a company or to a career, it's easy to default to listening. Just trying to absorb as much as you can and not get in the way.</p><p>But something our CTO, Bihl, told me early on really stuck: people want to follow conviction. A clear, directional opinion, even if imperfect, creates momentum. It gives the team something to respond to, iterate on, or rally around.</p><p>This pushed me to stop hedging and start forming stronger points of view. It's something I'm still working on but now I try to show up with an opinion. It's not always right, but it always moves the conversation forward. And that's the fastest way to grow.<br><br><strong>2. Building with High Stakes</strong><br>When you're dealing with core accounting infrastructure, the stakes are higher than typical product code. A bug doesn't just mean something looks off, it can mean data inconsistencies, failed integrations, or breaking critical customer workflows.</p><p>As a result, I adopted a much more defensive style of programming. I learned to think harder about edge cases, even the ones that 'should never happen.' It's better to hard-fail early and clearly than to silently continue in a broken state. So I started to think: What happens if this API call fails halfway through? What if this endpoint gets hit twice?</p><p>I remember working on our data synchronization flows and realizing how important idempotency was. One thing that we simply could not afford to do was duplicate or corrupt customer data. Whether it was a network issue or a system failure, we had to design around that, tracking every operation and ensuring the system behaved correctly no matter how many times it was triggered. With this, I learned to build systems that anticipate mistakes before they happen.<br><br><strong>3. Prioritization is Hard</strong><br>Once we launched new platform features, feedback came flooding in. Suddenly there were endless feature requests and support tickets:</p><ul><li><p>Can I customize this workflow?</p></li><li><p>Why isn't this data syncing properly?</p></li><li><p>Can I get better visibility into this process?</p></li></ul><p>As someone who was just a few weeks into my internship, figuring out where to start was hard. I learned to group issues into buckets. What's blocking customers right now? What can we work around for the moment? What's a nice-to-have vs. a need-to-have?It's something I'm still learning. But the key was that not everything that seemed urgent was actually a priority. Learning that distinction is the difference between shipping what matters and drowning in distractions.<br><br><strong>4. Testing is a Lifesaver</strong><br>On my first day, I was handed a set of failing tests as my first venture into the codebase. I was confused - how would writing tests help me onboard or even benefit anyone? They just seemed tedious.</p><p>Looking back, I realize how powerful testing really is. When you're joining a new project, seeing exactly what the desired behavior is for each event helps you understand what each part of the system is responsible for.</p><p>And testing isn't just an onboarding tool. They're also a safety net for the future. When we refactored our data processing pipeline, failing tests pointed us exactly to the areas of code that needed attention. The same thing happened when I was working on getting our system to support new integration patterns. The tests gave me a clear starting point for rolling out the changes and understanding their impact on existing codepaths we knew we needed to support. Once we had passing tests, I felt much more confident in our ability to ship this code to production without breaking existing workflows.<br><br><strong>5. Start with the Hardest Thing First</strong><br>De-risking early sounds obvious, until you're looking right at the messiest, least defined part of the project. It's tempting to push that to later, in hopes that it'll somehow become more clear down the line.</p><p>But the hardest part with the most uncertainty is usually where the real complexity lies. For my platform work, it was the core data transformation logic. That came first, then the external integrations, and finally the more predictable parts like monitoring and alerting.</p><p>Starting with the hardest things meant fewer surprises, and more confidence in everything that followed.<br><br><strong>6. Narrative Matters</strong><br>One thing I didn't expect to learn as an engineer: storytelling. At Numeric, the best leaders didn't just do good work or build good systems, but they also told the story behind the work.</p><p>Whether it was a spec, a demo, or a company update, they started with the bigger picture:</p><ul><li><p>What problem are we solving?</p></li><li><p>What's been tried before?</p></li><li><p>What does this change for the rest of the company?</p></li></ul><p>That kind of framing makes decisions easier to follow, feedback more grounded, and discussions more productive. I got to put this into practice by leading demos of features I built to our entire company, even those with little to no context of what I'd been working on. By contextualizing with our current processes and highlighting the clear problems being solved, it made the results more tangible to everyone. Storytelling is not just a skill for GTM, it's a core engineering (and even life) skill.<br><br><strong>7. Talk to Everyone</strong><br>I made it a goal to talk to as many people across the company as I could, and those conversations became some of the moments I learned most in:</p><ul><li><p>A session with Albert walking through a Gong sales call together and dissecting the dynamics of the conversation</p></li><li><p>A chat with Ben Baker about how understanding yourself well allows you to motivate others more effectively</p></li><li><p>And my favorite - when I asked Eli if I could shadow a customer onboarding, which somehow turned into me leading one (and going to a customer onsite!)</p></li></ul><p>Being in the driver's seat with a real customer taught me how to think on my feet, communicate clearly, and focus on what actually matters to the user.This reminded me that some of the most valuable learning happens in simple conversation. Ask questions. Be curious. The people doing the actual work are the best teachers, and all you have to do is start the conversation.<br><br><strong>8. Timing is Important</strong><br>I joined Numeric at a rare moment &#8212; right as the company was starting to scale. In just a few months, we went from a handful of engineers to growing so quickly we started to split into pods working on different areas of the product. The sales process was becoming more standardized. A brand new product team took shape. Processes that didn't exist when I started became essential.</p><p>Being in the middle of that kind of change taught me how timing shapes the problems you work on. In the early days, everyone touches everything, and when you scale, you have to start building systems and processes that help people focus on where they're most effective. I was lucky to experience that shift firsthand.<br><br><strong>9. Leadership Sets the Tone</strong><br>One thing that stood out at Numeric wasn't just how talented people were, but how much trust they had in each other. People owned their domains and made decisions confidently.</p><p>Leadership set a tone of clarity, speed, and empowerment. That kind of trust-based culture changes how you work. It pushes you to take ownership, move fast, and level up quickly.</p><p>This summer, when I was told I'd be working on our core accounting infrastructure, I felt both trusted and challenged. That trust was motivating.<br><br><strong>10. San Francisco is the Place to Be</strong><br>I've been here for 13 weeks, and it's hard to imagine a better place to be for this kind of learning and energy.</p><p>A few highlights outside of work that wouldn't have been possible anywhere else:</p><ul><li><p>Rode in a self-driving car</p></li><li><p>Ran the SF 10K (with no running experience before the summer)</p></li><li><p>Met so many inspiring people, including Joe Lonsdale (co-founder of Palantir, 8VC)</p></li></ul><p>There's a magic to being in a place where everyone's building.<br>This summer taught me more than I could have imagined: not just about code or systems, but about learning, ownership, and building for the future.<br><br>Huge thanks to Bihl, Owen, Ben, and the entire team at Numeric for making it an incredible experience.</p>]]></content:encoded></item><item><title><![CDATA[Righting Integration Testing's Wrongs with PGLite]]></title><description><![CDATA[Best explained with an example from The Fairly OddParents.]]></description><link>https://numeric.substack.com/p/righting-integration-testings-wrongs</link><guid isPermaLink="false">https://numeric.substack.com/p/righting-integration-testings-wrongs</guid><dc:creator><![CDATA[Nick Dupoux]]></dc:creator><pubDate>Wed, 07 May 2025 16:30:34 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mbrb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The engineering team at Numeric is continuously experimenting with ways to increase our development velocity and the overall quality of our codebase. One recent such experimentation was in the sometimes unwieldy arena of integration testing. It's proven successful enough to enshrine in a blog post.</p><p>We'll walk through our development philosophies concerning the why and the when to integration test, briefly discuss where we write them, and present the method of how to integration test that we've developed. We'll wrap up with a conversation on key considerations that must be kept in mind when following our testing methodology, and a deeper presentation of the test fixture we've developed for integration testing Postgres systems. It's all powered by the the embeddable Postgres implementation <a href="https://pglite.dev/">PGLite</a>; these experiments have been a great way to learn and interact with the tool, and we're impressed!</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>Why</h2><p>We consider a rich automated testing suite a key component of our efforts to build developer confidence. With increased confidence, we can attain greater speed, free of any hesitation to kick off a scary deploy. Engineers should feel empowered to merge code, fire off a deploy, and move on to the next PR. Good automated tests also provide us increased confidence in code quality<em>;</em> they codify what expectations we have for our software's behavior, guarding us from regressions and adding additional layers of validation for new feature releases.</p><p>Integration tests are one pillar of such a confidence-boosting test suite. For our purposes, they are differentiated from unit tests in that they aim to verify the correctness of full systems, rather than isolated functions or components. In particular, they test systems in which multiple components interact to produce results. Complexity explodes at these points of interaction, so one hundred percent verification is not the goal. Instead, through a solid set of integration tests, we aim for coverage of the invariants we expect to always hold.</p><p>No presentation of integration tests is complete, of course, without consideration of the canonical aches, fears, and worries their mention can trigger in a seasoned engineer. Because of their very nature, they can be difficult to write, expensive to operate, slow to run, hard to maintain, and prone to flakiness and test pollution. We hope that the demonstration that follows will show that this state of operation is not a fait accompli; it's very, very possible to introduce integration testing into a codebase without incurring these pains.</p><h2>When</h2><p>Our canonical target for integration test coverage is a data in/data out system, which you could argue, is every computing system&#8230; anyway, many times this is a REST API: the data in (HTTP request) triggers some operation (application logic) that produces data out (data in a database, data in the response, etc). To produce the data, the API may interact with multiple components: data stores, cache clients, data transformers, domain calculations, etc. Even more of a clear match, data ETL or ELT processes pull data from some source store and dump it into some destination.</p><p>So, when we decide whether to integration test, we look at our code and ask ourselves: What is the data in? What is the data out? Once identified, you can probably integration test. With the right fixtures in place for preparing the data in and inspecting the data out, of course.</p><h2>Where</h2><p>We write our integration tests like any other <a href="https://vitest.dev/">vitest</a> script. We place tests in a file entitled <code>&lt;component&gt;.integration.test.ts</code>, where <code>component</code> is the target system under test; for example, <code>accounts-data-transform.integration.test.ts</code> would hold integration tests for some <code>accounts-data-transform</code> system. It should live next to the entrypoint of the system under test.</p><p>CI runs are triggered on PRs, where integration tests and unit tests execute in parallel via separate vitest workspaces. This allows us to isolate the generally quicker unit tests from the generally slower integration tests.</p><h2>How</h2><p>So we've created an integration test file for our target system. Nice. What now?</p><p>We have the plumbing set up today to support integration testing anything that writes result data to Postgres, the main database in our tech stack. Systems that return data via API responses should also theoretically be integration testable, but we haven't yet written those. So, we'll walk through the first scenario, and leave writing an integration test for the latter as an exercise for the reader.</p><p>Our goal is to verify the correctness of a simple ETL pipeline that pulls data about ticket sales at the world's finest, fictitious sporting venue, the Dimmsdale Dimmadome , from the equally fictitious "DimmaAPI" (special thanks to <a href="https://www.youtube.com/watch?v=A4AjQ7eprN4">Doug Dimmadome</a> for access to this incredibly useful dataset). Once data is extracted, the pipeline applies some calculations on it, and ultimately dumps the results into tables in our internal Postgres database.</p><p>We test such Postgres sink systems following a simple pattern: mock data with in memory providers (also known as <a href="https://microsoft.github.io/code-with-engineering-playbook/automated-testing/unit-testing/mocking/#fakes">fakes</a>) and an in memory DB client, run the system under test, then verify what data was written out via the same in memory DB client. In detail:</p><ol><li><p>Source data is provided through the creation of <strong>in memory data providers</strong>. Naturally, this requires some foresight at the start of development (or after the fact with some refactoring) to separate the source of data from the code that acts on the data. We put that source behind an interface that can then be plugged and played with different implementations. This is generally a good design pattern to follow, so it's worth promoting beyond the benefits of integration testing.[1]</p></li><li><p>The in memory providers should expose an easy way to configure the test data to input into the system, and a way for state to be reset between tests. For example, consider this <code>DimmadomeInMemoryDataProvider</code>. It implements a <code>IDimmadomeDataProvider</code> interface. The interface abstracts away where the data comes from. In the real system, we have an implementation that pulls data from the Dimmadome Data API. Let's define the in memory provider and instantiate an instance to use in our test:</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mbrb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mbrb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 424w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 848w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mbrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png" width="1456" height="1442" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1442,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:233602,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!mbrb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 424w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 848w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.png 1272w, https://substackcdn.com/image/fetch/$s_!mbrb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc9f3dba8-6b75-4aac-aa40-8c8aa550de73_1616x1600.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><ol start="3"><li><p>Source data may also come from Postgres itself as a target. We thus introduce the pi&#232;ce de r&#233;sistance of our integration testing capabilities: a <a href="https://pglite.dev/">PGLite</a> powered in memory db client. PGLite is an embeddable Postgres client. It's lightweight enough to instantiate/tear down quickly, which helps mitigate many of the historic pains of writing and maintaining integration tests. For one, we aren't reliant on spinning up a separate, heavier Postgres process. And because we can afford to reinstantiate new PGLite instances between test runs, we eliminate the potential for difficult-to-debug failures due to data pollution between tests. For our purposes, we've built a testing client using it that is compatible for use with <a href="https://github.com/gajus/slonik">Slonik</a>, our Postgres query library of choice. Our test client exposes methods to execute queries. We use these to configure initial tables and data for use by systems under test. Our Dimmadome ETL depends on some metadata tables, so let's create our test client and get those configured.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DihE!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DihE!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 424w, https://substackcdn.com/image/fetch/$s_!DihE!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 848w, https://substackcdn.com/image/fetch/$s_!DihE!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 1272w, https://substackcdn.com/image/fetch/$s_!DihE!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DihE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png" width="1456" height="542" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/03bca034-3902-401e-a430-8781437016bd_1498x558.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:542,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:114212,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.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_!DihE!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 424w, https://substackcdn.com/image/fetch/$s_!DihE!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 848w, https://substackcdn.com/image/fetch/$s_!DihE!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.png 1272w, https://substackcdn.com/image/fetch/$s_!DihE!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F03bca034-3902-401e-a430-8781437016bd_1498x558.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_!6HbM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6HbM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 424w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 848w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 1272w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6HbM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png" width="1456" height="889" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:889,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:175096,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.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_!6HbM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 424w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 848w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.png 1272w, https://substackcdn.com/image/fetch/$s_!6HbM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F22c8c2a1-8789-4090-bc8c-13f868848761_1464x894.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><ol start="4"><li><p>Once test data sources have been set up, we can execute our target system. It should perform its magic and ultimately conclude with data written into the in memory test client.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LOAx!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LOAx!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 424w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 848w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LOAx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png" width="1456" height="1028" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1028,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:222380,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.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_!LOAx!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 424w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 848w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!LOAx!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4419504f-5fdc-4808-8923-4b9cf055a23c_1530x1080.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><ol start="5"><li><p>Now we can use the client to inspect the data written. How? Through good old SQL queries. Additionally, the client provides assertion methods that we use to inspect what queries were executed. This is useful for asserting things like "when we see data matching situation X, we should NOT run a query to do Y". We've already asserted that our ETL process returns a success response. Let's run some more assertions to verify it wrote the data we expect.</p></li></ol><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mMvS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mMvS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 424w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 848w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 1272w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mMvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png" width="1456" height="1588" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1588,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:504122,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.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_!mMvS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 424w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 848w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.png 1272w, https://substackcdn.com/image/fetch/$s_!mMvS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8fc9407b-29ef-4a14-be3a-617eaa358820_2048x2234.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><ol start="6"><li><p>That's it! We've successfully written an integration test that confirms a particular configuration of data into the system results in data out of it as we expect.</p></li></ol><p>Here's our final test in full.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!beo5!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!beo5!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 424w, https://substackcdn.com/image/fetch/$s_!beo5!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 848w, https://substackcdn.com/image/fetch/$s_!beo5!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 1272w, https://substackcdn.com/image/fetch/$s_!beo5!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!beo5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png" width="1456" height="2541" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2541,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:657210,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.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_!beo5!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 424w, https://substackcdn.com/image/fetch/$s_!beo5!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 848w, https://substackcdn.com/image/fetch/$s_!beo5!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.png 1272w, https://substackcdn.com/image/fetch/$s_!beo5!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F46fd507b-dceb-4d46-9f3a-b967d278dfed_2048x3574.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><h2>To what extent</h2><p>Now that we've walked through an example of integration testing in action, it's worth taking a moment to consider a delicate balancing act our procedure asks of test writers: to what extent should your in memory providers emulate the behaviors of the real data APIs used in the production system under test? In other words, how complex should their implementations be?</p><p>Writing in memory providers for use in mocking involves&#8230; writing code. And as is the case with any code, the more of it that exists, the greater the surface area is for bugs and disaster. Ideally, the in memory providers require little code, and what code is written is straightforward CS101 stuff. This was the case for mocking the DimmaAPI. But this may not always be the case. There are rougher paths that one might be led down, asking you to mirror more complex data operations or logic. Consider some source data API that exposes a <code>findKMeansNearestNeighbors</code> endpoint. At face value, it seems that it asks us to fully implement k-means clustering in our in memory provider to match its behavior. If we do follow that path, then what are we testing? We <em>want</em> to say the system as it actually exists, but past a threshold of sufficient complexity, the mocking code itself is what's under test.</p><p>As the test writer, it is your responsibility to consider the complexity of your in-memory providers &#8211; of your mocking utilities &#8211; and to find alternatives when possible to keep it contained. With the <code>findKMeansNearestNeighbors</code> example, do we need to provide the <em>real</em> k nearest? For many tasks downstream of it, we probably care about <em>what is done</em> with the results pulled from the data source, not the nature of the results themselves. For example, if the data source declares records X, Y, and Z the three closest neighbors of record A, do the downstream processes treat them as such? Viewed from this lens, we arrive at a straightforward implementation of <code>findKMeansNearestNeighbors</code>&#8230; simply return any three arbitrary (but deterministic!) records. Fin.</p><p>These considerations are more art than science. And are heavily context-dependent. Ergo, the balancing act.</p><p>It's also worth considering why we implement these in memory providers rather than using more traditional mocking libraries or frameworks. Our dominant motivation is to avoid complexity &#8211;another reason why the balancing act we discussed is so important! In our experience, mocking frameworks carry footguns that can make test code hard to understand. We consider this the classic "with great power comes great responsibility" issue. Full-fledged mocking frameworks offer a plethora of powerful tools for testing. Spys! Mocks! Stubs! We've found, though, that our tests are simpler with directly written in memory providers that provide exactly what you need, nothing more. As a bonus, they can be extended with custom assertion or verification utilities directly tailored for your use case in similar ways to the assertion methods we demonstrated on the PGLite test client.</p><h2>The client in detail</h2><p>We'll now present some of the inner workings of the test client; namely, its constructor (which initializes the PGLite in memory database), internal data structures for tracking executed queries, and its query interface.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wo2U!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wo2U!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 424w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 848w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 1272w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wo2U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png" width="1456" height="1853" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/ed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1853,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:638551,&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://numeric.substack.com/i/162995784?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.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_!wo2U!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 424w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 848w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.png 1272w, https://substackcdn.com/image/fetch/$s_!wo2U!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fed0582f1-4374-41ef-9ab3-749301a7591f_2048x2606.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>All of the heavy lifting of executing queries is passed on to PGLite. The test client serves as a simple wrapper that adds in the special sauce of tracking what queries have been executed<em>.</em> This is what allows us to provide richer assertion functionality beyond just inspection of table content. For now, these queries are normalized (whitespace stripped) and stored as strings in the query log. We have plans to more richly parse queries into ASTs, which can be interrogated with more complex questions like "did an update statement execute against table X settings column A to value W?"</p><h2>Conclusion</h2><p>We've benefited tremendously from this method of integration testing, in no small part due to the low barrier of entry to writing and executing them. There's no dependency on Postgres running in your local environment or in some container in our CI environment. It's all in memory. The tests are easy to write, cheap to run, cheap to reset. Which contrasts beautifully with the canonical worries surrounding integration testing we presented at the start of this article. With positive experiences under our belts, we at Numeric welcome the new era of in-memory, embedded Postgres.</p><p>---</p><p>[1]: We generally consider it nice to think of any data manipulating code in this way, for the benefit of readability and, especially, for testing. Separate the work of getting data from the work of actioning on the data to promote testability. This also produces code that adheres to the pure functions style standard.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Accounting’s Data Problem]]></title><description><![CDATA[&#8230;if you ask what the team needs, they may say more accountants.]]></description><link>https://numeric.substack.com/p/accountings-data-problem</link><guid isPermaLink="false">https://numeric.substack.com/p/accountings-data-problem</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Thu, 24 Apr 2025 19:01:21 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/3f839824-173a-4e59-9135-851a7d6ab9c7_1700x1263.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote><p><em>&#8230;if you ask what the team needs, they may say more accountants. At Numeric, we&#8217;d say they need a proper data platform.</em></p></blockquote><p>Why work on accounting? It&#8217;s a common question&#8212;one that we get all the time. We&#8217;ve built a product-centric company with a strong engineering culture, and accounting is not typically thought of as a domain which attracts top minds for solving problems in software. Yet we remain laser-focused on accounting.</p><p>In speaking with other engineers, an important part of explaining what we do is setting the baseline for what the state of play is today: the work of accounting is substantially, frustratingly, unsolved and manual even though every company has to do it. What you might assume is fully automated often isn&#8217;t. Here's a simple example:</p><p>A software company &#8220;Todo List Apps, Inc.&#8221;<em> </em>signs a 2 year contract in January with AWS for $240k worth of reserved EC2 instance capacity. It&#8217;s a win-win: TLA saves 40% on their compute costs, and AWS gets the full cash amount paid upfront.</p><p>From an accounting standpoint, it would be wrong to say &#8220;we burned a great deal of money in January but now we&#8217;re profitable&#8221;. The goal is to approximate the profit function of the business, and to be able to answer: if I put a dollar into this business, will more than a dollar come out? So we must normalize for time. In the case of this expense we&#8217;ve paid upfront for, the logic is fairly simple. Instead of saying we spent $240k in January, we consider that we&#8217;ve converted one asset (cash) into another asset (a pile of service owed to us). We then &#8220;spend&#8221; this asset evenly over the course of 2 years, using about $10k worth of EC2 credits a month.</p><p>The actual math here is dead simple: take an amount, and spread it out evenly over the length of the contract. There are corners, sure, but addressing things like partial start/end months or a leap year are fairly everyday problems in programming. In spite of this, automation plays only a small role in the way companies of <em>all</em> sizes solve this today (spoiler: with manual work and Excel). Here&#8217;s what it looks like.</p><p>At month-end, Angela on the accounting team has a task to search their accounting software for large expenses from the month to see if any look like they are <em>not</em> one-off expenses. Until treated otherwise, the system will assume they are. When she finds one for $240k, she knows it&#8217;s likely a contract for a year or more, not just for January. She logs into a tool like Bill.com, or Ramp, or Brex, or Zip, or otherwise to find the original contract. She downloads the PDF.</p><p>She opens up the <em>Prepaid Software Expenses</em> Excel file that her team shares and keys in the details of the contract to one tab&#8212;the vendor, the start of the service, the end, the total amount, the department that incurred the expense, and more. Then, there is another tab to project the change in the asset&#8217;s value over time. That tab contains a formula that looks something like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IEp6!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IEp6!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 424w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 848w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 1272w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IEp6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png" width="1456" height="157" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:157,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!IEp6!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 424w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 848w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 1272w, https://substackcdn.com/image/fetch/$s_!IEp6!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F29420e73-8153-4816-9207-d0a733aabe7c_1600x172.png 1456w" sizes="100vw" fetchpriority="high"></picture><div></div></div></a></figure></div><p>This formula drives the math for how they amortize, producing the change in each prepaid software contract per month. It will have hundreds or thousands of contracts represented (both present and past).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!pNRb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!pNRb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 424w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 848w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 1272w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!pNRb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png" width="1080" height="532" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:532,&quot;width&quot;:1080,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!pNRb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 424w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 848w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.png 1272w, https://substackcdn.com/image/fetch/$s_!pNRb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb9d71cd3-357c-4162-81ed-edf4e0b6a3d4_1080x532.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>She notices the row hasn&#8217;t been picked up and realizes she needs to click and drag a cell down one row to get it to include the newest row in the calculations. Once she&#8217;s done that, she goes to the &#8220;Export&#8221; tab which adds up all the deltas across every contract to produce a single number for the month: $117,320.29, representing the total usage of paid-upfront software services at the company. Later, once all such contracts have been entered, she&#8217;ll export the CSV to then import into their accounting software (e.g. Quickbooks or NetSuite). This simplified, aggregate number represents the ending result of this process. If something new comes in, she&#8217;ll delete the transaction, fetch the contract, update the spreadsheet, check the amortization, and upload a new CSV with the latest total. And if there is an error, it&#8217;s likely to burn days of her time as she hunts down the cause through the gargantuan file.</p><p>In the absence of a better system, these types of Excel files serve in place of a full software stack. In this instance, the file is the database for this contract&#8217;s structured data. It serves as the business logic and handles transformation of the values. It serves as an integration, producing a CSV for export. Teams will have various methods for version control of the file and some light checks for correctness. Unfortunately, this creates immense manual work and innumerable errors due to subtle mistakes, lack of tests, denormalized data, and out-of-sync state.</p><p>This tedium is not just performed for prepaid software. Next, Angela will move on to the file titled &#8220;Lease Schedules&#8221;, which contains the upfront paid asset representing their right to their office spaces, and the amortization math to use the space over the length of the lease. Physical assets, like their employee laptops, will also depreciate in a remarkably predictable fashion, yet those spreadsheets will require a manual export each month as well. Their revenue may look similar&#8212;just imagine the EC2 scenario from AWS&#8217;s point of view. If they have loans with banks, what they really have is a lump sum obligation and a <em>very</em> predictable schedule on which it will be amortized to zero. And so on and so forth.</p><p>Toward the end of all this, error checks and cross-referencing are done by the team to spot mistakes and missing information. When transactions are erroneous or incomplete, the cost to address the issues will heavily depend on how quickly (Days? Weeks? Months? Years?) they identified it. Meanwhile, the CFO, the CTO, the finance team, budget holders, and possibly investors are awaiting the results because the output of this careful dance is the financial data of the business. There are decisions, adjustments, and insights to be gleaned from this data.</p><p>This is a universal, though hardly comprehensive, story about how accounting is done in companies big and small. Manual work is immense, and accountants spend the majority of their working lives collecting information, moving data, running calculations, and uploading CSVs. Spreadsheets power this world. It&#8217;s a lot of data work, and the accounting role can be a draining job in a constant state of catch-up&#8212;most acutely observed during the month-end close.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bJJY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bJJY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 424w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 848w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 1272w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bJJY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png" width="850" height="483" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:483,&quot;width&quot;:850,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:56110,&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://numeric.substack.com/i/162011456?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F80b1ffe8-035d-4183-8840-ef20bbccc3fc_850x570.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_!bJJY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 424w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 848w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.png 1272w, https://substackcdn.com/image/fetch/$s_!bJJY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd4665f97-080c-4064-94f1-f0e4d08fef81_850x483.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><figcaption class="image-caption"><a href="https://www.reddit.com/r/Accounting/comments/1ew1sk3/accountants_what_do_you_do_when_its_not_monthend/">Reddit: &#8220;Accountants, what do you do when it&#8217;s not month-end close?&#8221;</a></figcaption></figure></div><p>If you ask the team, they may say their hardest problem is hiring&#8212;the field of accounting, after all, is not growing. In fact, the talent pool hasn&#8217;t kept pace while challenges have mounted. As the volume of data, the number of systems of information, and the scale of the business increase, things inevitably become more complex. Part of this growing pain is due to the explosion of data and systems since the turn of the millennium (which their accounting software predates), and another part stems from the expansion of regulations like Sarbanes-Oxley (2002) and the Accounting Standards Codification (2008), both created in response to catastrophic fraud and error. The job for many accountants is unrewarding and the field can be a difficult one to enter, leading to the state of under-supply today.</p><p>So if you ask what the team needs, they may say more accountants. At Numeric, we&#8217;d say they need a proper data platform.</p><p>Our direction as a company has been heavily informed by a few strong beliefs. First, when you have manual, repeated work, errors are inevitably entering the system and creating bad data. Second, if you have a data problem, it&#8217;s usually a bad idea to try and patch said problem downstream. Such issues proliferate in a way that can be exponentially painful, and building on top of bad data leads to predictable outcomes. If you can fix it at the source, you should. Third, accounting is sitting on a massive data problem and the situation cries out for good engineering and product.</p><p>We believe the future of accounting is one in which we&#8217;ve been able to effectively lift everyone&#8217;s role one or two layers of abstraction up from where it is today. The integration, movement, and matching of data should be done by computers. The math, projection, data pipelining, simple analysis, and error detection should be done by computers. Even the interpretation of the events, and the first take on the correct accounting treatment of things can now be done with computers. The current state is one in which the team building and owning the dataset&#8212;the accounting department&#8212;is so busy and undersupported that they are always playing catch-up. Meanwhile, leadership and decision-makers only gain access to a fairly shallow data set, limited in its usefulness because its accuracy is questionable, its depth is limited, and the tools available to use it are disappointing.</p><p>By freeing people from moving data, manually error checking, spot-testing Excel files, and more, we can raise the ceiling for how good outcomes can be. Getting the information and producing trustworthy accounting books should be just the first step toward an overall operation that uses the financial data of the business to drive strategy. These problems are holding back companies at large. They make life hell for accountants. And they are solvable. That&#8217;s why we&#8217;re building Numeric.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[How we doubled Zod performance to speed up Typescript validation]]></title><description><![CDATA[In Zod we trust.]]></description><link>https://numeric.substack.com/p/how-we-doubled-zod-performance-to</link><guid isPermaLink="false">https://numeric.substack.com/p/how-we-doubled-zod-performance-to</guid><dc:creator><![CDATA[Justin Chang]]></dc:creator><pubDate>Wed, 12 Mar 2025 21:10:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At Numeric, we rely heavily on <a href="https://github.com/colinhacks/zod">Zod</a> for runtime type validation in our TypeScript stack. While Zod has served us well, we ran into some hefty performance issues when processing large datasets. To address this, we <a href="https://github.com/numeric-io/zod">forked</a> Zod, applied targeted optimizations, and achieved a <strong>2x improvement</strong> in validation speed. Here&#8217;s what we did.</p><h2><strong>Why Runtime Type Checking Matters in TypeScript</strong></h2><p>TypeScript&#8217;s static type system is effective, but runtime validation remains necessary when dealing with external data sources. Without runtime checks, compile-time types for external data are meaningless. They create a false sense of security that lets subtle data inconsistencies spread through your system.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h2>What is Zod?</h2><p>Zod is a TypeScript-first validation library that ensures runtime data matches expected types. Developers define schemas that describe the expected structure and constraints of data, and Zod then validates inputs against those schemas. It also eliminates redundant type declarations by inferring static TypeScript types directly from the schemas.</p><p>Beyond type checking, Zod offers features such as:</p><ul><li><p>Transforms: Modify data while validating it</p></li><li><p>Pipes: Chain multiple validation steps</p></li><li><p>Coercion: Convert values (e.g., strings to numbers) during validation</p></li></ul><p>While these features enhance developer experience, they introduce performance overhead and require a validation approach that can impact scalability.</p><p>Our primary use cases for Zod include:</p><ul><li><p>Validating database query responses</p></li><li><p>Enforcing API contracts between frontend and backend</p></li><li><p>Verifying data from third-party integrations</p></li></ul><p>Given these use cases, our goal was to enforce strict type checking with minimal processing overhead. We sought surgical optimizations to improve performance while maintaining Zod&#8217;s API for an easy migration path.</p><h2><strong>The Bottleneck: Overhead from Deep Copying</strong></h2><p>By default, Zod validates data immutably&#8212;it returns a new object instead of mutating inputs. While this aligns with functional programming principles, it introduces significant overhead when processing large datasets.</p><h3>Symptoms</h3><p>In one real-world scenario, parsing 500,000 account objects took around 2 seconds using standard Zod validation. The performance overhead caused noticeable event loop delays and, in some cases, server OOM crashes due to excessive memory allocations (as well as overly nested promises when using async function variants).</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!bYcg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!bYcg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 424w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 848w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 1272w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!bYcg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png" width="1456" height="626" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:626,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:104512,&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://numeric.substack.com/i/158946168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.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_!bYcg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 424w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 848w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.png 1272w, https://substackcdn.com/image/fetch/$s_!bYcg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F3ec5942a-456c-4d32-823c-9a33fd6e4653_1824x784.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><em>A visualization of Zod parsing taking the majority of the CPU time it takes to run queries against our databases.</em></p><p>For the sake of reproducibility, here&#8217;s a simplified version of one of the schemas we benchmarked. We&#8217;ll use this naive benchmark of the time to parse an array of 500,000 account objects as a proxy for general library performance and improvements; it&#8217;s simple and easy to reproduce with some fake data. However, during experimentation and implementation, we augmented this with other benchmarks based on our production usage, as well as more <code>default</code> profiling.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ekVB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ekVB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 424w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 848w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 1272w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ekVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png" width="1288" height="640" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c491a480-57d9-42f1-9325-597763539df7_1288x640.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:640,&quot;width&quot;:1288,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:111390,&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://numeric.substack.com/i/158946168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.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_!ekVB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 424w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 848w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 1272w, https://substackcdn.com/image/fetch/$s_!ekVB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc491a480-57d9-42f1-9325-597763539df7_1288x640.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>The situation became dire enough that we introduced a configuration flag to disable type checking for certain high-throughput workloads&#8212;a clear signal that something needed fixing.</p><h2><strong>The Optimization: Bye-Bye, Deep Copies</strong></h2><p>We modified Zod&#8217;s validation logic to <strong>return the original object</strong> when validation succeeds, instead of creating a deep copy. This drastically cut down CPU and memory usage.</p><p>To enable this optimization, we had to remove features that rely on returning a new object, including:</p><ul><li><p><code>catch</code></p></li><li><p><code>coerce</code></p></li><li><p><code>default</code></p></li><li><p><code>intersection/and</code> (in favor of <code>merge</code>)</p></li><li><p><code>pipe</code></p></li><li><p><code>preprocess</code></p></li><li><p><code>transform</code></p></li></ul><p>For many users of the library, these are core features, which is why Zod itself can&#8217;t support something like this with minimal changes. But for us, our system already separates transformation logic from validation, so removing these features was a reasonable trade-off.</p><p>Zod&#8217;s internal functionality must be understood to understand why this optimization can be implemented in an elegant and contained manner. At its core, Zod&#8217;s validation logic is really just casting <code>any</code> to stronger types, with a bunch of checking along the way. The logic is illustrated in this simplified example:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!l9d1!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!l9d1!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 424w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 848w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 1272w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!l9d1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png" width="1280" height="162" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:162,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:21374,&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://numeric.substack.com/i/158946168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.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_!l9d1!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 424w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 848w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 1272w, https://substackcdn.com/image/fetch/$s_!l9d1!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F306a59cb-6e48-4fcd-b656-8d8eace018c2_1280x162.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>As long as the validation function ensures type correctness of the input object, there&#8217;s no strict need to return a cloned object. Eliminating deep copying doesn&#8217;t change how TypeScript will enforce the types of the returned value downstream.</p><p>(Clever readers may interject here that a proper in-place validation function should probably be using the <code>asserts</code> operator. While relying on type narrowing via <code>asserts</code> would be great, the implementation would require much deeper changes in Zod&#8217;s plumbing. We&#8217;re hoping to see something like this strategy come out in Zod 4, perhaps!)</p><h2>Implementation Details: Optimizing <code>_parse</code></h2><p>Our optimization focused on <code>ZodObject#_parse</code>, the function responsible for validating and processing objects against schemas.</p><h3>Key Changes:</h3><p>The macro-optimization here is validating data in place and then returning the input object itself if validation passes. This gives us around a 1.5x speed boost. Getting it up to the titular 2x required a few more micro-optimizations such as:</p><ul><li><p>Use direct key lookups instead of iterating through properties, reducing CPU cycles.</p></li><li><p>Handle excess properties efficiently, stripping or reporting unknown keys without redundant processing.</p></li></ul><p>With these changes, we saw a 2x validation speed improvement across our benchmarks. Check out the forked source code <a href="https://github.com/numeric-io/zod">on Github</a>.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!IyJ0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!IyJ0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 424w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 848w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!IyJ0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png" width="1128" height="1474" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1474,&quot;width&quot;:1128,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:219169,&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://numeric.substack.com/i/158946168?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.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_!IyJ0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 424w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 848w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.png 1272w, https://substackcdn.com/image/fetch/$s_!IyJ0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe6093671-1061-42b4-a8f5-c919f6bfff7c_1128x1474.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><h3>Trade-offs</h3><p>This approach allows us a fairly surgical implementation (besides the removal of some features en masse). However, this comes with a good number of trade-offs that may only make sense for our specific use cases. Otherwise, we would have just PR&#8217;d to the main repository &#128578;.</p><ul><li><p>Directly modifying input data in <code>strip</code> mode: extra keys are removed in place rather than on a cloned object. This is probably the biggest footgun introduced here. Optimally, we&#8217;d default to <code>passthrough</code> mode rather than <code>strip</code> to match Typescript&#8217;s default behavior (without excess property checking), but changing such a default would be a much more in-depth and dangerous change.</p></li><li><p>The input data itself is not type-checked: the returned value must be reassigned to enforce strict typing. Ideally, TypeScript&#8217;s <code>asserts</code> operator would be used, but that, too, would require more intrusive changes. This trade-off is not too bad, given it matches Zod&#8217;s own API. As an aside, a piece of essential reading for this subject matter is Alexis King&#8217;s article &#8220;<a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">Parse, don&#8217;t validate</a>&#8221;.</p></li><li><p><code>ZodIntersection</code> behavior changes: previously, <code>intersection</code> validated both schemas and merged results. With deep copying removed leading to the above <code>strip</code> behavior, <code>intersection</code> can lead to unexpected key deletions. Rather than get too clever here, we instead fully removed <code>intersection</code> with the recommendation to use <code>ZodObject#merge</code>. For our use cases of only ever intersecting object types, the minutiae lost in the differences between <code>merge</code> and <code>intersection</code> are insignificant.</p></li></ul><p>A massive kudos goes to Zod&#8217;s comprehensive test suite; with it, we caught and addressed these edge cases early in our implementation.</p><h3>Rolling Out to Production</h3><p>We tested our fork extensively using internal benchmarks on production servers. The results confirmed a 2x validation speed improvement on large datasets.</p><p>Migration was straightforward&#8212;since our fork is a strict subset of Zod&#8217;s API, the primary change involved replacing our few remaining <code>.transform()</code> calls with a separate transformation step. Other validations continued to work as expected, with dramatically improved performance.</p><h2>Looking Forward: Exploring ArkType</h2><p>While Zod has served us well, we&#8217;re extremely excited about <a href="https://github.com/arktypeio/arktype">ArkType</a>, a newer validation library leveraging JIT compilation. Based on the type definition, ArkType creates a function using the <code>new Function(...)</code> syntax that avoids a lot of the overhead from the deep recursive call stack that belabors libraries like Zod. ArkType consistently outperforms Zod&#8212;even our optimized fork&#8212;by a wide margin. The base Zod library benchmarked at around 40x slower than ArkType on the same set of 500K accounts, and our optimizations brought it down to 20x. ArkType remains the clear winner on speed and memory usage.</p><p>Given these numbers, why haven&#8217;t we switched yet?</p><ol><li><p>Stability: Zod is battle-tested and well-supported. ArkType, while promising, is still maturing.</p></li><li><p>Developer Experience: We love the Zod API and have found ArkType&#8217;s ergonomics lacking in some areas.</p></li><li><p>Clear Use Cases: We adopt new tools when the need is clear and the benefits outweigh the migration effort. ArkType shows promise, but we need further validation before committing.</p></li></ol><p>For now, our optimized Zod fork meets our production needs, but we&#8217;re actively testing ArkType for R&amp;D projects.</p><h2>Final Thoughts</h2><p>In the JavaScript ecosystem, it&#8217;s almost <em>too</em> normalized to use a package for everything. This experience reinforced an important engineering principle: general-purpose tools don&#8217;t always scale perfectly for specific workloads.</p><p>Zod is an excellent validation library, especially for form inputs or simple API contracts, but its default immutability strategy can be a performance bottleneck in high-throughput applications. When we found ourselves working around these constraints, we took a deeper look and modified the tool to fit our needs. By eliminating redundant object allocations, we doubled validation speed while preserving most of Zod&#8217;s stability.</p><h3>Our Engineering Philosophy</h3><p>We believe in the ability to peer into abstractions and modify them when necessary. Libraries should serve our needs&#8212;not the other way around. By understanding and optimizing Zod, we unlocked significant gains without sacrificing maintainability.</p><p>If you&#8217;re running into similar performance issues, take a step back. Rather than blindly accepting defaults, challenge abstractions and consider whether they serve your requirements.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[The Engineer's Onboarding: Advice for New Hires]]></title><description><![CDATA[A set of principles to help deliver value from day one, all while laying the foundation for long-term impact in your new role.]]></description><link>https://numeric.substack.com/p/a-numeric-engineering-leads-pov-on</link><guid isPermaLink="false">https://numeric.substack.com/p/a-numeric-engineering-leads-pov-on</guid><dc:creator><![CDATA[Ben Baker]]></dc:creator><pubDate>Thu, 20 Feb 2025 16:30:38 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/1e285140-550d-4919-9a85-d51bd33a1f21_4294x3220.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>So, you&#8217;ve just joined a new company as an engineer -- exciting! Amidst all the new opportunities, technologies, and challenges ahead, you've also got a small lump in your throat as you prepare for the inevitable learning curve of the onboarding process.</p><p>Most anyone understands the benefits to a successful onboarding: you get a chance to lay the groundwork for long-term success by building strong relationships with your team and gaining a holistic understanding of your new company's systems and culture. That said, for many engineers, onboarding is synonymous with frustration. The pains are numerous &#8211; setting up a new dev environment, setting up multiple new accounts, etc; even pushing a simple pull request can consume many annoying hours.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>Add to that the fact that many companies measure early success by how quickly you can ship code, you may be tempted to skip essential foundational learning for rapid output. In my view, that approach is short-sighted.</p><p>An onboarding period is a terrible thing to waste: the first fateful domino in your tenure at a company. As such, I&#8217;ve outlined a set of principles and practical tips for the optimal ramp-up. With the right mindset and preparation, you can establish a solid foundation for long-term impact.</p><h3><strong>The principles</strong></h3><p><strong>The larger the company (and codebase), the longer it will take you to be </strong><em><strong>effective in writing code.</strong></em></p><p>While this might seem obvious, it&#8217;s rarely acknowledged. New engineers often feel pressured&#8212;by themselves or their colleagues&#8212;to match the speed of their previous roles within days or weeks. However, the simple truth is that larger codebases require more time for one to build the institutional knowledge necessary for effective development.</p><p><strong>Optimize for building context</strong></p><p>With principle 1 in mind, one should optimize for building context &#8211; this is typically pretty hard since many company&#8217;s incentives serve as reward to those who churn out code, and one&#8217;s early success often is defined (incorrectly in my opinion) by how quickly you can start producing code. If you find this to be the case, talk with your manager about wanting to prioritize learning/building context; you can even map out the things you plan to learn to demonstrate that you are productively spending your time even if you aren&#8217;t producing code quickly. While this proactiveness will pay massive dividends in the long run, it can be seen as counterintuitive early on because you may feel pressured to provide value as quickly as possible.</p><p><strong>You still can have massive impact from day 1</strong></p><p>The first two principles might seem to imply that you cannot provide value to your company early on - after all, if you can&#8217;t develop effectively within your first weeks/months as an engineer at a new job, then what value can you provide? As it turns out, there's plenty:</p><ul><li><p><strong>Take notes on what went well during onboarding and what can be improved</strong> Use this to improve the onboarding process for future engineers. This is arguably the highest leverage activity you can do within your first 2 months at a new company.</p></li><li><p><strong>Improve or create new documentation</strong> <br>The more institutional knowledge an organization can take from the minds of engineers and put into concise documentation, the better the organization will operate over time. Improving the company&#8217;s documentation will not only help new engineers onboard, but also assist in ongoing knowledge transfers, etc. Onboarding is often the best time to improve documentation before you are mired with the demands of the work of a fully ramped engineer.</p></li><li><p><strong>Share what worked well from your last job</strong> <br>Were there any tools/processes/systems in place at your old company that could augment what your new company is doing?</p></li><li><p><strong>Leverage your fresh perspective</strong> <br>One of the most valuable resources for any tech company is the fresh set of eyes that come with every new engineering hire - you aren&#8217;t saddled with the assumptions/biases that are inevitably present in everyone currently working at the company. To that end, you provide a perspective that can <em>potentially</em> help drive the company towards the optimal solution for its current problems. Don't assume that your perspectives are automatically novel and that no one else has considered what you're seeing. At the very least however, questioning everything will help accelerate your learning.</p></li></ul><p>By taking these principles into consideration as you onboard, you stand to provide immense value to the company up-front which will afford you ample time to build context and learn your codebase/stack.</p><p>With these principles in mind, here are some practical tips that will help you accelerate your time-to-productivity as an engineer:</p><h3><strong>Practical tips</strong></h3><p><strong>Use the product as much as possible</strong></p><p>While this point may seem obvious, it is by far the most overlooked part of the engineering onboarding process. No matter where you operate on the stack, developing an understanding of the product that you are building <em>from the perspective of the user</em> will help you be more effective as an engineer. Adopting this POV is especially critical with B2B products that you may not use day-to-day. Using the product will help you understand how the codebase works at a system level, for understanding how each code module maps to the product is critical to understanding <em>why</em> that code exists.</p><p><strong>Pair Program</strong></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xWiD!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xWiD!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xWiD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg" width="522" height="695.8804945054945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1941,&quot;width&quot;:1456,&quot;resizeWidth&quot;:522,&quot;bytes&quot;:2335986,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://numeric.substack.com/i/157434454?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xWiD!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 424w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 848w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!xWiD!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F95772f41-8f01-40de-918c-921738f5d310_4284x5712.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Sometimes, we could all benefit from having a second set of eyes :)</figcaption></figure></div><p>Pair programming enables engineers to share best practices, improves the odds of reaching the optimal implementation, and helps create code that is more consistent and readable. Pair with every engineer on your team: first, by asking them to shadow you as you implement your first set of tasks and secondly, by shadowing them as a means to learn about the codebase and the set of coding practices your team/company follows.</p><p><strong>Shadow on-call engineers</strong></p><p>Taking the spirit of pair programming a step further, consider shadowing your on-call engineer as they debug issues or to chat at the end of their shift to go over the most interesting problems they solved. This understudy will help in the following ways:</p><ol><li><p>You learn about all the internal tools used by your team</p></li><li><p>You gain additional understanding of the data models in your team&#8217;s domain</p></li><li><p>You gain insight into how the customer-facing product maps to the code base</p></li><li><p>You gain perspective on the code-base from a systems perspective, as many bugs often span multiple layers of the stack or multiple services</p></li><li><p>You develop better relations with everyone you shadow</p></li></ol><p><strong>Follow requests along the full stack</strong></p><p>Whether you are a front-end, back-end, or full-stack engineer, you should follow a request from the client along the entire stack. Doing so will help you understand the overall system design, <strong>which in turn will help you navigate the codebase faster.</strong> When tracing through the stack, ask yourself the following:</p><ul><li><p>What is the purpose of this layer, and why does it exist? Does it handle auth, validation, serialization, DB reads/writes, etc?</p></li><li><p>Who owns each layer?</p></li><li><p>Are there code patterns unique to each layer?</p></li><li><p>Where is each layer found in the directory structure?</p></li><li><p>Are there naming conventions to each layer that will help you find new modules/code in the future?</p></li></ul><p>In following requests along the full stack, use this time as a chance to get familiar with your IDE&#8217;s debugger if you haven't already. Additionally, try using <a href="https://www.cursor.com/">Cursor</a>&#8212;our go-to internal IDE&#8212;which is helpful at answering questions about your codebase.</p><p><strong>Ask an engineer to walk you through the data models</strong></p><p>There are several important sets of knowledge that cannot be discovered through reading code and must be absorbed via talking to someone with historical context of the codebase:</p><ul><li><p>How has the database schema changed over time? Having this set of knowledge gives more context as to why the database exists as it does today</p></li><li><p>What were the trade-offs made in picking the current db schema? For example, for tracking updates made to table foo, why was a separate foo_updates table made instead of directly updating the foo table itself?</p></li><li><p>What are the current faults in the current database-schema? If given more time, would the team like to perform any migrations?</p></li></ul><p><strong>Ask why major engineering decision were made</strong></p><p>In general, it is rarely enough for a new engineer to just read the company codebase to learn about the system. How so? Because engineering is always about tradeoffs. The current state of the codebase is always a function of how the set of product requirements changed over time, how the engineering team adapted the system to these requirements, and how much time was available for making these changes (a team often foregoes implementing the optimal solution due to time constraints).</p><p>Asking experienced engineers about the trade-offs they considered when developing the current system helps you understand why the system is the way it is today; more importantly, those conversations give you insight into where improvements can be made going forward. It also helps you understand how the product itself has evolved overtime as client needs have changed/evolved.</p><p><strong>Dedicate time to understanding the dev tooling</strong></p><p>By the end of your first month, you should be intimately familiar with your entire tool stack, across alerting/observability (eg. Datadog), user session tracking (ie. Fullstory), analytics (ie. Segments), internal tooling (ie. Retool), database IDE (ie. Datagrip), IDE (if a new one is mandated by the company), etc.</p><p><strong>Optimize for breadth over depth by touching as much of the stack as possible</strong></p><p>Following principle 2 (optimize for context), in your first few months at a new company, you should try to work on many small tickets that touch as much of your team&#8217;s domain as possible. This work will be a forcing function for optimized learning over raw output. Of course, this may not align pragmatically with your team&#8217;s roadmap, or there may not be tickets that touch all parts of your team&#8217;s code. If so, I recommend telling your manager that this is a priority of yours and ask if they are willing to help optimize for breadth initially.</p><p>After you have closed out a few smaller tasks, I recommend asking your manager to help you find a project that touches multiple layers/services/domains that your team owns. Working on a project of this kind will help you to build a more cohesive understanding of the codebase from a systems perspective, as the project will illuminate how different parts of the codebase interact in practice.</p><p><strong>Shadow Calls with the Customer</strong></p><p>Reach out to CX/Sales/Product &#8211; whoever directly interacts with the customer &#8211; and ask if you can shadow several calls with them. Doing so will help you build empathy for the customer you are serving and help you understand the customer&#8217;s main pain points.</p><p><strong>Meet as many people as possible</strong></p><p>When joining a new company, aim to build relationships. That goes as advice in and of itself, but also will serve you well in advance of needing to ask people for PR reviews, meeting time, or help. I recommend figuring out which teams are adjacent to yours (both engineering and otherwise), and <strong>meeting everyone 1-on-1</strong> within your first month at the company. While that might sound exhausting,  especially for the introverts among us , you will begin to build meaningful relationships at the company that will in turn make your work more enjoyable. And as a helpful aside, people are always more willing to help those they already have met personally.</p><p>More tactically, ask about the objectives of their role, how they collaborate with other teams, the challenges they face, and any advice they have for navigating the company. This insight will help you understand your place within the broader organization and may even highlight opportunities to improve internal systems and communication.</p><div><hr></div><p>When we onboard new engineers to the Numeric team, we keep these principles and pieces of advice top of mind. We consider the process successful after two months if the new engineer has developed informed opinions on the codebase, product, team practices, and/or overall strategy&#8212;something that&#8217;s only possible with the right knowledge and context from the start.</p><p>To build that context quickly, we've streamlined our onboarding documentation and local development setup so engineers can have the app running on day one and submit their first pull request by the end of that day. This efficiency lets them focus on onboarding tasks that span many aspects of our product and domain without having to waste time burning brain cells trying to get a dev environment properly setup.</p><p>We also integrate context-building activities from the very beginning. New engineers join customer and sales calls during their first few weeks, meet colleagues across every department, shadow on-call engineers, and engage in in-depth discussions with team members about past projects. Combined with other effective practices, this approach enables us to ramp up engineers quickly while ensuring they have the comprehensive context needed for long-term success.</p><p>If these practices interest you, we are hiring for more talented, product-oriented, full-stack generalist engineers to join the team. <a href="https://www.notion.so/Numeric-05be308e532b424bb9fb3b851844f82e?pvs=21">Here is a one-pager</a> that gives more context on who we are, what we are building, and why we are building it. Please reach out if interested: <a href="mailto:ben@numeric.io">ben@numeric.io</a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support our work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Creativity vs. Toil]]></title><description><![CDATA[On knowing ourselves]]></description><link>https://numeric.substack.com/p/creativity-vs-toil</link><guid isPermaLink="false">https://numeric.substack.com/p/creativity-vs-toil</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Thu, 23 Jan 2025 16:12:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!yOlU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A significant part of being a good software engineer, as well as a good engineering manager, involves understanding the psychology that plays out for an individual developer. As engineers, improvement comes not just from the acquisition of new skills and knowledge, but also from building a meta-game of how we strategize and navigate through difficulty. This extends to understanding ourselves.</p><p>So it&#8217;s valuable to know what may trigger distraction or frustration in the course of work. In some cases, these can be wholly negative experiences. Things like being interrupted out of focus mode or hitting a significant unanticipated problem always feels bad in a way that can disrupt progress on a project. We learn to recognize such experiences so that we can consider how to prevent or navigate around them.</p><p>But there are more complex cases: those experiences that can be welcome in one context and unpleasant in another. There are many of these, but one such dynamic I&#8217;ve been pondering lately is the relationship between toil and creativity. I believe there is fundamental tension between two sorts of work, where each can be satisfying and rewarding in their own rite, yet they feel horrible to do at the same time.</p><p>Toil, for the purposes of this line of thinking, is work which just needs to get done. When you have a task, you work away at it, and progress is made. Other work involves thinking creatively about what to do and how to do it. This work is more creatively demanding and more unpredictable in its progress. Most white collar jobs have aspects of both, and engineering is no exception: there is a side of our work that feels like inventing and architecting, and there is a side of it that feels like digging holes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!yOlU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!yOlU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!yOlU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg" width="460" height="312.4587912087912" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:989,&quot;width&quot;:1456,&quot;resizeWidth&quot;:460,&quot;bytes&quot;:1091950,&quot;alt&quot;:&quot;Home and violin shop of Arvil Olof Anderson, near St. Ignatius, Montana - https://www.loc.gov/item/afc1981005_cf04/&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Home and violin shop of Arvil Olof Anderson, near St. Ignatius, Montana - https://www.loc.gov/item/afc1981005_cf04/" title="Home and violin shop of Arvil Olof Anderson, near St. Ignatius, Montana - https://www.loc.gov/item/afc1981005_cf04/" srcset="https://substackcdn.com/image/fetch/$s_!yOlU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 424w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 848w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!yOlU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb5674f3e-eff2-4d1a-94b5-a644ecf837ca_5060x3438.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p></p><p>Everyone knows that engineers enjoy the creative challenges of the job, whereas to toil almost implies a loss of agency or humanity. But I was painting shelves recently and it struck me how enjoyable the work actually was. There was a feeling of steady progress, a feeling of industriousness. It&#8217;s not that we don&#8217;t enjoy toil. As long as you have conviction you&#8217;re working on the right thing, toil can feel like the incarnation of<em> getting shit done.</em> As a programmer, these may even be the periods when the most lines of code are written. I like this feeling of executing, this sense of being automatic.</p><p>I was on a project and working in this mode when I encountered an important question about product behavior which I hadn&#8217;t considered ahead of time, and seeing it created a moment of dread. But why did I experience that dread? Considering product behavior, making strategic and user-facing decisions, and then planning how to build is the very creative work which I enjoy and take pride in. Nonetheless, my first feeling was frustration. </p><p>I realized later that the problem was not in one type of work or the other, but in their intermixing. I thought I was in the get-shit-done mode of toiling, and I got yanked right out of it. This is the fundamental tension, and it led me to wonder:</p><ul><li><p>Do other jobs deal with this internal conflict less frequently? If so, why? How?</p></li><li><p>Does the nature of programming necessitate dealing with this frustration?</p></li><li><p>What can a developer do about it?</p></li></ul><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe to read ideas on programming, product, and company building from the team.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>Do other jobs deal with this?</h1><p>In considering the other roles at our company, I don&#8217;t think they deal with the same degree of intermixing of toil and creativity that developers do, even though most knowledge work surely involves both. The sales team, for instance, has a <em>lot</em> of execution to do, and a lot of repetitions to sustain. They execute on a plan and maintain energy to work across many deals a day. Their creative work seems to come in the exceptions like making judgment calls about how to plan steps for a tricky deal, or in the systematic thinking like revisiting their demo playbook. In general, they do seem to not mix the work for the most part. When they&#8217;re on calls and sending follow-up notes, they&#8217;re very much not looking to rethink the plan and the playbook.</p><p>Recruiting has a similar dynamic. You hope to square away the creative judgment calls prior to the toil: What are the stages of our process? What are the formats of our interview? What are the criteria for evaluation? What should the content of this rejection email be? To the extent possible, we try to answer these things ahead of time so that we can efficiently execute within the system. Then, periodically, we take the observations from doing so and zoom back out on the creative aspect. Critically, much like sales, we save cognitive energy so that we are ready and able to home in on tricky cases and exceptions as they come up.</p><p>Why don&#8217;t software engineers do this more effectively? Why can&#8217;t we simply execute within a plan or system and save the refactors, the product planning, and more for later in the day or another day entirely? Maybe our work is not categorically different, but the degree of the challenge does seem to be distinct.</p><h1>Is it fundamental to software?</h1><p>A fundamental trait of software engineering as a practice is the unpredictability that comes as a consequence of novelty. Software is free to copy and to reuse. In most cases, when something we need is perfectly solved by an existing library, tool, or product, we simply install it&#8211;we don&#8217;t write it anew. If software engineers are involved, there usually must be something particular about the goals or the context that isn&#8217;t already solved. Moreover, the challenge of software is rarely simply one of implementing the answer to a question&#8211;it&#8217;s usually figuring out what the answer is, what the precise intention is. We are almost always, in some way or another, working under conditions of novelty.</p><p>Contrast this with recruiting. Even if your process were <em>exactly</em> identical to another company&#8217;s, you&#8217;d still have to do the work. You can&#8217;t just import a library and call a function, even if it&#8217;s been perfectly solved. You have to send the email, you have to get on the call, you have to book the hotel.<a class="footnote-anchor" data-component-name="FootnoteAnchorToDOM" id="footnote-anchor-1" href="#footnote-1" target="_self">1</a></p><p>So I do think there is a fundamental trait of our work which leads to unpredictability. But what is the connection between unpredictability and the creativity/toil association?</p><p>Put simply, the ability to separate the two (and thus avoid the tension) comes from the ability to toil consistently&#8211;to have a plan and to execute against it without disruption. This only works as long as the plan stays intact. For developers, it&#8217;s a fool&#8217;s errand to expect plans to stay intact. As a consequence, we don&#8217;t get to settle into a rhythm of getting shit done; we stay vigilant, paranoid even, for unforeseen problems and risks. It&#8217;s cognitively demanding in a way that can be very rewarding, but it&#8217;s also draining and leaves you occasionally wishing to have a simpler job.</p><h1>So what do I do about it?</h1><p>To be honest, I don&#8217;t think there is a silver bullet here. The best ideas I can offer are these:</p><p><strong>Avoid it where you can</strong></p><p>If you do know you have some high-precision focus work to execute on, and some creative planning or tricky architecture design to do, separate them. My point in this writing is that we simply cannot always do that, but in cases where you have the ability to separate the toil you should do so. Don&#8217;t make things harder than they have to be.</p><p><strong>Reduce the demands of the toil</strong></p><p>Another defining trait of software engineering is the level of depth and precision required in order to do the job. While we take pride in our ability and willingness to think precisely at low levels of abstraction, I also believe it is massive cognitive drain which makes it challenging to take on other items.</p><p>I recommend, wherever possible, to reduce the amount of brainpower required to toil. Break things down into smaller pieces, use reliable technologies and abstractions, and use any tooling that can help. Some tools, like programming languages, help to ease the challenge by raising the lowest level of abstraction you have to deal with. Even if you&#8217;re an expert with C, using Python can still free up your brain and your energy to be more prepared for the unexpected by raising the floor of abstraction at which you have to think. Just because you can do it all doesn&#8217;t mean you should. I believe AI copilots suit this purpose as well&#8211;one of their great benefits is not that they can write code that you wouldn&#8217;t be able to, but rather that you simply do not have to do it and can thus save your cognitive energy. For me personally, there is an amount of gas that stays in the tank by getting to offload some of the precise details of coding even in systems where I am perfectly capable of executing on the details.</p><p><strong>Be mindful</strong></p><p>Lastly, there is power in knowing something is coming even if you&#8217;re unable to prevent it. There will be moments of frustration and disruption. If you can be mindful of the situation and the impact it has on you, just knowing this can help you to recover and make progress. Perhaps it&#8217;s unsatisfying, but knowing one&#8217;s self is itself an important tool in dealing with such challenges, and the challenges will continue to come. In these cases, I recommend taking a moment to acknowledge the disruption and even planning to go for a walk or take a break to help smooth over the disruption.</p><div class="footnote" data-component-name="FootnoteToDOM"><a id="footnote-1" href="#footnote-anchor-1" class="footnote-number" contenteditable="false" target="_self">1</a><div class="footnote-content"><p>Maybe booking hotels is something that soon won&#8217;t require a human to execute, but I resisted the tiny urge to turn this into an essay on AI agents or how they might impact other fields. I leave that as an exercise to the bored commenter.</p></div></div>]]></content:encoded></item><item><title><![CDATA[Ergonomic Typescript at Numeric]]></title><description><![CDATA[The patterns and tools that we've used to make Typescript development a breeze, and some JS features to avoid entirely]]></description><link>https://numeric.substack.com/p/ergonomic-typescript-at-numeric</link><guid isPermaLink="false">https://numeric.substack.com/p/ergonomic-typescript-at-numeric</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Wed, 15 Jan 2025 00:50:41 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/31555fd7-c886-4aed-8f8b-b6dfebb4a219_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>With the exception of tools like Jupyter notebooks, we use Typescript for all of our development at Numeric. The reasons for choosing Typescript were fairly straightforward: the type system is excellent and expressive and using Typescript allows us to use one language across the stack, including our React app.</p><p>Today, it&#8217;s still a joy to work with but this does not happen by default&#8211;Javascript and Typescript are flexible enough that you can turn them into whatever you want. The language and ecosystem do not come with batteries included, and unlike some languages (e.g. Golang) there is no common convention of good practices. You have to choose your own style &amp; tools and stick with them in order to have a consistent and pleasant experience.</p><p>For us, success has come from choosing our style preferences, building our own idioms and patterns, and avoiding some parts of Javascript entirely.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://numeric.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Numeric Engineering! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><h1>Some JS things to simply avoid</h1><p>There are a handful of Javascript things we actively avoid using. Many of them are caught by linting.</p><h3>Null-ish ambiguity</h3><p>In JS, you can check the boolean value of anything. This means you can use the empty string, zero, null, or undefined to represent nothing if you so choose. We avoid this ambiguity by using null to mean nothing. To that end:</p><ul><li><p>Don&#8217;t use undefined as a value. Treat it as identical to &#8220;nothing was passed in/set&#8221;</p></li></ul><ul><li><p>Don&#8217;t use anything other than null to explicitly mean &#8220;nothing&#8221;</p><ul><li><p>Empty strings are probably the worst culprit, but 0 is pretty bad too.</p></li></ul></li><li><p>Do not use <code>||</code> to coalesce values. Given the above rule of using null to mean nothing, we use <code>??</code>.</p></li></ul><h3>Bad iterators</h3><p>We use a regular <code>for (const x of &#8230; )</code> loop for most iteration needs. It&#8217;s less error prone and more readable than an indexing loop. We also:</p><ul><li><p>Avoid <code>forEach()</code></p><ul><li><p>If an <code>await</code> is called inside the closure, it will not block&#8211;the iteration will proceed. It&#8217;s not a common  case, but if you make this mistake the ensuing bugs are tricky. There really aren&#8217;t benefits over the regular loop, so we avoid it.</p></li></ul></li></ul><ul><li><p>Avoid <code>reduce()</code></p><ul><li><p>Similar to forEach(), it adds nothing and occasionally creates subtle bugs.</p></li></ul></li></ul><ul><li><p>Use <code>.map()</code> in a functional manner, where a new value is produced for each element of the list without modifying anything in the list.</p></li></ul><h3>The 'in' keyword</h3><p>Avoid it. If you need to check whether something is in a collection, access it and check the result. The in keyword is confusing and easy to mistake for functionality which it does not offer, as it behaves differently depending on whether you&#8217;re accessing an object, an Array, a Map, or a Set.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9mnA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9mnA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 424w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 848w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 1272w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9mnA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png" width="1360" height="582" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:582,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:52183,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!9mnA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 424w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 848w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.png 1272w, https://substackcdn.com/image/fetch/$s_!9mnA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb72c71bd-aa3c-4eae-8e50-56e4d39cbdf1_1360x582.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><h3>Ambiguous function calls</h3><p>In Javascript, you can declare a function like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!vvDS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!vvDS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 424w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 848w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 1272w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!vvDS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png" width="1360" height="310" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:310,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:37933,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!vvDS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 424w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 848w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 1272w, https://substackcdn.com/image/fetch/$s_!vvDS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F292fe408-8ba9-451b-b1c4-118e7763a9a0_1360x310.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>This means both arguments are optional. What if I want to pass in a value for arg2 but not for arg1? The code would look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rRpY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rRpY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 424w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 848w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 1272w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rRpY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png" width="1360" height="232" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:232,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:27312,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rRpY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 424w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 848w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 1272w, https://substackcdn.com/image/fetch/$s_!rRpY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2182ef78-f0ae-4d9b-8118-3cc9d4b14906_1360x232.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>In fact, even if they weren&#8217;t optional arguments, it&#8217;s easy to mistakenly pass arguments in the wrong order:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LV_2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LV_2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 424w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 848w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 1272w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LV_2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png" width="1360" height="232" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:232,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:24681,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!LV_2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 424w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 848w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 1272w, https://substackcdn.com/image/fetch/$s_!LV_2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffcbd3bd8-4db7-4aae-ada0-ffc497ef89c0_1360x232.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>For any case where we have multiple arguments of the same type or multiple optional arguments, we prefer the safety and readability that comes from using a structured object instead:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!G6xv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!G6xv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 424w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 848w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 1272w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!G6xv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png" width="1360" height="504" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:504,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:57844,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!G6xv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 424w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 848w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.png 1272w, https://substackcdn.com/image/fetch/$s_!G6xv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5644b118-c6bd-4fb2-b003-4b9fa960cfea_1360x504.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>This makes it materially harder to accidentally pass in arguments incorrectly.</p><h3>Date object functionality</h3><p>The Javascript <code>Date</code> object is pretty unhelpful, especially when dealing with time zones. Attempting to do math with a Date or to render it in a certain way is tedious and error-prone. This matters for us as software in the domain of accounting must make regular use of concepts around time. Beyond that, we have two concepts of time: real time (moments denoted usually in milliseconds UTC) and the accounting timeline (day&#8217;s resolution without any time zone associated). To that end:</p><ul><li><p>We use libraries for operations involving manipulations of <code>Date</code> and keep all such operations in a single, unit-tested utility file.</p></li><li><p>We have our own <code>CalendarDay</code> type to represent the <code>YYYY-MM-DD</code> type which makes it clear which time concept is being used and which implements common operations.</p></li></ul><p>There is an <a href="https://tc39.es/proposal-temporal/docs/">ECMA proposal to add a new namespace &#8220;Temporal&#8221; to core Javascript</a> which we&#8217;re excited about. It offers improved tools for working with dates and times by applying learnings from the Date object and by separating distinct use cases; it would solve both of these needs out of the box.</p><h1>Tooling and missing pieces</h1><p>There are some components we&#8217;ve built ourselves to create an ergonomic environment for Typescript  development.</p><h2>Object validation and type safety off the wire</h2><p>Everything in our backend code is typed. When data is coming in from some external place (the database, responses to API requests, request bodies from our own app) we validate it using a library called <a href="https://github.com/colinhacks/zod">Zod</a>. This serves a few purposes.<br><br>The obvious function is to check the data coming in to catch all classes of error involving unexpected data. This is especially important for getting data from 3rd parties. Validation can also catch bad requests from the front end or mismatches between the database and the code&#8217;s expectations of the result from a query. The alternative would be to either optimistically cast the data to the intended type (opening the door to various type errors) or to check for properties where they&#8217;re used, which would lead to type-validation code spreading throughout the code base. We prefer to enforce the expectations at the door.</p><p>The second purpose is to allow us to make better use of Typescript; by having an object to represent the type, we can use generics where types are inferred from the object as an argument to the function.</p><p>Lastly, we can minimize repetitive logic to enforce expectations. Without a validation library, our code might look like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!C1x_!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!C1x_!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 424w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 848w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 1272w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!C1x_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png" width="1360" height="738" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:738,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:103396,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!C1x_!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 424w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 848w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.png 1272w, https://substackcdn.com/image/fetch/$s_!C1x_!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8e0d3a09-3a6c-4e16-80e9-2b4a49bbea3c_1360x738.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><br>In terms of the choice of library, Zod has excellent developer experience and depth of parity with the Typescript type system. With that said, it&#8217;s focused on <em>parsing</em>, meaning Zod copies the full object and can potentially transform the contents and type of the input object. This is useful for avoiding boilerplate logic in transformations but harmful when dealing with large volumes of data, as it is not as performant as libraries which only validate types without transforming the data.</p><h2>Our Result type: why we don&#8217;t throw errors</h2><p>In Javascript, errors are traditionally raised by calling <code>throw new Error(...)</code>.</p><p>This creates implicit behavior, where the potential return types of a function are not readable by its signature. We want functions to have their return types annotated, and the error case is part of the return type; it&#8217;s a result from calling the function.</p><p>Thus we do not throw errors. Instead, we have a special type to represent the result of some fallible computation.</p><h3>Result&lt;T&gt;</h3><p>The result type represents a potentially-failed return type of T, and we make heavy use of it in the backend. This pattern exists in other languages and was fairly easy to build into Typescript. At its core, the basic idea looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!Mt9Z!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 424w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 848w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 1272w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png" width="1360" height="660" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/f11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:660,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:68057,&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://numeric.substack.com/i/154796071?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.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_!Mt9Z!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 424w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 848w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.png 1272w, https://substackcdn.com/image/fetch/$s_!Mt9Z!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ff11d8ee8-87ed-4fe2-9f2f-19768531ab89_1360x660.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>The caller then calls a function:  </p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!oZi7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!oZi7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 424w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 848w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 1272w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!oZi7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png" width="1360" height="700" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:700,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:107368,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!oZi7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 424w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 848w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.png 1272w, https://substackcdn.com/image/fetch/$s_!oZi7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6053e85-c9db-4217-ba03-c4b4f0002880_1360x700.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>This approach makes code more explicit and encourages engineers to consider failure modes and avoid &#8220;happy path&#8221; coding.</p><p>In practice, both the Failed and Success cases are classes we've built, and we also have a static object Results to serve as a namespace for instantiating errors like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SRkz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SRkz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 424w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 848w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 1272w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SRkz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png" width="1360" height="388" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:388,&quot;width&quot;:1360,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:49645,&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;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!SRkz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 424w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 848w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.png 1272w, https://substackcdn.com/image/fetch/$s_!SRkz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F742d541d-61eb-4abc-8888-360c40c4917a_1360x388.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>These classes contain fields and methods we&#8217;ve added over time for various features like:</p><ul><li><p>Capturing stack traces</p></li><li><p>Storing context data alongside an error, usually to be logged out at the top</p></li><li><p>Prefixing or wrapping an error in the caller with additional context</p></li><li><p>Setting content to be bubbled up to the API response, including an HTTP status code or a user-friendly message</p></li><li><p>Formatting logging of these objects</p></li></ul><h1>Style preferences</h1><p>Ultimately, consistency is critical so you should identify and commit to your team&#8217;s style preferences. I want to call out a few of Numeric&#8217;s preferences for style, not to serve as any sort of comprehensive style guide but because these key points help explain and inform the choices described above.</p><h2>Explicit, readable. Avoid magic.</h2><p>An engineer should be able to read the vast majority of our code base without having Javascript-specific expertise.</p><p>Implicit behaviors should be avoided, and one should be able to &#8220;follow&#8221; the code in order to see what it does. Anything which breaks this rule should be done for a good reason, and done only where future contributions are expected to be rare. It&#8217;s a bit harder to satisfy this target in the front end, but the principle holds. We avoid anything that seems to favor density or write-time pleasantness if it comes at the cost of readability.</p><h2>Functional functions</h2><p>Functions should take arguments and return outputs. They should not mutate the arguments.</p><p>The answer to the question &#8220;<em>What does this function do?&#8221;</em> should be answered by the function&#8217;s signature: the name, the arguments, and the return type should reasonably summarize the story. All functions should have the return type annotated.</p><p>More generally, we aim to have the function signatures and the involved types do a good job of telling the story of what&#8217;s happening in an area of the code.</p><h2>Types as domain language</h2><p>A last idea worth pointing out is that you can express a lot with Typescript types. We define and reuse named types across the code base, including the front end. This has led to a gradual defining of the domain via types and their names. For new engineers to the team these types can serve as hooks by which unfamiliar concepts are identified.</p><p>Because Typescript has such flexibility and simplicity, it is easy to express ideas and types and then proceed from there. Personally, I lean toward a sort of type-first programming approach where I define types and think about their transformations prior to coding any control flow logic. Strong, consistent typing means refactors are substantially helped by the compiler, and changes can be made with this in mind. Additionally, an accidental but pleasant consequence of this approach is that coding copilots like Cursor also seem to do quite well with well-defined types, and show a strong ability to connect the dots between them.</p><p>Because we work on accounting&#8211;a fairly deep domain with which software engineers are generally unfamiliar&#8211;this trait of Typescript has been instrumental. Much of what we do is evolve concepts and ideas, making the language in which we express our ideas quite important. To date we&#8217;ve been quite happy with the language, and the warts of Javascript and its ecosystem have been acceptable drawbacks for working with Typescript.</p>]]></content:encoded></item><item><title><![CDATA[Things to Avoid Building as an Early Startup]]></title><description><![CDATA[Our velocity early on was driven substantially by the things we did not do. Here's what we recommend explicitly not building.]]></description><link>https://numeric.substack.com/p/things-to-avoid-building</link><guid isPermaLink="false">https://numeric.substack.com/p/things-to-avoid-building</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Tue, 01 Oct 2024 07:00:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/65a96865-619e-4bac-8723-4b50f3e6f96c_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Product development in a new company is defined by scarcity and exploration. You've got a small team (perhaps no team beyond the founders) and you're building an offering which is still in the process of being defined and discovered.</p><p>At Numeric, we made clear, intentional decisions of what not to build which allowed us to make great progress with a small team and ultimately gain traction with our audience. Our velocity was driven substantially by the things we did not do. It's important as product builders to identify those asks and ideas which might slow progress on core value to users. To that end, here are a few of those things which we avoided at Numeric and have been generally happy to have done so.</p><p>For context, we are a B2B SaaS company. Our users are finance &amp; accounting teams who use our platform to automate &amp; analyze their financial data as well as to collaborate on closing their books each month.</p><h2><strong>Dashboards</strong></h2><p>People love a dashboard. Or rather, people love the idea of a dashboard.</p><p>A splashy top-down view with charts, progress, indicators, statuses, and more looks great in a sales demo. It helps paint the picture of what success looks after engaging on your product. The reality, however, is that these dashboards can be of limited value and difficult to maintain in the early days.</p><p>To be clear: I'm referring to dashboards of your own, first-party data. For Numeric, that looks like this:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kS42!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kS42!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 424w, https://substackcdn.com/image/fetch/$s_!kS42!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 848w, https://substackcdn.com/image/fetch/$s_!kS42!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 1272w, https://substackcdn.com/image/fetch/$s_!kS42!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kS42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png" width="1456" height="760" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:760,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;A visual of a Numeric dashboard showing progress to the month-end close.&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="A visual of a Numeric dashboard showing progress to the month-end close." title="A visual of a Numeric dashboard showing progress to the month-end close." srcset="https://substackcdn.com/image/fetch/$s_!kS42!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 424w, https://substackcdn.com/image/fetch/$s_!kS42!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 848w, https://substackcdn.com/image/fetch/$s_!kS42!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 1272w, https://substackcdn.com/image/fetch/$s_!kS42!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F45a86ecc-b0d1-4c5c-8d68-316040e21682_1529x798.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>But we held off on building this for a long time, and had dozens of companies using our platform before we finally put the overview page in place.</p><p>This is because for a while user engagement wasn't excellent. We hadn't provided enough core value to get people consistently engaged and coming back. Any dashboards would have been fairly empty. And while a dashboard is good for a sales demo, it won't drive usage.</p><p>In short, a dashboard of your own app's data is what I'd call a "2nd degree feature"&#8211;its value is derived and dependent on other features of your product being used. It's not a good place to dedicate your early efforts. Put simply, if feature B depends on successful feature A, make A successful first.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!cqU2!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!cqU2!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 424w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 848w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!cqU2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png" width="1456" height="766" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:766,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&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="" title="" srcset="https://substackcdn.com/image/fetch/$s_!cqU2!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 424w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 848w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.png 1272w, https://substackcdn.com/image/fetch/$s_!cqU2!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87b9b5c9-9580-4549-826d-f6b0a000401a_5700x3000.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><h2><strong>Other 2nd-degree features</strong></h2><h3><strong>Sharing functionality</strong></h3><p>Is there something to share yet? Instagram started first by being a great app for taking and modifying photos. The sharing of photos drove its growth, but only after they had built a reason for people to be taking photos there in the first place.</p><p>In B2B software, this can look like sharing reports, views, or exports to other people or parts of the company.</p><p>As with dashboards, ask: is the content already there? Is there something worth sharing yet?</p><h3><strong>Preferences and configuration</strong></h3><p>When do you want your notifications? Through email, slack, daily, in-time, &#8230;? Do you want a dark mode?</p><p>Beware user input on this front: in research calls, users will happily surface "blockers" or "needs" along these lines. What they're doing is imagining that they're <em>already onboarded to your product</em>, and contriving what problems they might run into at some point. They're doing this both to be helpful and to make sure they don't go through the effort to switch and learn to use your product only to later run into deal-breakers.</p><p>It's important to realize that while these proposals may be accurate, they're still second-degree features. Make sure your users know that you understand their perspective, and meticulously keep track of what you're hearing. But don't put the cart before the horse. These features don't drive adoption.</p><p>You won't win users by eliminating reasons <em>not</em> to use your product&#8211;you have to have win them by giving them a good reason<em> for </em>using it. You'll even be surprised how many of these objections can be delayed into the future or forgotten entirely if the core value is substantial.</p><h2><strong>Table-stakes: billing, user management, etc.</strong></h2><p>There are a lot of things that may seem to fall into a category of &#8220;table-stakes&#8221;&#8211;things that users just expect a software product to have. Examples include:</p><ul><li><p>User management (invite &amp; remove users, avatars)</p></li><li><p>Notification preferences</p></li><li><p>Billing settings (change credit card)</p></li><li><p>Plan / tier selection</p></li><li><p>UI themes, dark mode</p></li></ul><p>Psychologically, these feel like safe things to do. They're easy to design, hard to argue with, and feel like progress. But these can be built later as they're needed. Instead, I recommend you handle these things manually (e.g. changing a credit card) until the burden to do so becomes significant.</p><p>Until then, stay focused on things that help you learn about your users.</p><p>Coding always takes longer than you expect so don't spend precious time on any tables stakes if you can get away with not including them. And don't assume what your audience will need, as it may be less than you think.</p><h2><strong>Permissions</strong></h2><p>Permissions are messy. They define rules based on the attributes of domain objects, the roles of users, and relationships among these. <a href="https://www.osohq.com/post/ten-types-of-authorization#:~:text=At%20Oso%2C%20we're%20constantly,all%20of%20them%20are%20related.">This article</a> does a great job of outlining this complexity, and rightly points out that most real applications will have a model which uses multiple types of authorization to meet their needs.</p><p>When your product is still early, things are in flux. Features are being built, re-built, removed, and refactored in response to new learnings all the time. This is not ideal when it comes to defining your permissions rules.</p><p>At Numeric, we avoided authorization for this exact reason. We knew the changes in the data model were too frequent, and introducing permissions too early would be a disproportionate headache to build, evolve, and maintain.</p><p>By the time we did add a permissions model, the model was almost obvious. Our platform's design and data model had coalesced around a few primitives like workspaces, reports, and tasks. With the help of <a href="https://www.osohq.com/">Oso</a> (both their software and their team), we were able to define a permissions model which was straight-forward enough that non-engineers can understand and discuss our user needs, and what we've built for authorization has been stable and long-lasting.</p><p>But I want to reiterate: it was <em>not obvious</em> what these primitives were going to be prior. In retrospect they seem obvious, but at times it looked like abstractions were going to develop in a very different way. Had we introduced permissions too early, it might have artificially tied our plans and our minds to a worse model for the platform.</p><h2><strong>Mileage may vary</strong></h2><p>For all of these points, you need to understand and evaluate what makes sense for your business. Focus on what is distinct about your product and your audience. If something I&#8217;ve listed above is necessary or part of the core value proposition to your users, don&#8217;t skip it.</p><p>All of these examples illustrate the same general theme: in a startup, learn about your users and your domain deeply, and stay aggressively focused on learning more and progressing toward product-market fit. It sounds simple, but in the fog of actually building a company it can be surprisingly hard and subtle to apply the idea. It's difficult, but alongside a tight-knit team and a fast iteration cycle this allows you to accomplish great things.</p>]]></content:encoded></item><item><title><![CDATA[An 80/20 Rule for Product Development]]></title><description><![CDATA[We believe that for every product goal, there's a solution that achieves 80% of the value with 20% of the effort&#8212; we outline how we've aligned our team around this philosophy of product development.]]></description><link>https://numeric.substack.com/p/80-20-rule-for-product-development</link><guid isPermaLink="false">https://numeric.substack.com/p/80-20-rule-for-product-development</guid><dc:creator><![CDATA[Andrew Bihl]]></dc:creator><pubDate>Tue, 01 Oct 2024 07:00:00 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/bce4d6a3-ff19-4999-8832-6e2d50a3d3ce_1456x1048.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>At Numeric, we have learned to make great progress with a small team. For any software startup this is essential. Incumbent players will have an established product, multiple years&#8217; head start, and far more resources and people. And yet, it&#8217;s not unheard of to see comparatively tiny companies rapidly catch up in an existing market. How?</p><p>The answer, of course, is that many different things must come together to make this possible. For us, one particularly important component has been how we approach discovery and product development, which depends critically on our belief in an &#8220;80/20 rule&#8221; for new feature development.</p><p>Consider a &#8220;traditional&#8221; approach to product development:</p><ol><li><p>Pain point is identified and information is gathered (led by product managers)</p></li><li><p>Design and feedback process (designers are now brought in the process)</p></li><li><p>Documentation and requirements are formalized</p></li><li><p>The project is handed off to engineering, who have their own approach for building</p></li></ol><p>&#8205;</p><p>For a lot of reasons, teams tend to operate in this model even if they didn't start this way. In many teams which claim to be "agile", the process will be largely the same but just repeated on smaller pieces. Or, in some cases, the "agile" practice only exists within the execution of step 4.</p><p>&#8205;</p><p>At Numeric, we hold a few beliefs which reveal problems with the above approach:</p><ol><li><p>There is more than one way to solve a problem, and both product and technical insights can introduce new ways of looking at a problem.</p></li><li><p>Identifying which details and requirements are valuable (or essential) requires deep understanding of the audience.</p></li><li><p>It's <a href="https://xkcd.com/1425/">notoriously difficult</a> for non-engineers to know the relative difficulty of technical tasks, and often small details and non-critical requirements drive disproportionate effort and cost.</p></li></ol><p>&#8205;</p><p>Taken together, these lead to a conclusion:</p><h4><strong>For most product goals or pain points, there exists some solution that achieves 80% of the value with 20% of the effort, but finding it requires combining deep understanding of engineering, product, and users.</strong></h4><p>&#8205;</p><p>The problem with the "traditional" approach then becomes obvious&#8211;if engineering is only brought in after requirements are formalized, we've lost the ability to leverage this flexibility in defining the solution.</p><p>Once the "define the solution" phase has been solved and documented, the core shape and definition of the solution is largely set. Engineering may push back on aspects at the fringes but it's unlikely the plan will be dramatically reoriented. Not only have we missed the possibility of a more optimal solution, but this pattern creates a frustrating and unsatisfying role for engineering to occupy.</p><p>Alternatively, we can work to combine customer insight, product understanding, and technical knowledge at <em>every</em> stage of development. Engineers provide insight into what's difficult, what's easy, what's possible and what's impossible. The product manager (or anyone with insight into the customer) can elucidate the goals: who are we are serving, what's necessary or nice-to-have, what's certain or experimental. And the designer can synthesize these ideas into a concrete, usable proposal. Together, these constraints encourage the kind of creative problem-solving that yields a great product which can be quite different from the first proposal and delivered in a fraction of the time.</p><p>This approach vastly shortens the timeline to learn more from customers after launching. And for early startups, <strong>this makes the difference between success and non-existence.</strong></p><p>It's not easy to do this. The domain, the customers, the company size, the members of the team and other variables can help or hinder the effort. But when done right, the result is a fast-moving and collaborative process with impressive results to show for it.</p>]]></content:encoded></item></channel></rss>