<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hanzla Baig</title>
    <description>The latest articles on DEV Community by Hanzla Baig (@hanzla).</description>
    <link>https://dev.to/hanzla</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png</url>
      <title>DEV Community: Hanzla Baig</title>
      <link>https://dev.to/hanzla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hanzla"/>
    <language>en</language>
    <item>
      <title>Getting Your First 10 Users Is Harder Than Building the Product (Or Is It?)</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Mon, 26 Oct 2026 15:47:44 +0000</pubDate>
      <link>https://dev.to/startupcafe/getting-your-first-10-users-is-harder-than-building-the-product-or-is-it-3omh</link>
      <guid>https://dev.to/startupcafe/getting-your-first-10-users-is-harder-than-building-the-product-or-is-it-3omh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Building proves you can create a solution. Finding users proves the solution matters to someone. These are &lt;em&gt;separate questions&lt;/em&gt;, and finishing the first never answers the second.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reading time: about 12 minutes. For developers and first-time founders.&lt;/p&gt;

&lt;p&gt;A developer can spend six months building a product without having to convince a single stranger to care about it.&lt;/p&gt;

&lt;p&gt;The code either works or it doesn't. A bug can be investigated. A slow query can be measured. If a feature breaks, there's usually somewhere to start looking.&lt;/p&gt;

&lt;p&gt;Then the product is ready, the landing page is live, and the founder starts reaching out to people.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nothing happens.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A few people visit the website. Someone says the idea sounds interesting. A friend offers encouragement. Perhaps a handful of people create accounts, but nobody returns the following week.&lt;/p&gt;

&lt;p&gt;Suddenly, the problem that seemed straightforward during development becomes difficult to explain. The product exists. The founder believes it solves a real problem. Yet the people who supposedly need it aren't showing up.&lt;/p&gt;

&lt;p&gt;This is where a familiar piece of startup advice enters the conversation: &lt;em&gt;getting your first ten users is harder than building the product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a reason the advice resonates, especially with developers who are building their first businesses. But it also leaves an important question unanswered.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h3&gt;
  
  
  The real question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Is finding those first ten users actually harder than building the product, or does it feel harder because founders have less control over what happens next?&lt;/strong&gt;&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;


&lt;p&gt;The distinction matters. If you misunderstand why people aren't using your product, you can spend months trying to solve the wrong problem.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Jump to a section
  &lt;ol&gt;
&lt;li&gt;Building gives you feedback. Selling gives you silence.&lt;/li&gt;
&lt;li&gt;Stripe's early customers didn't arrive through a signup page&lt;/li&gt;
&lt;li&gt;Ten signups can still mean zero real users&lt;/li&gt;
&lt;li&gt;When the advice is right, and when it isn't&lt;/li&gt;
&lt;li&gt;A quiet launch can mean four different things&lt;/li&gt;
&lt;li&gt;What should you actually do to find the first ten?&lt;/li&gt;
&lt;li&gt;The first ten are not the finish line&lt;/li&gt;
&lt;/ol&gt;



&lt;p&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Building gives you feedback. Selling gives you silence. &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Consider what happens when a developer encounters a difficult technical problem.&lt;/p&gt;

&lt;p&gt;The solution might take hours, days, or even weeks. But there is usually a way to investigate it. You can inspect the logs, reproduce the failure, test a different approach, and gradually narrow down the possibilities.&lt;/p&gt;

&lt;p&gt;Progress isn't guaranteed, but the work often produces information you can use.&lt;/p&gt;

&lt;p&gt;Customer acquisition is different.&lt;/p&gt;

&lt;p&gt;You send twenty carefully written messages and receive two replies. You publish a detailed explanation of your product and nobody clicks through. You demonstrate a feature to someone who says they would love to use it, then never hear from them again.&lt;/p&gt;

&lt;p&gt;You have an outcome, but you don't necessarily know what caused it.&lt;/p&gt;

&lt;p&gt;Perhaps the people you contacted don't have the problem. Perhaps they do, but your message arrived at the wrong time. Perhaps the product is useful but switching to it would take too much effort. Perhaps they simply don't trust an unfamiliar founder with something important to their work.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Those explanations require different responses. Silence alone cannot tell you which one is correct.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is one reason acquisition can feel more frustrating than development. With code, you can often make the next experiment more specific because you understand the system you're investigating. With customers, you are still learning how the system works.&lt;/p&gt;

&lt;p&gt;You are learning what people care about, what they already use, what they will pay to change, and what makes them trust a product they have never tried.&lt;/p&gt;

&lt;p&gt;And you are learning all of this while trying to persuade them to give you their time.&lt;/p&gt;

&lt;p&gt;That is a genuinely difficult problem. But it isn't necessarily a harder problem in every measurable sense. A technically ambitious product might require years of engineering, while a simple product could find its first customers in a few days.&lt;/p&gt;

&lt;p&gt;The more useful distinction:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Building tests...&lt;/th&gt;
&lt;th&gt;Finding customers tests...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The question&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can you create the solution?&lt;/td&gt;
&lt;td&gt;Does the solution matter enough that someone will act on it?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Feedback you get&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Logs, errors, measurable results&lt;/td&gt;
&lt;td&gt;Often silence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who controls the outcome&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mostly you&lt;/td&gt;
&lt;td&gt;Mostly other people&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those are separate questions, and finishing the first does not answer the second.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Stripe's early customers didn't arrive through a signup page &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In his 2013 essay &lt;a href="https://paulgraham.com/ds.html" rel="noopener noreferrer"&gt;&lt;em&gt;Do Things That Don't Scale&lt;/em&gt;&lt;/a&gt;, Paul Graham describes how Patrick and John Collison acquired early users for Stripe.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;When somebody agreed to try the payment integration, the brothers wouldn't simply send a link and wait. They would ask to borrow the person's laptop and help install it immediately.&lt;/p&gt;

&lt;p&gt;Y Combinator came to call this the &lt;strong&gt;"Collison installation."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The approach seems almost excessive now. Why should a founder personally install a product when the whole point of software is to make the experience repeatable?&lt;/p&gt;

&lt;p&gt;Because the founders were solving a problem that a signup page couldn't solve for them.&lt;/p&gt;

&lt;p&gt;A person who agrees to try a product has made a relatively inexpensive commitment. They can change their mind without consequence. Actually installing it requires more effort and brings the product into contact with the person's real workflow.&lt;/p&gt;

&lt;p&gt;By helping with the installation, the founders removed friction and discovered what happened when someone tried to use Stripe for real.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Context matters.&lt;/strong&gt; Stripe was addressing a concrete problem for developers and businesses, and its early founders had access to a network of potential customers through the startup ecosystem. Their approach does not prove that every product can acquire customers by offering personal onboarding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does show something more practical: sometimes the obstacle between interest and usage is not a lack of awareness. &lt;strong&gt;It is the work required to get started.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What that looks like in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer tool:&lt;/strong&gt; help someone integrate it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Software for accountants:&lt;/strong&gt; import a customer's existing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A service:&lt;/strong&gt; deliver the first result personally before asking for a longer commitment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These approaches do not scale indefinitely. That is precisely why they can be valuable at the beginning.&lt;/p&gt;

&lt;p&gt;Before automating an onboarding process, a founder needs to understand where people struggle with it. Before spending heavily on marketing, they need to know what happens after somebody responds.&lt;/p&gt;

&lt;p&gt;The first ten customers can be valuable for the revenue they bring in. They can also reveal problems that a founder working alone would never have discovered.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Ten signups can still mean zero real users &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There is another problem with the way founders talk about their first users: the word &lt;em&gt;user&lt;/em&gt; can describe several very different situations.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Someone visits the landing page.&lt;/li&gt;
&lt;li&gt;Someone else joins the waitlist.&lt;/li&gt;
&lt;li&gt;A third person creates an account, opens the dashboard, and leaves.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three might appear in a growth report. None necessarily demonstrates that the product is delivering its promised value.&lt;/p&gt;

&lt;p&gt;This distinction is more than a matter of terminology.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://review.firstround.com/how-superhuman-built-an-engine-to-find-product-market-fit/" rel="noopener noreferrer"&gt;his account of Superhuman's product-market-fit process&lt;/a&gt;, founder Rahul Vohra described a company that had spent years building its email product but still needed a clearer way to measure whether users genuinely valued it.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Superhuman surveyed people who had used the product at least twice in the preceding two weeks. It asked how they would feel if they could no longer use it.&lt;/p&gt;

&lt;p&gt;Only &lt;strong&gt;22%&lt;/strong&gt; said they would be very disappointed.&lt;/p&gt;

&lt;p&gt;That result mattered because these weren't simply people who had heard about Superhuman. They had already experienced the product. Yet most did not consider it indispensable.&lt;/p&gt;

&lt;p&gt;The team used the survey responses to identify the people who valued the product most, understand what those people appreciated, and make improvements intended to strengthen product-market fit. Vohra subsequently reported that the score rose from 22% to 58%.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- 22%  "very disappointed"  (before)
&lt;/span&gt;&lt;span class="gi"&gt;+ 58%  "very disappointed"  (after)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;58%&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;22%&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;36&lt;/span&gt;&lt;span class="mord text"&gt;&lt;span class="mord"&gt;&amp;nbsp;percentage&amp;nbsp;points&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;



&lt;p&gt;&lt;/p&gt;
  What this example does and doesn't establish
  &lt;p&gt;There are limits to what this example establishes. The survey is a useful diagnostic, not a universal scientific test that can independently prove product-market fit. The results came from Superhuman's particular audience and product, and a high score does not guarantee a sustainable business.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Still, the underlying problem is important.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Getting somebody to sign up and getting somebody to care whether your product disappears are not the same achievement.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a founder trying to find ten users, this means the milestone needs a definition.&lt;/p&gt;

&lt;p&gt;Ten registered accounts might be an appropriate early milestone for testing whether people will respond to an offer. Ten people who repeatedly use a core feature tell you something different. Ten paying customers tell you something different again.&lt;/p&gt;

&lt;p&gt;None of those numbers is automatically the right target. The right target depends on what the product is supposed to accomplish.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product type&lt;/th&gt;
&lt;th&gt;A meaningful signal might be&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool used every morning&lt;/td&gt;
&lt;td&gt;Returning frequently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quarterly accounting software&lt;/td&gt;
&lt;td&gt;Completing the quarterly task (daily usage would be a strange expectation)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-time service&lt;/td&gt;
&lt;td&gt;Successful completion and willingness to recommend the provider&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A founder should measure the behaviour that demonstrates value, rather than assume every product needs the same pattern of engagement.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://review.firstround.com/how-superhuman-built-an-engine-to-find-product-market-fit/" class="crayons-btn crayons-btn--primary" rel="noopener noreferrer"&gt;Read the Superhuman product-market-fit story&lt;/a&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  4. When the advice is right, and when it isn't &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The claim that customer acquisition is harder than building is most convincing when a founder has a working product but no reliable way to reach people who need it.&lt;/p&gt;

&lt;p&gt;Imagine a developer who builds a scheduling tool for freelance designers. The technical work is manageable. The product works. But the developer has no existing audience among designers, no relationships with design agencies, and no idea where these freelancers look for tools.&lt;/p&gt;

&lt;p&gt;The product might be finished in a month. Finding a group of people willing to try it, learning what they actually need, and discovering whether any of them will pay could take much longer.&lt;/p&gt;

&lt;p&gt;The developer isn't necessarily struggling because the product is bad. They are trying to solve a problem for a group of people they don't yet understand well enough.&lt;/p&gt;

&lt;p&gt;Now consider a different founder: an accountant who builds a small tool to automate a tedious task that several existing clients already complain about. The founder knows the workflow, has access to the intended users, and can test the tool with people who experience the problem every week.&lt;/p&gt;

&lt;p&gt;The technical challenge could be the hardest part of that business. Finding the first few users might be comparatively straightforward.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Developer building for designers&lt;/th&gt;
&lt;th&gt;Accountant building for own clients&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Existing audience&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Knows the workflow&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Likely hardest part&lt;/td&gt;
&lt;td&gt;Finding users&lt;/td&gt;
&lt;td&gt;Building the tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Neither example is unusual enough to support a universal rule. Together, they show why the headline depends on the founder, the market, and the product.&lt;/p&gt;

&lt;p&gt;Existing distribution changes the equation, too. Someone with an established audience, a respected reputation in a niche, or a network of potential buyers isn't starting from the same position as someone nobody knows.&lt;/p&gt;

&lt;p&gt;That advantage is real. It doesn't mean their product is better, or that their acquisition strategy is automatically repeatable. It means some of the work required to earn attention happened before the product was launched.&lt;/p&gt;

&lt;p&gt;The same qualification applies to technically novel products. If a founder doesn't yet know whether a proposed system can work at an acceptable cost, speed, or level of reliability, engineering experiments may need to come first. Talking to potential customers cannot resolve every question about technical feasibility.&lt;/p&gt;

&lt;p&gt;The mistake is treating either building or customer acquisition as the correct first step for every startup.&lt;/p&gt;

&lt;p&gt;Sometimes the founder needs to write more code. Sometimes they need to speak to potential customers before writing another line. Often, they need to do both, using what they learn from one to inform the other.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. A quiet launch can mean four different things &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose you launch your product and, after several weeks, you still haven't found ten meaningful users.&lt;/p&gt;

&lt;p&gt;The tempting response is to do more marketing. Post in more communities. Send more messages. Try another launch platform.&lt;/p&gt;

&lt;p&gt;That might help. But first, consider where the process is breaking.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A[Launch] --&amp;gt; B{Right people&amp;lt;br/&amp;gt;seeing it?}
    B -- No --&amp;gt; C[1. Reach problem]
    B -- Yes --&amp;gt; D{Taking the&amp;lt;br/&amp;gt;next step?}
    D -- No --&amp;gt; E[2. Offer problem]
    D -- Yes --&amp;gt; F{Reaching the&amp;lt;br/&amp;gt;main benefit?}
    F -- No --&amp;gt; G[3. Activation problem]
    F -- Yes --&amp;gt; H{Returning&amp;lt;br/&amp;gt;or paying?}
    H -- No --&amp;gt; I[4. Retention problem]
    H -- Yes --&amp;gt; J[Keep going]&lt;/code&gt;&lt;/pre&gt;



&lt;h3&gt;
  
  
  1. The right people aren't seeing the product
&lt;/h3&gt;

&lt;p&gt;Perhaps the product solves a genuine problem, but your potential customers don't spend time on the channels you're using. Or perhaps you're targeting such a broad audience that your message never feels relevant to anyone in particular.&lt;/p&gt;

&lt;p&gt;In this case, changing the product may accomplish very little. You need to understand where the intended customers already look for solutions and how to reach them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. People see the product but don't take the next step
&lt;/h3&gt;

&lt;p&gt;This could mean the value proposition is unclear. It could also mean the problem isn't urgent, the price is too high, the proposed solution feels risky, or the person reading your message doesn't have the authority to make a purchase.&lt;/p&gt;

&lt;p&gt;It is worth asking what your offer requires someone to believe before they act. An unfamiliar product asking for access to sensitive business data has a much larger trust problem than a free tool that works entirely in the browser.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. People try the product but never reach its main benefit
&lt;/h3&gt;

&lt;p&gt;Perhaps setup takes too long. Maybe the first screen asks users to configure five things before they can accomplish anything useful. The product might even work exactly as intended, but the value isn't apparent to a new user.&lt;/p&gt;

&lt;p&gt;This is where watching someone use the product can reveal more than another round of promotional posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. People get value once but don't return or pay
&lt;/h3&gt;

&lt;p&gt;This is the most uncomfortable possibility because the founder has evidence that the product can work for someone, yet the behaviour isn't continuing.&lt;/p&gt;

&lt;p&gt;The problem might be retention, pricing, a low-frequency need, or a mismatch between the person using the product and the person expected to pay. It could also mean the product is useful but not important enough to replace an existing habit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Not a complete diagnosis.&lt;/strong&gt; These four categories aren't exhaustive, and the boundaries between them aren't always clean. A confusing product can look like a marketing problem. A weak offer can look like a distribution problem. Low retention can reflect a product that works perfectly but solves a problem people rarely encounter.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The point is not to label every failure immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It is to stop treating every failure as the same failure.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. What should you actually do to find the first ten? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The best starting point is smaller than a launch campaign.&lt;/p&gt;

&lt;p&gt;Choose a specific group of people who already experience the problem your product is intended to solve. Be precise enough that you can identify actual individuals, rather than an enormous category of potential customers.&lt;/p&gt;

&lt;p&gt;Then investigate what they do today.&lt;/p&gt;

&lt;p&gt;If you're building invoice software for freelancers, find out how those freelancers currently create invoices, follow up on unpaid bills, and keep track of their income. Ask what happens when the process goes wrong. Look for examples of real frustration, time lost, money lost, or workarounds people have already built.&lt;/p&gt;

&lt;p&gt;Steve Blank's &lt;a href="https://steveblank.com/category/customer-development/" rel="noopener noreferrer"&gt;writing on customer development&lt;/a&gt; makes a useful distinction here: founders begin with hypotheses about customers and their needs, then test those hypotheses through conversations and experiments.&lt;sup id="fnref3"&gt;3&lt;/sup&gt; The goal is to discover what is true, not simply to collect encouraging responses.&lt;/p&gt;

&lt;p&gt;That means asking about actual behaviour is usually more informative than asking whether somebody likes your idea.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  Example: weak evidence vs. strong evidence
  &lt;p&gt;A person may tell you they would happily use an invoicing tool. &lt;strong&gt;That costs them nothing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finding out what they currently use, whether they have paid for alternatives, and whether they'll try your product with a real invoice gives you stronger evidence.&lt;/p&gt;



&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Once you understand the problem, offer a small, concrete next step.&lt;/p&gt;

&lt;p&gt;Don't necessarily ask someone to adopt your entire product. Ask whether they'll try one feature, bring one real task into it, or spend twenty minutes showing you how they currently handle the problem.&lt;/p&gt;

&lt;p&gt;If the product is ready, help them use it. If it isn't, a prototype or a manually delivered version of the service might be enough to test the underlying assumption, provided you don't misrepresent what you've built.&lt;/p&gt;

&lt;h3&gt;
  
  
  Track what happens next
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What to record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reached&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did the intended person actually see the offer?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Responded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did they reply or ask a meaningful question?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tried&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did they attempt the real task?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did they reach the product's core value?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Returned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did they use it again when the need arose?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Committed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Did they pay, renew, recommend it, or make another meaningful commitment?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not every product needs all six stages, and not every commitment looks like a payment. The table is a way to make the process observable, not a universal funnel that every business must follow.&lt;/p&gt;

&lt;p&gt;Keep notes on why people stop, preferably in their own words. A polite refusal and a detailed objection are not equivalent. Neither is the same as someone who tries the product, gets value from it, and simply doesn't need it again until next month.&lt;/p&gt;

&lt;p&gt;Then choose the next experiment based on what you learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The wrong people are seeing the offer: &lt;strong&gt;change the audience or channel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] The right people understand the offer but don't care enough to try it: &lt;strong&gt;investigate the problem and the value proposition&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] They try the product but fail to reach its main benefit: &lt;strong&gt;work on that experience&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;[ ] They get value but don't return or pay: &lt;strong&gt;investigate the frequency, importance, and economics of the problem&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two weeks can be enough to uncover useful patterns when the target customers are accessible and the product delivers value quickly. It may be nowhere near enough for a business with a long sales cycle or a problem that occurs only a few times a year.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The objective isn't to promise ten users within fourteen days. It is to finish the experiment knowing more than you did when you started.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  7. The first ten are not the finish line &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;There is a reason founders remember their first users. Each one represents a decision that happened outside the codebase: somebody noticed the product, considered it relevant, decided to try it, and perhaps found enough value to return.&lt;/p&gt;

&lt;p&gt;But the number ten can be misleading.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Ten users who...&lt;/th&gt;
&lt;th&gt;...might tell you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;signed up because a friend asked&lt;/td&gt;
&lt;td&gt;Very little about whether strangers will pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;independently share the same painful problem and pay to solve it&lt;/td&gt;
&lt;td&gt;A great deal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;never return&lt;/td&gt;
&lt;td&gt;A product problem that another hundred signups would only make harder to ignore&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The work isn't finished when the tenth person arrives. That's often when the next question becomes possible.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you find another ten people with the same problem?&lt;/li&gt;
&lt;li&gt;Can you reach them through a process you understand?&lt;/li&gt;
&lt;li&gt;Do they experience enough value to keep using the product?&lt;/li&gt;
&lt;li&gt;Does acquiring and serving them make economic sense?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions are harder to answer than whether a product can attract ten signups, and they matter more to the future of the business.&lt;/p&gt;

&lt;p&gt;So, is getting your first ten users harder than building the product?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sometimes.&lt;/strong&gt; Especially when the founder understands the technology but knows very little about the people who might buy it. But the comparison becomes less useful when it turns into a universal rule, or when it encourages founders to treat engineering and customer discovery as competing activities.&lt;/p&gt;

&lt;p&gt;A product can be technically excellent and commercially irrelevant. It can also be commercially promising before its engineering is finished. The difficult work is finding out which situation you're in while there is still time to change direction.&lt;/p&gt;

&lt;p&gt;That begins with a more useful question than how to get ten users.&lt;/p&gt;


&lt;div class="crayons-card c-embed"&gt;

  
&lt;h3&gt;
  
  
  Your next question
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What would you need to learn from the next person who tries your product to know what to do next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Share your answer in the comments.&lt;br&gt;

&lt;/p&gt;
&lt;/div&gt;





&lt;p&gt;&lt;em&gt;The Startup Café — Real Startup Lessons. Beyond the Hype.&lt;/em&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Paul Graham, "Do Things That Don't Scale," 2013. The essay is where the Collison installation story comes from.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Rahul Vohra's account on the First Round Review. The 22% and 58% figures come from Superhuman's own survey of its own users, so treat them as one company's data point.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Steve Blank's customer development writing is a collection of posts, so start with the ones on customer discovery and hypothesis testing.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>career</category>
      <category>webdev</category>
      <category>discuss</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Should You Build an MVP Before Your First Customer?</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Sun, 25 Oct 2026 12:52:03 +0000</pubDate>
      <link>https://dev.to/startupcafe/should-you-build-an-mvp-before-your-first-customer-4242</link>
      <guid>https://dev.to/startupcafe/should-you-build-an-mvp-before-your-first-customer-4242</guid>
      <description>&lt;p&gt;&lt;em&gt;Some questions about a startup idea can be answered with a conversation or a cheap web page. Others need a working product. A lot of wasted effort comes from mixing the two up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Joel Gascoigne started coding Buffer before he'd checked that anyone wanted it. He says so himself, in a &lt;a href="https://buffer.com/resources/idea-to-paying-customers-in-7-weeks-how-we-did-it" rel="noopener noreferrer"&gt;February 2011 post&lt;/a&gt; about getting from idea to paying customers. Partway in he stopped and put up a two-page site instead. Then he wedged a pricing page between the two pages, so a visitor had to click a plan before leaving an email address for a launch notice. A small number of people clicked paid plans, and that was enough to send him back to the code. Seven weeks of evenings and weekends later he launched, on 30 November, and by his account the first paying customer turned up within four days.&lt;/p&gt;

&lt;p&gt;Figma went the other way. Its &lt;a href="https://www.figma.com/blog/design-meet-the-internet/" rel="noopener noreferrer"&gt;launch post&lt;/a&gt; talks about three years of silence. Dylan Field has said that when Figma raised its first round, &lt;a href="https://blog.southparkcommons.com/p/accessible-design-for-a-remote-world-in-conversation-w-dylan-field-of-figma" rel="noopener noreferrer"&gt;it had no user metrics&lt;/a&gt; and the product was still in stealth.&lt;/p&gt;

&lt;p&gt;People cite both stories as proof of opposite rules: test first, or build first and let the market judge the finished thing. Both founders ran companies that survived, so neither story tells you much about the odds. Put side by side, though, they show two founders stuck on different questions. This article is about working out which question you're stuck on, and what the cheapest test of it would be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two risks under one label
&lt;/h2&gt;

&lt;p&gt;Rob Fitzpatrick's &lt;em&gt;The Mom Test&lt;/em&gt; splits risk in two, at least as &lt;a href="https://www.supersummary.com/the-mom-test/summary/" rel="noopener noreferrer"&gt;summarised by study guides&lt;/a&gt;. Customer risk asks whether people want this and will pay. Product risk asks whether it can be built. When most of the risk sits in the product, those summaries say, no amount of customer conversation settles it. His examples include video games and a farm fertility monitor.&lt;/p&gt;

&lt;p&gt;Buffer was mostly customer risk. Queuing tweets wasn't technically scary; the open question was whether anyone cared. Figma's early browser-based design tool is a simplified example of the opposite. People could picture wanting it, and nobody knew whether a browser could do the job. That contrast is mine, not something any of the sources says. Real ideas usually carry some of each, so the practical question is which one is the bigger unknown right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not all evidence weighs the same
&lt;/h2&gt;

&lt;p&gt;Here is what common signals can and can't tell you.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you got&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;th&gt;What it doesn't&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A compliment&lt;/td&gt;
&lt;td&gt;Goodwill, maybe real interest&lt;/td&gt;
&lt;td&gt;Behaviour, or intent to buy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An interview about what they do today&lt;/td&gt;
&lt;td&gt;This person has the problem and how they cope&lt;/td&gt;
&lt;td&gt;That the market is big, or that they'd switch to you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A signup or waitlist&lt;/td&gt;
&lt;td&gt;Your message landed in that channel&lt;/td&gt;
&lt;td&gt;That they'd use it or pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A pricing-page click&lt;/td&gt;
&lt;td&gt;Which plan caught their eye&lt;/td&gt;
&lt;td&gt;That anyone would pay. Nobody did&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A signed pilot or letter of intent&lt;/td&gt;
&lt;td&gt;Commitment, as strong as its terms (scope, date, price). Some letters are non-binding&lt;/td&gt;
&lt;td&gt;That it holds up beyond the pilot&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A payment&lt;/td&gt;
&lt;td&gt;Willingness to pay for this offer, under these conditions&lt;/td&gt;
&lt;td&gt;Renewal, or whether a discount, a friendship or a one-off need did it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Repeat usage belongs on that list too. It's strong evidence for a product people are meant to use often, and you need a product before you can see it.&lt;/p&gt;

&lt;p&gt;Fitzpatrick's own test, again via summaries, is whether a conversation ends in &lt;a href="https://mtlynch.io/book-reports/the-mom-test/" rel="noopener noreferrer"&gt;commitment or advancement&lt;/a&gt;: the other person gives up time, reputation or money, or agrees to a next step. A compliment costs nothing to give. Treat it as a weak signal until behaviour backs it up.&lt;/p&gt;

&lt;p&gt;Don't read the table as a ladder. A payment for a service you deliver by hand says a lot about willingness to pay and very little about whether people will put up with your software. The strongest evidence is whichever kind matches the claim you're testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can test without a product
&lt;/h2&gt;

&lt;p&gt;Interviews about past behaviour tell you whether the problem is real and how people cope. They're poor at predicting what someone will do with something new, and they're easy to steer without meaning to. A landing or pricing page tests whether a message pulls attention in one channel, and it tests willingness to pay only if money changes hands. A manual or concierge service shows what the job really involves and gives you honest data on price, but it hides everything about scale. A demo video or clickable prototype helps when an idea is hard to explain and says nothing about retention. A scoped paid pilot, a pre-order (with explicit delivery and refund terms) or a letter of intent gets you commitment from a specific buyer, though early buyers often differ from later ones. A technical spike tells you whether the hard part is possible and nothing about demand.&lt;/p&gt;

&lt;p&gt;Look at Buffer with that in mind. The pricing-page click wasn't a payment, and the product had little technical risk. Gascoigne is open that his early "validated" was a handful of emails and some feedback. The better evidence, a paying customer, arrived after he'd built and launched. So the sequence wasn't "validate, then build." He ran a cheap test for customer risk, then built the smallest thing that could produce stronger evidence.&lt;/p&gt;

&lt;p&gt;Food on the Table shows the manual route. In accounts of the Lean Startup method, an early customer got weekly personal service in place of software. Manuel Rosso's &lt;a href="https://www.slideshare.net/slideshow/manuel-rosso-food-on-the-table/7239850" rel="noopener noreferrer"&gt;talk on concierge MVPs&lt;/a&gt; frames that service as a way to learn rather than to scale, and advises writing code only when it makes learning cheaper. The takeaway is narrow. Manual delivery shows what the job involves and whether people accept a high-touch version. It can't tell you whether software will do the job.&lt;/p&gt;

&lt;p&gt;One rule covers every test before a product exists: say what doesn't exist yet. A landing page should describe what customers will get and when, and if you take money, the refund terms should be plain. Buffer's pricing page collected emails for a launch notice, which is a long way from pretending the product was live.&lt;/p&gt;

&lt;h2&gt;
  
  
  When building is the experiment
&lt;/h2&gt;

&lt;p&gt;Some questions don't yield to conversation. Two cases show how, and both come with the usual survivor caveat.&lt;/p&gt;

&lt;p&gt;Dropbox is awkward to describe and obvious once you see it. Drew Houston's own &lt;a href="https://www.slideshare.net/vishaltx/dropbox-startup-lessons-learned-16991502" rel="noopener noreferrer"&gt;slide deck&lt;/a&gt; lists a three-minute screencast on Hacker News in April 2007 that brought "lots of immediate, high-quality feedback", then a private-beta video in March 2008 after which the waitlist jumped from 5,000 to 75,000 in a day. In a &lt;a href="https://techcrunch.com/2011/11/01/founder-storie-how-dropbox-got-its-first-10-million-users/" rel="noopener noreferrer"&gt;2011 interview&lt;/a&gt; he said they'd expected 15,000 at most, and that they used a closed beta because they didn't want to risk a buggy product on all 75,000. One secondary account says &lt;a href="https://stayscrappy.substack.com/p/how-dropbox-got-its-first-users-with" rel="noopener noreferrer"&gt;a working prototype&lt;/a&gt; sat behind the first video. I couldn't confirm that from Houston himself. What the evidence supports is narrower: a demo helped communicate a hard-to-explain product and showed interest among early adopters. It doesn't show willingness to pay, retention, or that the audience generalised. A waitlist is interest, not commitment.&lt;/p&gt;

&lt;p&gt;Figma is the cleaner case. The open question was whether a professional design tool could run in a browser at acceptable speed. Field says people who had tried WebGL before &lt;a href="https://blog.southparkcommons.com/p/accessible-design-for-a-remote-world-in-conversation-w-dylan-field-of-figma" rel="noopener noreferrer"&gt;warned them off&lt;/a&gt;. According to &lt;a href="https://www.sequoiacap.com/?p=3624" rel="noopener noreferrer"&gt;Sequoia's profile&lt;/a&gt;, the closed beta came in December 2015 and the first dollar was charged in summer 2017. Interviews couldn't settle feasibility. That took technical experiments and a working prototype, whatever customer learning went on alongside. It was also affordable only because of a founding pair with deep graphics skills and investors willing to fund years in stealth. Few solo founders have both.&lt;/p&gt;

&lt;p&gt;What the two cases share, and this is my inference, is that building was justified by one specific unanswered question. If your riskiest assumption can't be tested credibly without a working prototype, build, and build only the part that tests it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How much validation is enough?
&lt;/h2&gt;

&lt;p&gt;There's no credible number. The nearest research is a line of randomised trials by Camuffo, Gambardella, Spina and colleagues. In the &lt;a href="https://doi.org/10.1287/mnsc.2018.3249" rel="noopener noreferrer"&gt;2020 trial&lt;/a&gt; (Management Science, 116 Italian startups in the paper's panel sample), some founders were trained to treat their idea as a theory, form hypotheses and test them, while a control group followed its instincts. The authors &lt;a href="https://iris.unibocconi.it/handle/11565/4013977" rel="noopener noreferrer"&gt;report&lt;/a&gt; that the treated group pivoted more and earned more revenue.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://doi.org/10.1002/smj.3580" rel="noopener noreferrer"&gt;2024 replication&lt;/a&gt; in the Strategic Management Journal pooled four randomised trials covering 759 firms. Its abstract reports a positive effect on idea termination and a nonlinear effect on radical pivots: treated firms ran a few pivots rather than none or many. The authors read this as more efficient search plus more "methodic doubt". So the 2020 headline, more pivoting, didn't hold up as stated. The clearer result is more decisive stopping and more deliberate changes.&lt;/p&gt;

&lt;p&gt;Both are training programmes for early-stage founders, and neither compares interview-first with MVP-first. They support something narrower: tests built to be able to say no seem to help founders decide. That reading is mine, not a conclusion the authors draw about MVPs.&lt;/p&gt;

&lt;p&gt;In practice four things move the answer: what a wrong build would cost, how easily you could undo it, how reachable your customers are, and how much is still unknown. A weekend project you can bin needs very little evidence. Six months of work for buyers you can't easily reach needs a lot more.&lt;/p&gt;

&lt;h2&gt;
  
  
  A way to decide (my synthesis, not an established method)
&lt;/h2&gt;

&lt;p&gt;Five questions, in order. Skip any you can already answer with evidence.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Which assumption is riskiest?&lt;/strong&gt; That the problem exists, that a specific group cares, that your solution works, that it's feasible, or that they'll pay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's the cheapest test that could come back negative?&lt;/strong&gt; A conversation, a manual service, a prototype, a technical spike, a pilot, a payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What result would change your mind?&lt;/strong&gt; Write it down before you run the test. If no result would make you stop or change course, you aren't testing anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's still unknown afterwards?&lt;/strong&gt; Three paid pilots say nothing about what the fourth customer costs to find.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's the next smallest step?&lt;/strong&gt; Keep learning, build a limited version, run a paid pilot, change the customer, or stop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A rough rule for interview versus experiment: when several people independently describe the same recent problem and workaround, consider moving from broad discovery to a more specific test. More interviews can still help if you're exploring a different segment or an unresolved objection. Otherwise the next useful question may be whether someone will commit to a concrete step.&lt;/p&gt;

&lt;h2&gt;
  
  
  An experiment for a solo founder: the scoped pilot ask
&lt;/h2&gt;

&lt;p&gt;This suits a B2B or prosumer idea.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hypothesis:&lt;/strong&gt; People in one narrow segment who already spend time or money working around this problem will agree to a specific, paid pilot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test:&lt;/strong&gt; Talk to people who've shown the problem in past behaviour. Describe exactly what they'd get, by when, and what isn't built. Ask for a concrete step: a start date, a price, or a deposit with clear refund terms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Before you start:&lt;/strong&gt; Write down how many yeses would make you proceed and how many would make you rethink. Choose a threshold that fits your market, your access to buyers and the cost of being wrong. Don't borrow a universal number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record:&lt;/strong&gt; For each person, their current workaround, what it costs them, what they said (paid, agreed in writing, asked to wait, declined) and why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits:&lt;/strong&gt; You'll mostly reach people like you, a pilot price isn't a product price, and delivering by hand hides software costs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next decision:&lt;/strong&gt; A yes counts when it's a defined step, such as an accepted scope, timeline and price, not verbal encouragement. Enough of those means deliver by hand and note which steps repeat, since those are what to automate. Repeated "interesting, not now" means urgency or the offer hasn't been shown. Ask why: wrong buyer, unclear offer, budget timing, missing trust, or a problem that isn't pressing. Then narrow the segment, change the offer, or stop.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Back to Buffer and Figma
&lt;/h2&gt;

&lt;p&gt;The two stop looking like opposites once you ask what each founder didn't know. Gascoigne didn't know whether people cared, and a small page could probe that. Field didn't know whether it was possible, and that took building. Both are survivors telling their own stories, so hold the conclusions loosely.&lt;/p&gt;

&lt;p&gt;Before your next coffee chat or your next hour in the editor, write down the assumption most likely to sink you, and pick the cheapest test that could prove it wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your counterexample?&lt;/strong&gt; Have you built first and been right to, or run a validation test that said yes while the product still failed? What did you measure, and what would you do differently?&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__15427"&gt;
  &lt;a href="/startupcafe" class="ltag__user__link profile-image-link"&gt;
    &lt;div class="ltag__user__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15427%2F3acf9cd5-6b15-46e1-91e5-bda4b2625e0b.jpg" alt="startupcafe image"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
      &lt;a href="/startupcafe" class="ltag__user__link"&gt;The Startup Café&lt;/a&gt;
      Follow
    &lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a href="/startupcafe" class="ltag__user__link"&gt;
        A community for startup founders, indie hackers, and developers to share projects, get feedback, find co-founders, and grow together. Grab a coffee and let's build something great!
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;



&lt;div class="ltag__user ltag__user__id__3615540"&gt;
    &lt;a href="/hanzla" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" alt="hanzla image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/hanzla"&gt;Hanzla Baig&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hanzla"&gt;👨‍💻 I turn coffee-free ideas into bugs 🐛  
🚀 Co-founder @ TheBitForge  
✍️ Sharing dev adventures, wins &amp;amp; fails  
🇵🇰 Building, breaking &amp;amp; learning daily 😅&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>programming</category>
      <category>startup</category>
      <category>discuss</category>
    </item>
    <item>
      <title>"Build in Public" Is Everywhere. Does It Actually Get You Customers? 🤔</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Thu, 22 Oct 2026 15:41:56 +0000</pubDate>
      <link>https://dev.to/startupcafe/build-in-public-is-everywhere-does-it-actually-get-you-customers-2hlh</link>
      <guid>https://dev.to/startupcafe/build-in-public-is-everywhere-does-it-actually-get-you-customers-2hlh</guid>
      <description>&lt;p&gt;&lt;em&gt;What founders report, what that evidence can't prove, and a four-week experiment you can run on your own product.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In 2023, the founders of Senja, a customer-testimonial tool, noticed how much attention revenue screenshots were getting in the build-in-public community. They gave the makers who focused on milestones a nickname: "milestone men." Then they started sharing losses and context alongside their wins.&lt;/p&gt;

&lt;p&gt;They weren't skeptics, though. In the &lt;a href="https://buildinpublichub.substack.com/p/how-i-built-this-in-public-olly" rel="noopener noreferrer"&gt;same interview&lt;/a&gt;, co-founder Olly said one in four of Senja's paying customers came from Twitter and building in public.&lt;/p&gt;

&lt;p&gt;So one practice earned applause for some founders and, by one founder's account, paying customers for another. This article is about the gap between those outcomes. The question isn't whether building in public is good or bad. It's what has to be true between a post getting attention and a stranger paying for your software.&lt;/p&gt;

&lt;p&gt;We'll look at what attention metrics can and can't tell you, then three documented accounts: a modest success, a spectacular one, and a failure. Then I'll explain why even those are weaker evidence than they look. Finally, we'll turn the patterns into an experiment small enough for a solo founder to run without losing a quarter.&lt;/p&gt;

&lt;h2&gt;
  
  
  One phrase, four different activities
&lt;/h2&gt;

&lt;p&gt;"Building in public" gets used for at least four things that behave differently.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Progress sharing:&lt;/strong&gt; changelogs, screenshots, technical decisions. Mostly interesting to people who care about the process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Metrics sharing:&lt;/strong&gt; revenue, signups, churn. It builds credibility, and as the Senja founders noticed, it can easily turn into a performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Teaching:&lt;/strong&gt; posts that solve a reader's problem whether or not they ever buy. This is content marketing under a founder's name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversation:&lt;/strong&gt; asking users for feedback and answering people who have the problem you solve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This split is my own, not an industry standard. It matters because the activities tend to attract different kinds of attention. Progress and metrics posts often draw people who like following founders. Teaching and conversation tend to draw people with the problem. Those groups sometimes overlap, but often they don't, and the product, audience, and platform all change the picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a like measures
&lt;/h2&gt;

&lt;p&gt;A founder's funnel has more stages than a social feed shows. Treat this table as a simplified model, not a strictly linear customer journey:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;What you can count&lt;/th&gt;
&lt;th&gt;What it tells you&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Attention&lt;/td&gt;
&lt;td&gt;Impressions, likes, followers&lt;/td&gt;
&lt;td&gt;People saw it and reacted. Not proof of buying intent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interest&lt;/td&gt;
&lt;td&gt;Replies and DMs from people who fit your buyer profile, email signups&lt;/td&gt;
&lt;td&gt;Someone with the problem wants more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trial&lt;/td&gt;
&lt;td&gt;Signups, then activated users (these are different things)&lt;/td&gt;
&lt;td&gt;Someone tried the product, and maybe got value from it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue&lt;/td&gt;
&lt;td&gt;Paying customers&lt;/td&gt;
&lt;td&gt;Someone valued it enough to pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durability&lt;/td&gt;
&lt;td&gt;Retention, or repeat purchases for non-subscription products&lt;/td&gt;
&lt;td&gt;The value held up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not everyone who sees a post moves to the next stage, and many never do. How big the drop-off is depends on your audience, your offer, and your channel. A post can do brilliantly at the top while adding nothing at the bottom. If your audience is mostly other founders and you sell to dentists, a viral thread is a pleasant event with no commercial consequence. Your mom will be very proud, though. 🦷&lt;/p&gt;

&lt;p&gt;There's a complication in the other direction. Influence doesn't always show up as a click. Olly said far more happens in DMs than people assume, especially around launches. Readers who never click a link may still remember your name later. That's an argument for tracking conversations, not just clicks, and for asking new users how they found you. Self-reported attribution is imperfect, but it beats assuming the answer is "direct."&lt;/p&gt;

&lt;h2&gt;
  
  
  Three accounts, three different lessons
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Senja: customers, with caveats.&lt;/strong&gt; The interview ran in March 2023, when Senja had reached $4,000 in monthly recurring revenue in under a year. Olly reported that one in three signups and one in four paying customers came from Twitter and building in public, and that he spent about seven hours a week on it. Note that those figures combine Twitter and building in public, so they don't isolate the effect of building in public itself. He called it, along with SEO, the best-return marketing work they did. He also listed conditions that cut against the hype. It's easier if you're making a product for other makers, it can take months to generate sales even then, and he wasn't starting from zero, since he already had a small audience from an earlier project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RB2B: the spectacular case.&lt;/strong&gt; Adam Robinson's account is the dramatic one. Interviews, including a &lt;a href="https://pod.co/product-led-podcast/how-adam-robinson-scaled-rb2b-to-5m-arr-in-13-months" rel="noopener noreferrer"&gt;ProductLed podcast episode&lt;/a&gt;, report that he took RB2B, a website-visitor identification product, to $5 million in annual recurring revenue in about 13 months with a tiny team, using LinkedIn heavily for awareness. A &lt;a href="https://www.trueframe.xyz/blog/adam-robinson-linkedin-funnel" rel="noopener noreferrer"&gt;Trueframe write-up&lt;/a&gt; of his published figures (Trueframe is a content agency, so read it with that in mind) says he attributed about 75% of site traffic to LinkedIn in an October 2025 snapshot. It also notes that his own launch retrospectives disagree on basics, such as 1,600 waitlist members in one telling versus 3,000 free signups in another, and that meme content drew attention from people outside his buyer group. Now look at what surrounds those numbers. He already ran a larger, established company, his LinkedIn audience was made of SaaS operators, and RB2B sells to SaaS operators. That's less "I posted memes and got rich" and more "I posted to the exact people who buy." Treat all of this as his claims, not audited results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A six-month failure.&lt;/strong&gt; A founder &lt;a href="https://www.indiehackers.com/post/i-validated-my-idea-with-almost-80-people-built-in-public-for-6-months-and-still-failed-spectacularly-6382ec2034" rel="noopener noreferrer"&gt;wrote on Indie Hackers&lt;/a&gt; in September 2025 about posting daily updates, sharing the journey on Reddit, and ending up with 43 signups, 2 paying customers, and $18 in monthly revenue. Their own diagnosis was that the people cheering them on were supporters, not people with the problem. It's a single self-reported anecdote, and the post ends by linking the author's product, so weigh it accordingly. It's also as much a story about weak demand and validation as about building in public. Lots of cheering, very little paying.&lt;/p&gt;

&lt;h2&gt;
  
  
  What these accounts can't tell us
&lt;/h2&gt;

&lt;p&gt;These stories are interesting, but they don't prove that building in public causes customers. Here's why.&lt;/p&gt;

&lt;p&gt;First, every account is self-reported. Nobody audited these numbers, and an interview with a founder is a founder describing their own business.&lt;/p&gt;

&lt;p&gt;Second, none of them isolates building in public from everything else. Senja credits SEO alongside Twitter. Robinson had an existing company and a reputation among his buyers. The failed project may simply have lacked a real problem to solve. The strategy might be the cause, a contributor, or just a companion to something else, and a story told after the fact can't separate those.&lt;/p&gt;

&lt;p&gt;Third, we mostly hear from the visible. Founders who posted for a year, got nothing, and moved on rarely write retrospectives. That's my inference, not a measured fact. I couldn't find a controlled comparison of founders who build in public and similar founders who don't. If you see a precise "founders who build in public grow X times faster" statistic, ask where the denominator came from.&lt;/p&gt;

&lt;p&gt;The three cases don't produce the same outcome, but they share a thread. Where the strategy visibly worked, the audience was close to the buyer. That's suggestive, not conclusive.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it seems to work, and when it probably doesn't
&lt;/h2&gt;

&lt;p&gt;Treat these as hypotheses consistent with the accounts above, not proven rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audience overlap.&lt;/strong&gt; Senja said building in public is easier for a product aimed at makers, and Robinson's audience matched his buyers. The failed project's audience was supporters. If your customers aren't on the platforms where founders talk to each other, you may grow a friendly audience that's useless to your revenue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content a buyer would value.&lt;/strong&gt; Senja's "milestone men" and engagement from people who were never going to buy teach the same lesson from two directions: attention from the wrong people, or about the wrong thing, doesn't convert. A revenue screenshot can show that a business exists. On its own, it rarely shows a buyer that you'll solve their problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A route from reading to trying.&lt;/strong&gt; A free tier, a waitlist, and Senja's DM follow-ups all give an interested reader a next step. This one is my inference from the accounts, not something any of them tested. Attention without a path leaves people with nowhere to go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time, including yours.&lt;/strong&gt; Seven hours a week is nearly a full working day for a solo founder. The honest comparison isn't "posting versus nothing." It's posting versus the same hours spent building, or reaching out directly to people who fit your buyer profile.&lt;/p&gt;

&lt;p&gt;One more thing the success stories don't dwell on: transparency is a strategic choice, not an obligation to disclose everything. Customer details, security specifics, and anything covered by a contract don't belong in a public update, however good the engagement would be.&lt;/p&gt;

&lt;h2&gt;
  
  
  A four-week experiment
&lt;/h2&gt;

&lt;p&gt;Four weeks is a proposed starting point, not a proven duration. Senja's experience suggests sales can take months, so this experiment measures leading indicators, not revenue. The question it answers: is public content producing conversations with people who look like buyers?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Baseline (before week 1).&lt;/strong&gt; Record how many signups you get per week now and where they come from. Note how many hours you can honestly spend. Without a baseline, any result is uninterpretable, and I'm not aware of a defensible benchmark conversion rate to use instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audience.&lt;/strong&gt; Write one sentence naming who pays you and where they already read or talk. If you can't, you've learned something important before posting a single word.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hypothesis.&lt;/strong&gt; Something like "Posting twice a week in [place] will start conversations with at least [N] people who fit my buyer profile." Set N before you start, based on your baseline and your available hours (for example, what the same hours of direct outreach would produce), not after you see the results. Moving the goalposts is a sport, but not this one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content.&lt;/strong&gt; Mix teaching posts that address a buyer's problem, build updates framed around what changed for a user, and replies in existing communities. Treat revenue and milestone screenshots as optional. Log which type of post led to each qualified conversation, so you learn what worked and not just that something did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Distribution.&lt;/strong&gt; Pick one or two places where buyers already spend time. Reply and participate more than you broadcast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measurement.&lt;/strong&gt; Leading indicators: replies and DMs from people who fit your buyer profile, and qualified visits to your site. Middle: signups and activation. Lagging: paying customers and retention, which probably won't show up in four weeks, so don't conclude from their absence alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribution.&lt;/strong&gt; Use a distinct tracked link per channel and log DMs by hand. Add one free-text "how did you hear about us?" question at signup, and let people name more than one touchpoint, since someone may see a post, come back later, and sign up through a search result. Accept that this undercounts, because readers who see a post and return later will often look like direct traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review and decision rules.&lt;/strong&gt; Check in at the end of week two to see whether the routine is sustainable and the content is landing with the right people. At week four, look at the quality of conversations (one qualified chat can beat ten polite likes), whether any turned into trials, how much time you spent, and whether you're reaching the right audience. Then decide whether to continue, change the channel or content, or run another cycle. Don't use four weeks to declare the whole strategy a success or a failure. If attention is high but buyers are absent, that usually points to the wrong venue or topic, not the wrong product. Consider demoting it to an accountability and networking habit if, after two cycles (my suggestion, not a proven threshold), qualified conversations stay near zero.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  So, does it get you customers? 🎯
&lt;/h2&gt;

&lt;p&gt;Sometimes. In the documented cases where it did, the audience was already close to the buyer, the content was useful to that buyer, and there was a path from reading to trying. When those conditions are missing, building in public may still help with accountability, feedback, and networking, but its value as a customer-acquisition channel becomes much less certain.&lt;/p&gt;

&lt;p&gt;How often it works across all founders, nobody can say from the evidence available. What you can do is test it on your own product, with your own baseline, before spending a year on it.&lt;/p&gt;

&lt;p&gt;So here's the question I'd most like to see answered in the comments. If you've built in public for a product whose buyers weren't other builders (dentists, accountants, game studios, anyone), where did your first ten paying customers actually come from, and could you trace any of them back to a public post?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The Hidden Bugs Behind Duplicate API Requests</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Thu, 22 Oct 2026 06:37:18 +0000</pubDate>
      <link>https://dev.to/hanzla/the-hidden-bugs-behind-duplicate-api-requests-1npb</link>
      <guid>https://dev.to/hanzla/the-hidden-bugs-behind-duplicate-api-requests-1npb</guid>
      <description>&lt;p&gt;One click on "Place order." Two rows in the orders table, created 40 milliseconds apart, identical down to the last field.&lt;/p&gt;

&lt;p&gt;Nothing in the frontend code looks wrong. The server logs show two requests, both returning &lt;code&gt;201&lt;/code&gt;. Whoever wrote the endpoint is fairly sure it only creates one order per call, and they're right. It was called twice.&lt;/p&gt;

&lt;p&gt;"Duplicate request" is a symptom with at least six different causes, and the fixes don't overlap much. Some of these causes are harmless (React in development), some are annoying (a double-clicked button), and one is genuinely dangerous: a client that timed out and sent the request again, while the server had already done the work. This article goes through them roughly in the order I'd check them, and spends the most time on that last one, because it's the one that costs money.&lt;/p&gt;




&lt;h2&gt;
  
  
  Before you debug: is it really a duplicate?
&lt;/h2&gt;

&lt;p&gt;Three things look like duplicates and aren't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CORS preflight.&lt;/strong&gt; A cross-origin request with a JSON &lt;code&gt;Content-Type&lt;/code&gt;, an &lt;code&gt;Authorization&lt;/code&gt; header, or a method like &lt;code&gt;PUT&lt;/code&gt; makes the browser send an &lt;code&gt;OPTIONS&lt;/code&gt; request first. In DevTools the URL appears twice. Look at the Method column. (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#preflighted_requests" rel="noopener noreferrer"&gt;MDN: CORS preflight&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development-only behavior.&lt;/strong&gt; If the doubles show up on your machine but not in production, go straight to the React section below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A different caller.&lt;/strong&gt; A service worker, browser extension, or second tab can send requests that resemble yours. The Initiator column in the Network tab points at the line that started each request.&lt;/p&gt;

&lt;p&gt;Then do the single most useful thing: log a request ID on the &lt;strong&gt;server&lt;/strong&gt;. One request on the server means you're misreading the client. Two means keep reading.&lt;/p&gt;




&lt;h2&gt;
  
  
  React runs your effect twice (on purpose)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setProduct&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setProduct&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under &lt;code&gt;&amp;lt;StrictMode&amp;gt;&lt;/code&gt; in development, this sends two requests on mount. In a production build it sends one.&lt;/p&gt;

&lt;p&gt;That's deliberate. With Strict Mode on, React runs an extra setup, cleanup, setup cycle on each effect in development, to expose effects that don't clean up after themselves. The behavior arrived with React 18, and the surprise it caused is well documented in &lt;a href="https://github.com/facebook/react/issues/24502" rel="noopener noreferrer"&gt;facebook/react #24502&lt;/a&gt;, where users asked for it to be explained better in the docs.&lt;/p&gt;

&lt;p&gt;Removing Strict Mode makes the symptom disappear and leaves the bug. If an effect misbehaves when it runs twice, it will also misbehave when a user navigates away and back.&lt;/p&gt;

&lt;p&gt;For reads, add a cleanup that cancels the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AbortController&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setProduct&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll still see two requests in the Network tab in development, but the first is cancelled and only the second can touch state. An &lt;code&gt;AbortController&lt;/code&gt; is better than the common &lt;code&gt;ignore&lt;/code&gt; flag here: as &lt;a href="https://dev.to/krofdrakula/stop-wasting-bandwidth-with-reacts-useeffect-and-fetch-27ne"&gt;this DEV Community post&lt;/a&gt; points out, the flag discards the result but still lets the first request download its whole response.&lt;/p&gt;

&lt;p&gt;Cancelling on the client does not undo anything the server already did. Which brings up the real question this section raises: what is the effect doing? A duplicate &lt;code&gt;GET&lt;/code&gt; is noise. A duplicate &lt;code&gt;POST&lt;/code&gt; fired from an effect means a non-idempotent action is tied to rendering. React's guidance is that effects exist to synchronize with external systems, and that you &lt;a href="https://react.dev/learn/you-might-not-need-an-effect" rel="noopener noreferrer"&gt;don't need an effect to handle a user event&lt;/a&gt;. Creating an order belongs in the click handler, where it runs once per click.&lt;/p&gt;




&lt;h2&gt;
  
  
  Race conditions: the stale response wins
&lt;/h2&gt;

&lt;p&gt;Sometimes two different requests are in flight and the older one finishes last.&lt;/p&gt;

&lt;p&gt;A search box fires on every keystroke. The user types "re", then "react". The request for "re" is slow, the one for "react" is quick. Results for "react" render, then results for "re" arrive and overwrite them. The page now shows results for a query that no longer matches the input.&lt;/p&gt;

&lt;p&gt;Nothing was sent twice, and nothing here is React-specific. The UI depends on the order responses arrive instead of the order the user acted. The cleanup shown above fixes it, since React runs the previous effect's cleanup before the next effect. If you'd rather not cancel, the flag version works too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/api/search?q=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;setResults&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ignore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Debouncing the input reduces how many requests you send. The cleanup makes sure the right one wins. They solve different problems, and a search box usually needs both.&lt;/p&gt;




&lt;h2&gt;
  
  
  The double click
&lt;/h2&gt;

&lt;p&gt;A checkout button, an 800 ms API call, and a user who isn't sure the first click registered.&lt;/p&gt;

&lt;p&gt;Disabling the button during submission is the obvious fix, with one catch. State updates don't apply synchronously inside the handler that sets them, so two clicks that land before React re-renders can both pass an &lt;code&gt;if (submitting)&lt;/code&gt; check. A ref closes that gap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CheckoutButton&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;submitting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSubmitting&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inFlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// the real lock&lt;/span&gt;
    &lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;setSubmitting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// the visual feedback&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;inFlight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;setSubmitting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleClick&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;disabled&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitting&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submitting&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Placing order…&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Place order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This protects one component in one tab. A second tab, a mobile app, or a retry after a dropped connection walks right past it. Treat it as UI feedback, and keep the safety check on the server.&lt;/p&gt;




&lt;h2&gt;
  
  
  The same handler attached twice
&lt;/h2&gt;

&lt;p&gt;If the server log shows two identical requests a few milliseconds apart and the button logic is fine, count the listeners.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// runs on every route change&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#save&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;saveDraft&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;addEventListener&lt;/code&gt; ignores a repeat registration only when the function reference and capture flag are the same. An inline arrow function is a new object every call, so after three route changes one click runs &lt;code&gt;saveDraft()&lt;/code&gt; three times. (&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" rel="noopener noreferrer"&gt;MDN: addEventListener&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The usual sources are SPA code that attaches listeners imperatively on every navigation, jQuery's &lt;code&gt;.on()&lt;/code&gt; inside a function that runs on every partial render, and React effects that subscribe without returning a cleanup. A form with both &lt;code&gt;onSubmit&lt;/code&gt; on the &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; and &lt;code&gt;onClick&lt;/code&gt; on its submit button doing the same job produces the same symptom.&lt;/p&gt;

&lt;p&gt;In Chrome DevTools, select the element and open the Event Listeners panel. Two &lt;code&gt;click&lt;/code&gt; entries where you expected one is your answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retries: duplicates made by code that's trying to help
&lt;/h2&gt;

&lt;p&gt;This is the case worth slowing down for.&lt;/p&gt;

&lt;p&gt;You send a &lt;code&gt;POST /orders&lt;/code&gt;. Ten seconds pass. Your client times out. Here is everything you know at that moment: you did not receive a response. Here is what you don't know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The request never reached the server.&lt;/li&gt;
&lt;li&gt;The server processed it, and the response was lost on the way back.&lt;/li&gt;
&lt;li&gt;The server is still working on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the client these three look identical. If you retry in case 2 or 3, the operation runs twice.&lt;/p&gt;

&lt;p&gt;HTTP has vocabulary for this. &lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-idempotent-methods" rel="noopener noreferrer"&gt;RFC 9110, section 9.2.2&lt;/a&gt; defines idempotent methods: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, &lt;code&gt;TRACE&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt;, where repeating the request has the same intended effect as sending it once. &lt;code&gt;POST&lt;/code&gt; isn't in that set, and neither is &lt;code&gt;PATCH&lt;/code&gt; in general. The RFC's error-handling section also notes that some requests can be automatically retried after a connection failure, pointing to section 9.2.2 for which ones. Secondary write-ups summarize the rule as: don't automatically retry a non-idempotent request unless you know it's effectively idempotent or can tell the original was never applied (&lt;a href="https://apyhub.com/blog/what-is-an-idempotent-request" rel="noopener noreferrer"&gt;ApyHub&lt;/a&gt;, &lt;a href="https://dev.to/erfan-moghaddam/designing-safer-api-failover-in-an-android-app-136i"&gt;a DEV Community post on safe failover&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Check what your client library does by default
&lt;/h3&gt;

&lt;p&gt;Retry behavior varies by library and version, so read your client's documentation instead of assuming. One public example: a GitLab client-library change (&lt;a href="https://gitlab.com/gitlab-org/api/client-go/-/merge_requests/2945" rel="noopener noreferrer"&gt;merge request 2945&lt;/a&gt;) added an option to restrict retries to idempotent methods, because its default check retried any method on a 5xx response, which could duplicate a side effect the server had already applied. That's one project's default, but it shows how the problem hides: nobody wrote a retry for &lt;code&gt;POST /orders&lt;/code&gt;; a generic retry policy did.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retries multiply across layers
&lt;/h3&gt;

&lt;p&gt;If the browser code retries 3 times, the API gateway retries 3 times, and the service retries its database call 3 times, one click can become 27 attempts at the bottom. Amazon's &lt;a href="https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/" rel="noopener noreferrer"&gt;Builders' Library article on timeouts, retries and backoff with jitter&lt;/a&gt; makes this point about stacked retries, and also calls retries "selfish": each one spends more of the server's time to improve the caller's odds. Its recommendations are timeouts based on downstream latency, capped exponential backoff, jitter so clients don't all retry in lockstep, and limiting retries (the AWS SDK has used a token bucket for this since 2016).&lt;/p&gt;

&lt;p&gt;Infrastructure retries too. Reverse proxies can pass a failed request to another upstream. For example, &lt;a href="https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_next_upstream" rel="noopener noreferrer"&gt;nginx's &lt;code&gt;proxy_next_upstream&lt;/code&gt;&lt;/a&gt; documents that non-idempotent requests aren't passed on by default unless you opt in with &lt;code&gt;non_idempotent&lt;/code&gt;. Read the equivalent setting for whatever sits in front of your app, and check access logs on every hop.&lt;/p&gt;

&lt;h3&gt;
  
  
  A retry helper that actually retries HTTP failures
&lt;/h3&gt;

&lt;p&gt;One trap when writing your own: &lt;code&gt;fetch()&lt;/code&gt; resolves successfully even when the server answers &lt;code&gt;503&lt;/code&gt;. A &lt;code&gt;try/catch&lt;/code&gt; around it never sees the failure. You have to look at the status yourself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HttpError&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HTTP &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RETRYABLE_STATUS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;408&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;504&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AbortError&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;HttpError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;RETRYABLE_STATUS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// fetch rejects with TypeError on network failure&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;capMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;retries&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;isRetryable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ceiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;capMs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baseMs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// full jitter&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran this helper against a stub that returns &lt;code&gt;503&lt;/code&gt; twice and then &lt;code&gt;201&lt;/code&gt;: it made three attempts and returned the &lt;code&gt;201&lt;/code&gt;. A &lt;code&gt;400&lt;/code&gt; was thrown after one attempt, as intended. A production version should also honor a &lt;code&gt;Retry-After&lt;/code&gt; header when the server sends one.&lt;/p&gt;

&lt;p&gt;Retrying a &lt;code&gt;POST&lt;/code&gt; with this helper is only safe if the server can recognize the repeat. That's the next section.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making the server safe to call twice
&lt;/h2&gt;

&lt;p&gt;Everything above reduces how often a duplicate arrives. None of it can remove the three-way ambiguity after a timeout, because the network is allowed to lose a response. For operations that must not happen twice, the main defense is an &lt;strong&gt;idempotency key&lt;/strong&gt;: the client generates a unique key per logical operation, the server records it with the result, and a repeat with the same key returns the stored result instead of doing the work again.&lt;/p&gt;

&lt;p&gt;Stripe's API is the best-known example. &lt;a href="https://docs.stripe.com/api/idempotent_requests" rel="noopener noreferrer"&gt;Their documentation&lt;/a&gt; says the idempotency layer saves the status code and body of the first request for a key, whether it succeeded or failed, and returns that for later requests with the same key. Keys can be up to 255 characters, and reusing a key with different parameters produces an error. &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; don't take keys because they're idempotent by definition.&lt;/p&gt;

&lt;p&gt;There's also a standards effort: the IETF HTTPAPI working group's &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/" rel="noopener noreferrer"&gt;Idempotency-Key HTTP header field&lt;/a&gt; draft. As of the version I read (draft-07, October 2025), it's an active Internet-Draft, not an RFC, so details may change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client: create the key once, reuse it on every retry
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create it when the user's intent is created (page load, checkout start),&lt;/span&gt;
&lt;span class="c1"&gt;// never inside the retry loop.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;withRetry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;idempotencyKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HttpError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new key per attempt defeats the whole mechanism.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server: let the database arbitrate
&lt;/h3&gt;

&lt;p&gt;The sketch below uses Express and PostgreSQL. It's an illustration of the pattern: I haven't run the SQL against a live database, so test it in your own stack, including the concurrent case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;idempotency_keys&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;key&lt;/span&gt;           &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;request_hash&lt;/span&gt;  &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;response_code&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;response_body&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;    &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Same payload in a different key order must hash the same.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])])&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Idempotency-Key required&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;canonicalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;BEGIN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Claim the key. The PRIMARY KEY decides who wins a race.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;claim&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`INSERT INTO idempotency_keys (key, request_hash, response_code, response_body)
       VALUES ($1, $2, 0, '{}')
       ON CONFLICT (key) DO NOTHING
       RETURNING key`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rowCount&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SELECT request_hash, response_code, response_body FROM idempotency_keys WHERE key = $1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;409&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Retry-After&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// first attempt rolled back&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request_hash&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;422&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Key reused with a different payload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_code&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response_body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// We own the key. Do the work, in the same transaction.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`UPDATE idempotency_keys SET response_code = 201, response_body = $2 WHERE key = $1`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;COMMIT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ROLLBACK&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;release&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three design points are worth spelling out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;INSERT ... ON CONFLICT&lt;/code&gt; and not "check, then insert."&lt;/strong&gt; A &lt;code&gt;SELECT&lt;/code&gt; followed by an &lt;code&gt;INSERT&lt;/code&gt; has a gap, and two simultaneous requests can both pass the check. Letting a unique constraint decide closes the gap. As I understand PostgreSQL's behavior, a second insert of the same key blocks until the first transaction commits or rolls back, which is why the example doesn't need an &lt;code&gt;in_progress&lt;/code&gt; state. Verify that against your isolation level and driver.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why one transaction.&lt;/strong&gt; Creating the order and recording the key together means a crash can't leave an order without a key, or a key without an order. This only works when both writes live in the same database. Once the operation involves something outside that transaction (a payment provider, a message queue, an email), the guarantee stops at the transaction boundary. For those, pass a key derived from yours to the provider's own idempotency mechanism, and consider an explicit &lt;code&gt;in_progress&lt;/code&gt; state with a defined policy for concurrent callers, such as &lt;code&gt;409&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why canonicalize before hashing.&lt;/strong&gt; &lt;code&gt;JSON.stringify&lt;/code&gt; preserves key order, so &lt;code&gt;{a:1,b:2}&lt;/code&gt; and &lt;code&gt;{b:2,a:1}&lt;/code&gt; hash differently even though they mean the same thing. Without canonicalization, a legitimate retry from a different serializer gets rejected as "key reused with a different payload."&lt;/p&gt;

&lt;h3&gt;
  
  
  When a simpler rule is enough
&lt;/h3&gt;

&lt;p&gt;If the domain gives you natural uniqueness, such as one open order per &lt;code&gt;(user_id, cart_id)&lt;/code&gt;, a unique constraint can reject the duplicate without any key table. Idempotency keys are the more general tool, and constraints are the cheaper one when they fit.&lt;/p&gt;

&lt;p&gt;Idempotency isn't the only defense either. Uniqueness constraints, transactional design and operation-specific checks all contribute. What an idempotency key gives you that they don't is a consistent answer to the retry: the same response, not an error saying "already exists."&lt;/p&gt;




&lt;h2&gt;
  
  
  A checklist for the next time you see doubles
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Is one of them an &lt;code&gt;OPTIONS&lt;/code&gt; preflight?&lt;/li&gt;
&lt;li&gt;Does it happen in a production build? If not, check Strict Mode and effect cleanups.&lt;/li&gt;
&lt;li&gt;What does the Initiator column say about each request?&lt;/li&gt;
&lt;li&gt;How many listeners does the element have?&lt;/li&gt;
&lt;li&gt;Does the server log show one request or two? Log a request ID.&lt;/li&gt;
&lt;li&gt;Are the two requests identical, or do query strings differ? Different ones suggest a race or dependency-array bug, not a retry.&lt;/li&gt;
&lt;li&gt;How far apart are the timestamps? Milliseconds points at a double-click or double-mount. Seconds points at a timeout and retry.&lt;/li&gt;
&lt;li&gt;Which layers retry? Browser code, client library, gateway, load balancer, service client, job queue.&lt;/li&gt;
&lt;li&gt;Can this endpoint safely run twice? If not, fix that first.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first eight steps tell you why a duplicate happened. The ninth decides whether it matters.&lt;/p&gt;

</description>
      <category>debugging</category>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
    </item>
    <item>
      <title>Introducing The Startup Café: A Community for People Building Startups ☕</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:44:12 +0000</pubDate>
      <link>https://dev.to/startupcafe/introducing-the-startup-cafe-a-community-for-people-building-startups-11n1</link>
      <guid>https://dev.to/startupcafe/introducing-the-startup-cafe-a-community-for-people-building-startups-11n1</guid>
      <description>&lt;p&gt;&lt;strong&gt;I Started The Startup Café on DEV. Here's the Idea Behind It.&lt;/strong&gt;&lt;br&gt;
I've just started a new organization on DEV called &lt;strong&gt;The Startup Café&lt;/strong&gt;, and I wanted to share it here while it's still at the very beginning.&lt;/p&gt;

&lt;p&gt;Here's the organization: &lt;a href="https://dev.to/startupcafe"&gt;https://dev.to/startupcafe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The idea came from something pretty simple: there's a lot to learn from people who are building things, even when they're working on completely different projects.&lt;/p&gt;

&lt;p&gt;Some developers build things for clients. Some work on side projects after their day job. Some are trying to launch a SaaS, while others are still figuring out which idea is worth pursuing.&lt;/p&gt;

&lt;p&gt;I've always found that side of building interesting. Not just seeing what someone has launched, but understanding why they started, what they're figuring out, and what they've learned along the way.&lt;/p&gt;

&lt;p&gt;I'd like The Startup Café to be a place for those conversations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I have in mind?
&lt;/h2&gt;

&lt;p&gt;I don't want this to be just another place to drop a project link and move on.&lt;/p&gt;

&lt;p&gt;I'd like people to share what they're working on, ask for feedback when they're stuck, write about things they've learned, and discover projects they might not have found otherwise.&lt;/p&gt;

&lt;p&gt;Maybe you're building your first website. Maybe you're working on a SaaS and trying to find your first users. Maybe you've got an open-source project, or an idea you keep coming back to but haven't started yet.&lt;/p&gt;

&lt;p&gt;All of those are worth talking about.&lt;/p&gt;

&lt;p&gt;You don't need to have a successful startup or years of experience to take part. Sometimes the most interesting discussions come from people who are still working things out.&lt;/p&gt;

&lt;p&gt;I'd also love to see people connect through the organization. You might find someone dealing with the same problem, discover a project you want to contribute to, or meet a developer who's interested in building something similar.&lt;/p&gt;

&lt;p&gt;There's no guarantee of where those conversations will lead, but that's part of what makes a community interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's just getting started
&lt;/h2&gt;

&lt;p&gt;I want to be clear that The Startup Café is brand new. I'm not introducing an established community with a big audience or claiming we've already built something huge.&lt;/p&gt;

&lt;p&gt;I'm starting small, and I'd like the people who join to have a say in what this becomes.&lt;/p&gt;

&lt;p&gt;What would make it useful? What kinds of posts would you like to see? Would you be interested in sharing your progress, getting feedback, finding collaborators, or learning from other people's experiences?&lt;/p&gt;

&lt;p&gt;Those are the kinds of questions I'd like us to figure out together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interested in joining?
&lt;/h2&gt;

&lt;p&gt;The Startup Café is a &lt;strong&gt;DEV.to organization&lt;/strong&gt;, and that's where the community will come together.&lt;/p&gt;

&lt;p&gt;You can visit the organization here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/startupcafe"&gt;https://dev.to/startupcafe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've also built a small website for it. If you'd like to join, you can visit the website to learn more and submit an application:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://thestartupcafe.vercel.app" rel="noopener noreferrer"&gt;https://thestartupcafe.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If this sounds like something you'd enjoy being part of, I'd be glad to have you check it out.&lt;/p&gt;

&lt;p&gt;And even if you're not ready to join, I'd still appreciate hearing your thoughts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, what are you building these days?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It can be a startup, a side project, an open-source contribution, or just an idea you're considering. I'd love to hear what you're working on and where you are with it.&lt;/p&gt;

&lt;p&gt;Let's start there.&lt;/p&gt;

</description>
      <category>startup</category>
      <category>discuss</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>My AI Experiment Was Going Great Until My Client Replied 😂</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:53:07 +0000</pubDate>
      <link>https://dev.to/hanzla/my-ai-experiment-was-going-great-until-my-client-replied-17kd</link>
      <guid>https://dev.to/hanzla/my-ai-experiment-was-going-great-until-my-client-replied-17kd</guid>
      <description>&lt;p&gt;Quick confession. Last Sunday I opened dev.to and my brain made a tiny error noise. 😵&lt;/p&gt;

&lt;p&gt;Opus against GPT-6 against Gemini 4. One post said Gemini wins twelve of eighteen benchmarks. Another said a Meta model costs the same as Opus and loses most of its own tests. One minute of scrolling. Zero joy.&lt;/p&gt;

&lt;p&gt;So I made a deal with myself. Thirty days. One editor, VS Code. One model, Claude Sonnet 5.5, on the free plan. Yes, free. 😂 Every new launch was allowed to happen without me.&lt;/p&gt;

&lt;p&gt;My old routine, for context. Google AI Studio on Monday, trying one Gemini flavour after another. DeepSeek on Tuesday because a bug felt like its kind of bug. Then I'd test a few models inside my AI powered projects too. I called it research. My calendar called it wandering. 🧭&lt;/p&gt;

&lt;p&gt;That same week, FrancisTRdev's ( &lt;a class="mentioned-user" href="https://dev.to/francistrdev"&gt;@francistrdev&lt;/a&gt; ) post about AI fatigue was sitting in my feed. He misses the dev.to from before 2022, the one with CSS tips and small weird projects, and he hopes AI becomes a quiet boring tool in the toolbox. Boring! That's the exact word I wanted for my editor. His post got well over a hundred reactions, so I'm clearly not the only tired one.&lt;/p&gt;

&lt;p&gt;Sylwia Laskowska ( &lt;a class="mentioned-user" href="https://dev.to/sylwia-lask"&gt;@sylwia-lask&lt;/a&gt; ) wrote about AI features that run in the browser and what your hardware can really handle. Jamilxt ( &lt;a class="mentioned-user" href="https://dev.to/jamilxt"&gt;@jamilxt&lt;/a&gt; ) keeps pointing out that a model priced at two dollars per million tokens can still give you a very different real bill. 💸 Maame Afua A. P. Fordjour ( &lt;a class="mentioned-user" href="https://dev.to/maame-codes"&gt;@maame-codes&lt;/a&gt; ) wrote that localhost is a lie, because real users break clean databases. Three different voices, one message. Real work beats shiny news.&lt;/p&gt;

&lt;p&gt;So what did my thirty days look like?&lt;/p&gt;

&lt;p&gt;Quiet. Busy, but quiet. I finished a few client projects, some for clients in India, many of them ecommerce sites. I built the website for my agency TheBitForge ( &lt;a class="mentioned-user" href="https://dev.to/thebitforge"&gt;@thebitforge&lt;/a&gt; ) and worked on its projects. I wrote a few articles for topblogs.online and some other places. And after a few emails with the founder of Floot.com, I used a paid Floot plan connected with Claude to build a full stack blogging platform with an admin panel and AI summaries for every article, plus a newsletter system for my own use. &lt;/p&gt;

&lt;p&gt;Claude Sonnet 5.5 helped me well through all of it, the ecommerce work included. And look at that list again. Where's the new model? Nowhere. I just finished things.&lt;/p&gt;

&lt;p&gt;Now the part that hurt.&lt;/p&gt;

&lt;p&gt;One client got their ecommerce website from me. They didn't like it. A few days later they came back and asked me to redesign the whole thing again. All of it. 😅&lt;/p&gt;

&lt;p&gt;That was easily the worst moment of my month. And here's the strange part. No model on earth could have stopped it. Not the newest one, not the priciest one, not the one with twelve benchmark wins. A client's taste isn't inside any benchmark. Maame was right. The hard part of this job is people, not tokens.&lt;/p&gt;

&lt;p&gt;What I'm taking from it? Show the client a rough version sooner, before the pretty one. Ask what they hate before I build anything. Simple, a bit embarrassing that I needed a full redesign to learn it. 🙃&lt;/p&gt;

&lt;p&gt;Do I think everyone should stay on one model? No. If your job is literally comparing models, please keep comparing. For the rest of us, I'd check the news once a month instead of once an hour. If something is truly better, it'll still be better in four weeks, and by then someone on dev.to has probably tested it and posted the real bill.&lt;/p&gt;

&lt;p&gt;Your turn. Has a client ever asked for a full redesign right after you delivered? How did you handle it? And which model are you on right now? Tell me below, I'm genuinely curious. 👇&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>👏👏👏</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Tue, 08 Sep 2026 05:38:12 +0000</pubDate>
      <link>https://dev.to/hanzla/-2169</link>
      <guid>https://dev.to/hanzla/-2169</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/hadil/i-tested-brilliantdesign-building-a-real-portfolio-from-figma-to-ai-powered-design-and-code-5b2a" class="crayons-story__hidden-navigation-link"&gt;I Tested Brilliant.design: Building a Real Portfolio from Figma to AI-Powered Design and Code&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/hadil" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1209000%2Fb29d37d8-2efe-4391-9796-a6f8a483f1bd.png" alt="hadil profile" class="crayons-avatar__image" width="768" height="1376"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/hadil" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hadil Ben Abdallah
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hadil Ben Abdallah
                
                
              
              &lt;div id="story-author-preview-content-4568725" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/hadil" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1209000%2Fb29d37d8-2efe-4391-9796-a6f8a483f1bd.png" class="crayons-avatar__image" alt="" width="768" height="1376"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hadil Ben Abdallah&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/hadil/i-tested-brilliantdesign-building-a-real-portfolio-from-figma-to-ai-powered-design-and-code-5b2a" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 7&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/hadil/i-tested-brilliantdesign-building-a-real-portfolio-from-figma-to-ai-powered-design-and-code-5b2a" id="article-link-4568725"&gt;
          I Tested Brilliant.design: Building a Real Portfolio from Figma to AI-Powered Design and Code
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/design"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;design&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/coding"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;coding&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/hadil/i-tested-brilliantdesign-building-a-real-portfolio-from-figma-to-ai-powered-design-and-code-5b2a" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;71&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/hadil/i-tested-brilliantdesign-building-a-real-portfolio-from-figma-to-ai-powered-design-and-code-5b2a#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              18&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            22 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>Jack Dorsey's Buzz: The Dev Workflow Game Changer We Didn't Know We Needed?</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Wed, 22 Jul 2026 04:21:13 +0000</pubDate>
      <link>https://dev.to/hanzla/jack-dorseys-buzz-the-dev-workflow-game-changer-we-didnt-know-we-needed-1kpk</link>
      <guid>https://dev.to/hanzla/jack-dorseys-buzz-the-dev-workflow-game-changer-we-didnt-know-we-needed-1kpk</guid>
      <description>&lt;p&gt;Alright, so I just caught wind of something that actually made me pause my &lt;code&gt;npm install&lt;/code&gt; for a second. Jack Dorsey, yeah, &lt;em&gt;that&lt;/em&gt; Jack Dorsey, is launching something called Buzz. And it's not just another chat app or another Git host. It's an &lt;em&gt;integration&lt;/em&gt; of team chat, AI agents, and Git hosting. As a full-stack dev grinding on Next.js, TypeScript, and constantly wrangling Supabase and various AI APIs, this immediately piqued my interest. &lt;/p&gt;

&lt;h2&gt;
  
  
  The "Why" Behind the Buzz: One Ring to Rule Them All?
&lt;/h2&gt;

&lt;p&gt;Let's be real: our current dev workflow is a fragmented mess. We've got Slack/Discord for chat, GitHub/GitLab for code, Jira/Trello for project management, and then a whole host of bespoke tools for AI model management, testing, and deployment. Context switching is a silent killer of productivity. Every time I jump from a PR review to a design discussion to debugging a webhook, I lose a little bit of flow. &lt;/p&gt;

&lt;p&gt;Buzz, on paper, sounds like it's trying to tackle this head-on. Imagine a world where your AI agent, trained on your codebase and documentation, can directly participate in your team chat, suggest code improvements, answer questions about specific Git commits, or even help you craft a new feature branch based on a discussion. That's not just convenience; that's a fundamental shift in how we interact with our tools and, crucially, with our code.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Agents: More Than Just Smart Bots
&lt;/h2&gt;

&lt;p&gt;We've all played with ChatGPT, and we're integrating LLMs into our apps. But the idea of deeply integrated, context-aware AI agents &lt;em&gt;within&lt;/em&gt; our core collaboration and Git platform? That's where it gets spicy. Think about it: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Automated PR Summaries:&lt;/strong&gt; An agent could analyze a PR, summarize changes, flag potential issues based on internal guidelines, and even suggest reviewers who have worked on similar parts of the codebase. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Instant Code Answers:&lt;/strong&gt; Instead of digging through docs or Slack history, you could ask your Buzz AI, "How do I integrate the new payment gateway service?" and it could pull up relevant code snippets, documentation, and past discussions directly from your Git history and chat logs. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Proactive Issue Detection:&lt;/strong&gt; Imagine an agent monitoring your Git pushes, noticing a pattern of errors, and proactively alerting the team or even suggesting a fix before it hits staging.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just about making things faster; it's about making our development process smarter and more resilient. It's about offloading the repetitive, cognitive load so we can focus on the truly creative and complex problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Git Hosting &amp;amp; Chat: A Symbiotic Relationship
&lt;/h2&gt;

&lt;p&gt;The Git hosting aspect is key here. It's not just about having a place for your code; it's about the deep integration. When your chat and AI agents live in the same ecosystem as your code, the possibilities explode. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Code-Aware Discussions:&lt;/strong&gt; Imagine discussing a bug and being able to instantly reference a line of code, and have the AI agent immediately pull up its history, related issues, and even potential fixes. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Automated Branching/Committing:&lt;/strong&gt; A discussion in chat could lead to an AI agent automatically creating a feature branch, populating it with a basic structure based on a template, and even generating an initial commit message based on the conversation. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Enhanced Onboarding:&lt;/strong&gt; New team members could leverage AI agents to quickly understand the codebase, project history, and team conventions by asking natural language questions directly within their chat interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This level of integration could dramatically reduce the friction in our daily dev lives. It's less about switching tabs and more about a fluid, continuous flow of information and action.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future of Dev Tools and SaaS: A Unified Front?
&lt;/h2&gt;

&lt;p&gt;If Buzz delivers on this promise, it could set a new precedent for developer tools. The current landscape of specialized SaaS products might start to feel clunky and inefficient in comparison. We've seen platforms try to do "all-in-one" before, often resulting in a mediocre experience across the board. But with Jack Dorsey's backing and the current advancements in AI, this could be different. &lt;/p&gt;

&lt;p&gt;It forces us to ask: Are we entering an era where the best developer experience isn't about having the best individual tools, but the best &lt;em&gt;integrated&lt;/em&gt; ecosystem? And what does this mean for companies like GitHub, GitLab, Slack, and even smaller niche AI dev tools? They'll either need to incredibly deepen their own integrations or risk being outflanked by a truly unified platform.&lt;/p&gt;

&lt;p&gt;I'm genuinely curious to see how Buzz plays out. Could this be the platform that finally unifies our fragmented dev world and truly leverages AI to make us more effective? Or will it be another ambitious attempt that falls short? &lt;/p&gt;

&lt;p&gt;What do you think? Is this the future, or just another buzzword-laden promise?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>saas</category>
      <category>typescript</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Tue, 30 Jun 2026 18:27:30 +0000</pubDate>
      <link>https://dev.to/hanzla/-34j6</link>
      <guid>https://dev.to/hanzla/-34j6</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli" class="crayons-story__hidden-navigation-link"&gt;Need a break? Play today's game from The Daily Context.&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
      &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli" class="crayons-article__context-note crayons-article__context-note__feed"&gt;&lt;p&gt;AI Engineer World's Fair Coverage&lt;/p&gt;

&lt;/a&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devteam"&gt;
            &lt;img alt="The DEV Team logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/jess" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" alt="jess profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jess" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jess Lee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jess Lee
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                &lt;img alt="Community Curator" class="community-leader-icon" src="https://assets.dev.to/assets/community-leader-icon-b72c9e74eff54916e5c46c962f47ba40c9f611a71f8b157511f9613f69c0001b.svg" width="20" height="20"&gt;
              
              &lt;div id="story-author-preview-content-4024781" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jess" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jess Lee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devteam" class="crayons-story__secondary fw-medium"&gt;The DEV Team&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli" id="article-link-4024781"&gt;
          Need a break? Play today's game from The Daily Context.
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/aie"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;aie&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gamedev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gamedev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/gemini"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;gemini&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;118&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devteam/need-a-break-play-todays-game-from-the-daily-context-1fli#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              33&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>React Is Not Slow — Your Architecture Is</title>
      <dc:creator>Hanzla Baig</dc:creator>
      <pubDate>Fri, 16 Jan 2026 13:10:28 +0000</pubDate>
      <link>https://dev.to/hanzla/react-is-not-slow-your-architecture-is-5d5d</link>
      <guid>https://dev.to/hanzla/react-is-not-slow-your-architecture-is-5d5d</guid>
      <description>&lt;div class="ltag-netlify"&gt;
  &lt;iframe src="https://hanzla-beig.netlify.app" title="Netlify embed"&gt;
  &lt;/iframe&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wph93h41bnewj23j62v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0wph93h41bnewj23j62v.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm going to say something that might sting a little: if your React application is slow, it's almost certainly not React's fault. I've been building production React applications for years now, and I've seen this pattern repeat itself over and over again. A team builds something, it starts getting sluggish, and suddenly React becomes the scapegoat. "React is too slow," they say. "We should've used Svelte," someone suggests in a retrospective. "Maybe we need to rewrite this in vanilla JavaScript," another developer proposes, half-seriously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7laqs5lxcn9hsatxmtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm7laqs5lxcn9hsatxmtl.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable truth: React is fast. Incredibly fast, actually. What's slow is the architecture you built on top of it. The tangled mess of components that re-render on every keystroke. The global state that's subscribed to by half your application. The 500-line component that's trying to do everything at once because "it's all related functionality." The Context providers nested twelve levels deep, each one triggering cascading updates through your component tree like dominoes falling in slow motion.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02cm7qxgz98u1wnijxgy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F02cm7qxgz98u1wnijxgy.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm not here to shame anyone. I've made every single one of these mistakes myself, often multiple times, because that's how you actually learn this stuff. You don't learn good React architecture from reading the docs once and building a todo app. You learn it by shipping a feature, watching it grind your app to a halt, spending three days debugging why a dropdown causes the entire page to freeze, and finally understanding—deeply, viscerally understanding—what you did wrong. This blog post is the distillation of all those painful lessons, the architectural mistakes I've made and seen others make, and what I've learned about building React applications that actually perform well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Developers Think React Is Slow
&lt;/h2&gt;

&lt;p&gt;Let's start by acknowledging that the perception of React being slow is incredibly common, especially among developers who are either new to React or who learned it in a specific way that encourages bad patterns. And honestly, I get it. When you first start building with React, everything seems fine. Your todo app renders instantly. Your small dashboard feels snappy. Then your application grows, you add more features, more state, more components, and suddenly everything feels sluggish. The interface starts lagging. Inputs feel unresponsive. You open the React DevTools profiler and see this horrifying cascade of components re-rendering, and you think, "This framework is the problem."&lt;/p&gt;

&lt;p&gt;But here's what's actually happening: you're running into the natural consequences of architectural decisions you made when your app was small, decisions that don't scale. The problem isn't that React is slow—it's that React is extremely good at exposing bad architecture. React will happily re-render your entire component tree on every state change if you tell it to. It will dutifully run every expensive calculation you put in your render functions. It will obediently trigger effects and updates in whatever tangled dependency chain you've created. React is not opinionated enough to stop you from doing these things, which means it's incredibly easy to build something that performs poorly if you don't understand what you're doing.&lt;/p&gt;

&lt;p&gt;One of the biggest reasons developers perceive React as slow is poor component structure. I see this constantly: components that are too big, too complex, and doing way too much. A single component handling data fetching, state management, business logic, conditional rendering for a dozen different scenarios, and managing side effects all at once. When that component re-renders—and it will re-render often because it's touching so much state—everything inside it runs again. Every function gets redefined. Every calculation gets recomputed. Every child component receives new props and has to decide whether it needs to re-render. This isn't React being slow; this is you forcing React to do an enormous amount of work every single update cycle.&lt;/p&gt;

&lt;p&gt;Another massive issue is unnecessary re-renders. This is probably the number one performance problem I see in React applications, and it stems from a fundamental misunderstanding of how React's rendering model works. Developers will structure their state in a way that causes huge portions of their component tree to re-render when only a tiny piece of data changes. They'll lift state way too high up the tree because "these components need to share data," not realizing that they've just made 47 components re-render every time a user types in a search box. They'll create Context providers for convenience without understanding that every component consuming that context will re-render whenever any value in that context changes, even if they're only using a small piece of it.&lt;/p&gt;

&lt;p&gt;And then there's the overuse of state. Not all data needs to be state. I've reviewed codebases where literally everything is in state, including data that could be derived from other state, data that never changes, data that's only used in event handlers, and data that should actually be refs. Every piece of unnecessary state is another potential trigger for re-renders, another moving part that makes your application harder to reason about and slower to update. React's useState and useReducer hooks are powerful, but with great power comes great responsibility, and apparently also the great temptation to put absolutely everything into state "just in case."&lt;/p&gt;

&lt;p&gt;Bad data flow decisions compound all of these problems. When you don't have a clear strategy for how data moves through your application, you end up with state scattered everywhere—some in components, some in Context, some in a global store, some in URL params, some in refs, some passed through props five levels deep. Nobody knows where the source of truth is. Updates are unpredictable. You fix a performance issue in one place and cause three new ones somewhere else. This isn't a React problem; this is a failure to design a coherent data architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architectural Mistakes That Kill Performance
&lt;/h2&gt;

&lt;p&gt;Let me walk you through the specific architectural mistakes that I see destroying React application performance. These aren't obscure edge cases or advanced optimization failures. These are fundamental design problems that slow down applications, make them hard to maintain, and lead developers to incorrectly blame React.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Massive components that do everything.&lt;/strong&gt; This is the cardinal sin of React architecture. You've seen these components. Hell, you've probably written these components. I know I have. They're 500, 800, sometimes over a thousand lines long. They manage five different pieces of state. They have ten useEffect hooks, half of which have dependency arrays that nobody fully understands anymore. They fetch data, process it, display it, handle user interactions, manage forms, coordinate with other parts of the application, and probably make coffee too if you dig deep enough into the code.&lt;/p&gt;

&lt;p&gt;These monolithic components are performance killers because when they re-render, everything inside them runs. Every single line of JSX gets processed. Every inline function gets redefined. Every calculation happens again. And because they're often at the root of some section of your UI, their re-renders cascade down to dozens or hundreds of child components. The worst part is that these components usually re-render way more than necessary because they're touching so much state. A user updates a form field? Re-render the whole thing. Data comes back from an API? Re-render the whole thing. A child component calls a callback? You guessed it—re-render the whole thing.&lt;/p&gt;

&lt;p&gt;The reason this happens is usually because developers start with a simple component that does one thing well, and then they keep adding to it. "Oh, we need to handle this edge case, I'll just add a bit more state." "This related functionality needs access to the same data, I'll just put it in here." "We need to coordinate these two things, might as well keep them together." Before you know it, you have a component that's responsible for an entire feature area, and extracting logic from it seems impossible because everything is so tangled together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prop drilling chaos.&lt;/strong&gt; Prop drilling gets a bad rap, and honestly, a lot of that criticism is deserved. But the real problem isn't prop drilling itself—it's what prop drilling represents: a failure to properly structure your component hierarchy. When you're passing props down through five or six levels of components, that's usually a sign that your component tree doesn't match your data flow. You've created intermediate components that don't actually care about the data they're passing through, they're just dumb conduits, and every time you need to add a new piece of data or change how something works, you have to modify every single component in that chain.&lt;/p&gt;

&lt;p&gt;But here's where it gets really bad for performance: developers often "solve" prop drilling by lifting state higher and higher up the tree, thinking they're making things simpler. Now the state lives in some common ancestor component that's far removed from where the data is actually used, and every update to that state causes a huge portion of your component tree to re-render. You've traded the inconvenience of prop drilling for a massive performance problem, and you probably didn't even realize you were making that trade-off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Global state abuse.&lt;/strong&gt; Oh man, global state. This is where things get spicy. Global state management libraries like Redux, Zustand, MobX, and others are incredibly useful tools. They're also incredibly easy to misuse, and when you misuse them, you create performance nightmares. I've seen applications where almost all state is global. Every piece of UI state, every form value, every loading flag, every error message—it's all in the global store. The reasoning is usually something like "we might need this data somewhere else" or "it's easier to access from anywhere" or "this is how we do state management."&lt;/p&gt;

&lt;p&gt;The problem is that when everything is in global state, everything is connected to everything else. Components subscribe to slices of the global store, but often they subscribe to more than they need, or the slices aren't granular enough, or the selectors aren't optimized. A user types in a search input in one corner of your application, updating global state, and suddenly twenty components in completely unrelated parts of your UI are re-rendering because they're subscribed to the same store slice or because you didn't properly implement selector equality checks.&lt;/p&gt;

&lt;p&gt;Global state should be for actually global concerns: user authentication, theme preferences, data that truly needs to be shared across distant parts of your application. It should not be your default choice for all state. Yet time and time again, I see developers reach for the global store first, local component state second, and then wonder why their application is slow and hard to reason about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context API misuse.&lt;/strong&gt; The Context API is one of React's best features and also one of its most dangerous. Context is fantastic for dependency injection, theming, providing stable values throughout a tree, and avoiding prop drilling for data that truly needs to be accessed at multiple levels. But Context has a fundamental characteristic that many developers don't fully appreciate: when a Context value changes, every component consuming that Context re-renders. Every single one. Even if they're only using a tiny piece of the Context value.&lt;/p&gt;

&lt;p&gt;I've seen developers create a single massive Context provider for an entire feature area, stuffing everything into one Context value—user data, UI state, API responses, form values, you name it. Then they sprinkle useContext calls throughout dozens of components, and suddenly the entire feature re-renders whenever any piece of that Context changes. A loading flag flips from true to false? Everything re-renders. A form field updates? Everything re-renders. Some unrelated data gets updated? You get the idea.&lt;/p&gt;

&lt;p&gt;The solution isn't to avoid Context—it's to use it thoughtfully. Multiple smaller Contexts are often better than one large one. Context values should change infrequently. If you need frequently-changing values to be available throughout a tree, Context probably isn't the right tool—you need a proper state management solution with subscriptions, or you need to rethink whether that data really needs to be globally available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treating React like jQuery.&lt;/strong&gt; This is a more subtle mistake, but it's incredibly common among developers who came to React from imperative programming backgrounds or who haven't fully internalized React's declarative model. These developers write React code that's constantly fighting against React's design. They store references to DOM nodes and manipulate them directly. They try to coordinate updates imperatively instead of declaratively. They reach for useEffect for things that should just be derived state or regular event handlers. They treat state updates like direct mutations instead of transitions from one UI state to another.&lt;/p&gt;

&lt;p&gt;This approach leads to confusing code that's hard to reason about and often performs poorly because you're working against React's optimizations instead of with them. React is designed around the idea that you describe what the UI should look like for any given state, and React figures out how to make that happen efficiently. When you try to manually orchestrate things imperatively, you bypass those optimizations and create code that's both slower and more bug-prone.&lt;/p&gt;

&lt;h2&gt;
  
  
  How React Actually Works (And Why It's Fast By Design)
&lt;/h2&gt;

&lt;p&gt;To understand why React is not inherently slow, you need to understand what React actually does under the hood. I'm not going to walk you through the source code—that's not useful for most developers—but I am going to explain the conceptual model that makes React fast by design, because once you understand this, a lot of performance optimization becomes intuitive.&lt;/p&gt;

&lt;p&gt;React's core job is to keep your UI in sync with your state. That's it. That's the whole ballgame. You have some state, and you have a description of what the UI should look like for that state, and React's job is to make the actual DOM match that description. The genius of React is in how it does this efficiently, even when state changes frequently and the UI descriptions are complex.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconciliation&lt;/strong&gt; is the process React uses to figure out what actually needs to change in the DOM when your state updates. This is crucial because DOM manipulation is expensive—it's one of the slowest things you can do in a web browser. Creating DOM nodes, updating them, removing them, and especially causing layout recalculations and repaints—all of this is computationally expensive. So React's fundamental goal is to minimize DOM operations.&lt;/p&gt;

&lt;p&gt;Here's how it works conceptually: when your state changes and React needs to re-render, it doesn't immediately touch the real DOM. Instead, React calls your component functions to get a description of what the UI should look like—this description is your JSX, which becomes a tree of React elements. React then compares this new tree with the previous tree it rendered last time. This comparison process is reconciliation, and React's algorithm for doing this efficiently is pretty clever.&lt;/p&gt;

&lt;p&gt;React walks through the tree, comparing elements. If an element's type hasn't changed (still a div, still the same component), React can reuse the existing DOM node and just update its properties. If the type changed (was a div, now a span), React knows it needs to destroy the old DOM node and create a new one. For lists of elements, React uses keys to match up which elements correspond to which, so it can efficiently handle reordering, additions, and deletions without recreating everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Virtual DOM&lt;/strong&gt; is often misunderstood. It's not some magical performance silver bullet—in fact, maintaining the Virtual DOM has overhead. The Virtual DOM is simply React's way of representing your UI as JavaScript objects (React elements) instead of actual DOM nodes. The advantage is that comparing JavaScript objects is much faster than manipulating the real DOM, so React can diff the old and new Virtual DOM trees, figure out the minimal set of changes needed, and then apply those changes to the real DOM in a batch.&lt;/p&gt;

&lt;p&gt;Some people criticize the Virtual DOM as unnecessary overhead, and in some cases they're right—frameworks that compile to optimal imperative updates can be faster for certain workloads. But the Virtual DOM gives React a huge advantage: it makes React declarative and predictable. You don't have to manually track what changed and update the DOM accordingly. You just describe the entire UI for your current state, and React figures out the efficient way to make it happen. This makes React code much easier to write and maintain than imperative DOM manipulation, and for most applications, the performance is more than good enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rendering versus committing&lt;/strong&gt; is a crucial distinction that many developers don't understand. When React "renders" a component, it just calls your component function to get a React element tree. This is a pure operation—it doesn't cause side effects, doesn't touch the DOM, doesn't do anything except execute JavaScript to build a description of what the UI should be. Rendering is relatively cheap because it's just running JavaScript functions.&lt;/p&gt;

&lt;p&gt;The "commit" phase is when React actually applies changes to the DOM. After React has rendered your components and diffed the new tree against the old one, it enters the commit phase and performs the actual DOM mutations. This is more expensive because DOM operations are expensive. But here's the key insight: React batches commits. Even if multiple state updates happen in quick succession, React groups them together, renders once, and commits once. This batching is a massive performance win.&lt;/p&gt;

&lt;p&gt;Understanding this distinction helps you understand why some things cause performance problems and others don't. A component re-rendering is not necessarily expensive—it's just JavaScript execution. What's expensive is when that re-render leads to actual DOM updates, especially if those updates are large or cause layout recalculations. And what's really expensive is when you cause React to render and commit multiple times in rapid succession, bypassing the batching optimizations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;React is fast by design&lt;/strong&gt; because it's built around these principles: minimize expensive DOM operations, batch updates, reuse existing work when possible, and give developers a declarative API that lets React handle the optimization details. When people say React is slow, what they usually mean is that their application is causing React to do far more work than necessary—rendering huge component trees on every update, triggering excessive DOM mutations, or bypassing React's optimizations through poor architectural choices.&lt;/p&gt;

&lt;p&gt;The performance characteristics of React are actually pretty straightforward: rendering components is cheap until you have huge components or very deep trees, commits are expensive but React minimizes them, and side effects (like API calls, animations, or complex calculations) are as expensive as you make them. If you design your architecture around these characteristics—keep components small and focused, minimize unnecessary re-renders, and be thoughtful about expensive operations—React will be blazingly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Performance Killers Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Everyone talks about re-renders and memoization, but there are performance killers in React applications that don't get nearly enough attention. These are the issues that cause real, user-facing slowness, yet they often go undiagnosed because developers are too busy optimizing the wrong things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-fetching data&lt;/strong&gt; is probably the most common performance problem in modern React applications, and it's barely a React issue—it's an API design and data fetching issue. Your component loads, fetches data from an API, gets back a massive JSON payload with ten times more data than you need, parses it all, processes it all, stores it all in state, and then renders based on a tiny slice of it. Then you do this for five different endpoints on the same page load, and you wonder why your application feels slow.&lt;/p&gt;

&lt;p&gt;The problem is compounded when you're fetching data in individual components without coordination. Component A fetches data. Component B fetches overlapping data. Component C fetches related data. None of them know about each other. You end up with waterfall requests—A loads, triggers B to load, which triggers C to load—when you could have fetched all the necessary data in parallel or in a single request. Or worse, you have multiple components fetching the same data because you didn't implement any caching or deduplication.&lt;/p&gt;

&lt;p&gt;The solution isn't in React—it's in your data layer. You need proper API design that returns only what you need. You need a data fetching strategy that coordinates requests and caches results. Libraries like React Query, SWR, or Apollo Client solve a lot of these problems, but only if you use them thoughtfully. I've seen developers use these libraries and still over-fetch constantly because they didn't design their queries properly or because they're invalidating caches too aggressively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor memoization strategy&lt;/strong&gt; is another silent killer. And by "poor strategy," I don't just mean "not memoizing enough"—I mean having no coherent strategy at all. Developers either memoize nothing and suffer through unnecessary re-renders, or they memoize everything "just in case" and end up with code that's harder to read, harder to maintain, and sometimes actually slower because they're paying the memoization overhead for things that didn't need it.&lt;/p&gt;

&lt;p&gt;Here's the thing about memoization: it's not free. Every useMemo, useCallback, and React.memo call has overhead. You're trading CPU cycles spent on memoization checks against CPU cycles spent on re-rendering or recomputing. For simple components and cheap calculations, the memoization overhead can actually be more expensive than just doing the work again. I've profiled applications where aggressive memoization made things slower because developers were memoizing simple arithmetic or shallow component renders that would have been faster to just recompute.&lt;/p&gt;

&lt;p&gt;The right strategy is to memoize selectively based on actual measurement. Profile your application, identify the expensive operations, and memoize those. Don't memoize preemptively. Don't wrap every function in useCallback "because it's a dependency." Don't throw React.memo on every component "for optimization." Understand what's actually expensive in your application and optimize that specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expensive calculations inside render&lt;/strong&gt; is such an obvious mistake when you see it, but it's incredibly common. I've seen render functions that do complex data transformations on large arrays. Render functions that perform expensive regular expression operations on every render. Render functions that recursively traverse object trees or do distance calculations or parse dates in loops. All of this work happening on every single render, even when none of the relevant data has changed.&lt;/p&gt;

&lt;p&gt;The insidious part is that these expensive calculations often start out small and innocent. You have a simple sort or filter operation on a small array, and it's fine. Then your data set grows. Or you add another transformation. Or you nest these operations. Before you know it, you're spending 50 milliseconds on calculations in your render function, and your UI feels sluggish because React can't commit the update until your render function finishes running.&lt;/p&gt;

&lt;p&gt;This is where useMemo actually shines—not for memoizing components or callbacks, but for memoizing expensive derived state. If you have a calculation that depends on props or state but doesn't need to run on every render, memoize it. If you're transforming data structures in your render body, that's a code smell. Pull it out, memoize it, and only recompute when the dependencies actually change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad list rendering&lt;/strong&gt; is another performance killer that deserves way more attention than it gets. Rendering lists efficiently in React requires understanding a few key concepts that many developers ignore or misunderstand. First, keys. Everyone knows you need keys, but not everyone knows that keys need to be stable and unique. Using array indices as keys is fine if your list never reorders and items are never added or removed from anywhere but the end. Otherwise, you're going to confuse React's reconciliation and cause unnecessary DOM operations.&lt;/p&gt;

&lt;p&gt;But keys are just the start. The bigger issue is what you're rendering in each list item. If your list items are complex components that take many props, and those props change frequently, you're going to render your entire list constantly. This is especially brutal for long lists—imagine a list of 500 items where each item re-renders on every parent update because you're passing down inline functions or derived data that gets recreated on every render.&lt;/p&gt;

&lt;p&gt;For very long lists, you need virtualization—rendering only the items that are currently visible and recycling DOM nodes as the user scrolls. Libraries like react-window and react-virtualized exist for this reason. But even for moderately sized lists, you need to be thoughtful about minimizing list item re-renders through proper memoization, stable prop references, and component design that limits what data each item depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Good React Architecture Actually Looks Like
&lt;/h2&gt;

&lt;p&gt;Let me paint you a picture of what good React architecture actually looks like in practice. This isn't about following some dogmatic pattern or religiously applying a specific state management library. Good architecture is about understanding the principles of component design, state management, and data flow, and applying them thoughtfully to your specific application's needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Component responsibility boundaries&lt;/strong&gt; are the foundation of good React architecture. Each component should have a single, clear purpose. Not a single line of code—a single conceptual responsibility. A button component renders a button. A form component manages form state and validation. A list component renders a list. A data fetching component fetches data and passes it to presentational components. When a component starts doing multiple things, when you struggle to name it clearly, when you find yourself reaching for "and" in the component name—that's a signal to split it up.&lt;/p&gt;

&lt;p&gt;The way I think about component boundaries is to ask: if this piece of state changes, what needs to re-render? If the answer is "just this small part of the UI," then that state should live in a component as close to that UI as possible. If the answer is "several different parts of the UI that aren't directly related," then you might need lifted state or a more sophisticated state management solution. But if the answer is "everything, even though most of it doesn't care," then your component boundaries are wrong.&lt;/p&gt;

&lt;p&gt;Good component design also means thinking about what data a component owns versus what data it receives. A component should own state that's entirely internal to its operation—form field values, open/closed states for dropdowns, animation states, things like that. A component should receive data through props when that data is determined by parent components or external state. Mixing these concerns—having components that both own critical state and receive critical state—leads to confusion and bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Smart versus dumb components&lt;/strong&gt; is an older pattern that fell out of fashion for a while, but the underlying principle is still incredibly valuable. You want a clear separation between components that contain logic, manage state, and handle side effects (smart components, sometimes called container components) and components that just receive data through props and render UI (dumb components, sometimes called presentational components).&lt;/p&gt;

&lt;p&gt;This separation gives you several huge benefits. First, your presentational components become incredibly easy to test—you just pass in props and verify the output. Second, they become highly reusable—the same presentational component can be used with different data sources and in different contexts. Third, your application becomes much easier to reason about because you can look at a component and immediately know whether it's going to do anything surprising or if it's just going to render what you give it.&lt;/p&gt;

&lt;p&gt;In practice, this means you might have a UserProfile component that fetches user data, manages editing state, and handles save operations, and then it renders a UserProfileView component that just takes that data as props and displays it. The smart component is ugly and imperative and full of business logic. The presentational component is clean and declarative and easy to understand. You've separated concerns, and your architecture is better for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;State colocation&lt;/strong&gt; is one of those principles that sounds simple but has profound implications. State should live as close as possible to where it's used. If only one component needs a piece of state, that state should live in that component. If several sibling components need to share state, it should live in their nearest common ancestor. If components across your entire application need access to state, then and only then should it be in global state.&lt;/p&gt;

&lt;p&gt;The reason this matters so much for performance is that React's re-rendering is based on state changes. When state changes, React re-renders the component that owns that state and all of its descendants. If you lift state higher than necessary, you're re-rendering a bigger portion of your tree than you need to. If you put state in a global store when it's only used in one small section of your UI, you're paying the coordination and subscription overhead for no benefit.&lt;/p&gt;

&lt;p&gt;I've reviewed applications where almost all state was lifted to the root component or put in Redux, and the developers justified this by saying "we might need it somewhere else later" or "it's easier to have all state in one place." But when I asked them to identify which state was actually shared across multiple parts of the UI, it was maybe 10% of what they had lifted. The other 90% was causing unnecessary re-renders and making the application harder to understand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Composition over configuration&lt;/strong&gt; is another principle that dramatically improves React architecture. Instead of creating configurable components with lots of props and conditional rendering logic, create simple, focused components and compose them together to build more complex UIs. Instead of a Button component with props for isLoading, isDisabled, hasIcon, iconPosition, variant, size, and ten other configuration options, create simple composable pieces and let the consuming code combine them as needed.&lt;/p&gt;

&lt;p&gt;Composition leads to more flexible and maintainable code because you're not trying to anticipate every possible use case in a single component. You're creating building blocks that can be combined in ways you might not have predicted. It also tends to lead to better performance because simpler components with fewer props are easier for React to optimize and less likely to re-render unnecessarily.&lt;/p&gt;

&lt;p&gt;A concrete example: instead of a Card component that has props for headerContent, bodyContent, footerContent, isCollapsible, isExpanded, and various styling options, create Card, CardHeader, CardBody, and CardFooter components that can be composed together. The parent code becomes more verbose, but it's also more flexible and clearer. Each piece does one thing well, and performance characteristics are more predictable.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Actually Fix Performance Problems
&lt;/h2&gt;

&lt;p&gt;Let's get practical. You've identified that your React application has performance problems. You've measured, you've profiled, and you know where the bottlenecks are. How do you actually fix them? And just as importantly, how do you know when to fix them and when to leave well enough alone?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When to optimize and when not to&lt;/strong&gt; is the first question you need to answer, and it's not always obvious. The conventional wisdom is "don't optimize prematurely," which is good advice but not very actionable. Here's my rule: optimize when you have evidence of a user-facing performance problem and you've identified the cause through profiling. Don't optimize based on assumptions about what might be slow. Don't optimize because a component "feels" like it might re-render too much. Don't optimize because you read a blog post about React performance and now you're worried.&lt;/p&gt;

&lt;p&gt;Performance optimization has costs. It makes your code more complex. It makes it harder for other developers (including future you) to understand what's happening. It introduces potential bugs when your memoization dependencies are wrong or your optimization assumptions become invalid. You should only pay these costs when you're getting clear benefits in return, and the only way to know that is through measurement.&lt;/p&gt;

&lt;p&gt;When you do optimize, start with the biggest problems first. If you have a component that takes 200ms to render and another that takes 5ms, fix the 200ms one first. This sounds obvious, but I've seen developers spend days optimizing small issues while ignoring the elephant in the room. Profile your application under realistic conditions—realistic data sizes, realistic user interactions, realistic network conditions—and fix the problems that actually impact users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to think about useMemo, useCallback, and React.memo&lt;/strong&gt; is crucial because these are the main tools React gives you for performance optimization, and they're widely misunderstood. These tools are not magic. They're trade-offs. They have overhead. They add complexity. They should be used judiciously, not reflexively.&lt;/p&gt;

&lt;p&gt;useMemo is for expensive calculations. If you're doing complex data transformations, sorting large arrays, performing recursive operations, or calculating derived state that's expensive to compute, useMemo can help. It caches the result and only recomputes when dependencies change. But if your calculation is simple—basic arithmetic, accessing object properties, simple array operations on small arrays—useMemo's overhead might exceed the cost of just doing the calculation again.&lt;/p&gt;

&lt;p&gt;useCallback is for stable function references. This is useful when you're passing callbacks to child components that are wrapped in React.memo or when function identity matters for dependency arrays. But here's the thing: in many cases, creating a new function on each render is completely fine. Functions are cheap. If your child component isn't memoized or the render is cheap anyway, useCallback adds overhead for no benefit.&lt;/p&gt;

&lt;p&gt;React.memo is for preventing component re-renders when props haven't changed. It's great for expensive components that receive the same props frequently, like list items in a long list or complex visualizations that are expensive to render. But for simple components—a button, a text label, a simple div with a few children—the memo check might be more expensive than just re-rendering. Measure first.&lt;/p&gt;

&lt;p&gt;The pattern I follow is: write code without memoization first, profile to identify actual performance problems, then add memoization surgically to fix those specific problems. Don't memoize preemptively. Don't wrap everything in useMemo and useCallback "just to be safe." Start simple, measure, optimize what matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measuring before optimizing&lt;/strong&gt; can't be stressed enough. React DevTools has an excellent profiler that shows you exactly what's rendering, how long it takes, and why it rendered. Use it. Record a profile of a slow interaction. Look at the flame graph. Identify which components are taking the most time. Look at what caused them to render—was it a props change, a state change, a context update? Once you know what's slow and why, you can fix it intelligently.&lt;/p&gt;

&lt;p&gt;Network performance matters too. Use your browser's network tab. Are you making too many requests? Are your requests too slow? Are you fetching data you don't need? Are you triggering waterfalls? Sometimes what feels like a React performance problem is actually a data fetching problem. You can optimize your React code all day, but if you're waiting 2 seconds for an API response, your app will still feel slow.&lt;/p&gt;

&lt;p&gt;Real user monitoring is valuable for production applications. Tools that track actual user experience metrics—First Contentful Paint, Time to Interactive, interaction latency—give you insight into what real users experience, not just what you see in your development environment. Sometimes performance problems only manifest at scale, with slow devices, or with poor network conditions. Synthetic benchmarks in your dev environment won't catch those.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bad Architecture vs Good Architecture: A Real Comparison
&lt;/h2&gt;

&lt;p&gt;Let me contrast what bad versus good architecture looks like in practice, with specific examples of how architectural choices cascade into performance problems or smooth user experiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad architecture:&lt;/strong&gt; You're building a dashboard with multiple widgets that display different kinds of data. You decide to manage all the state in a single component at the root of the dashboard. This root component fetches all the data for all widgets on mount. It stores everything—data for each widget, loading states, error states, filter selections, sort orders, pagination state—in a massive state object. Each widget is a child component that receives its slice of data through props that are derived in the root component's render function. When any piece of state changes—a user changes a filter, data comes back from an API, a widget refreshes—the entire dashboard re-renders. All widgets re-render, even though only one widget's data changed. The derived data calculations run again for all widgets. The entire component tree gets processed. The UI feels sluggish because every interaction causes a large re-render cycle.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good architecture:&lt;/strong&gt; The same dashboard, but structured differently. Each widget is a self-contained component that manages its own data fetching, loading states, and error handling. The root dashboard component just renders the widgets and provides shared context like the current user or theme. Each widget only re-renders when its own state changes. When one widget fetches new data, the others are unaffected. Filtering a widget only re-renders that widget. The dashboard is fast because work is localized—only the part of the UI that needs to update actually updates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad architecture:&lt;/strong&gt; You have a form with twenty fields. Every field is controlled—you have state for each field value. You lift all this state to the form's parent component. You have an onChange handler that updates state on every keystroke. Because all the state is in the parent, every keystroke triggers a re-render of the entire form and all twenty fields. Each field re-renders because it receives new props on every update, even if its own value didn't change. The form feels laggy because you're processing twenty field components on every keystroke. You try to fix it by wrapping each field in React.memo, but that doesn't help much because the field components are receiving new onChange handlers on every render (they're defined inline in the parent's render function). You add useCallback to stabilize the handlers, which helps some, but the form is still slow because you're fundamentally doing too much work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good architecture:&lt;/strong&gt; Each form field is its own component with its own internal state. Fields are uncontrolled by default—they manage their own values until form submission. When you need to validate or submit the form, you read the values from the fields. This way, typing in one field only re-renders that field. The rest of the form is unaffected. The form feels instant because each interaction is localized to a single component. For fields that need to interact with each other (like a "password confirmation" field that needs to validate against the "password" field), you lift just that shared state to the nearest common ancestor, not all state to the form root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad architecture:&lt;/strong&gt; You need to share some user data across multiple parts of your application. You create a UserContext that includes everything about the user—profile data, preferences, recently viewed items, shopping cart, notification settings, UI state like which modals are open, everything. You wrap your entire application in this UserContext provider. Now you have dozens of components throughout your app consuming this context with useContext(UserContext). When any piece of the user data updates—the user changes their theme preference, adds an item to their cart, dismisses a notification—every single component consuming UserContext re-renders. A user typing in a search box that updates a "recent searches" array in the UserContext causes your navigation bar, your sidebar, your footer, and twenty other components to re-render even though they don't care about recent searches. You try to fix this by splitting the context into smaller pieces, but now you have eight different context providers nested at the root, and you're not sure which components should consume which contexts. The performance is still bad because your context values aren't stable—you're creating new objects on every render, which causes all consumers to re-render even when the actual data hasn't changed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good architecture:&lt;/strong&gt; You create multiple, focused context providers. AuthContext for authentication state (user ID, auth token, login/logout functions). ThemeContext for theme preferences. CartContext for shopping cart state. Each context has a single, well-defined purpose. Context values are stable—you use useMemo to ensure that the context value object only changes when the actual data inside it changes. Components only consume the contexts they actually need. Your navigation bar consumes AuthContext and ThemeContext but not CartContext. Your product list doesn't consume any contexts at all—it receives data through props. When cart data changes, only components that care about the cart re-render. The rest of your application is unaffected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad architecture:&lt;/strong&gt; You're building a data table with sorting, filtering, and pagination. All the logic lives in one giant component. The component maintains state for the current page, sort column, sort direction, filter values, and the data itself. Every time any of these change, the entire table re-renders. You're rendering all rows in the dataset, even though only 20 are visible at a time—you're just hiding the others with CSS. The row rendering logic is complex, with lots of conditional formatting based on cell values, and it's all inline in the main component's render function. The component is 800 lines long. Sorting the table feels slow because you're re-rendering potentially thousands of hidden rows. Changing filters is slow because you're running the filter logic on the entire dataset and then re-rendering everything. The code is hard to modify because all the logic is tangled together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good architecture:&lt;/strong&gt; The table is composed of several focused components. A Table component handles layout. A TableHeader component handles the column headers and sorting UI. A TableBody component handles rendering visible rows. A useTableData custom hook handles the data fetching, filtering, sorting, and pagination logic. The hook returns only the data for the current page, already filtered and sorted. Row components are simple, memoized presentational components. When you sort, the hook recalculates which rows should be visible, and only those rows get rendered. The table only renders what's actually visible on screen—20 rows, not 2000. Each piece of the system has a clear responsibility. The code is maintainable because you can modify the filtering logic without touching the rendering code, or update how rows display without touching the data management logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad architecture:&lt;/strong&gt; You have a complex workflow with multiple steps—a checkout process, a multi-step form, a wizard interface. You manage all the state for all steps in a single reducer at the root. Every step is a separate component, but they all receive the entire state object as props. You pass down dispatch functions to allow steps to update state. As users move through the workflow, you keep all previous steps mounted in the DOM (just hidden with display: none) because you need to preserve their state. The entire workflow re-renders whenever state changes, even though only one step is visible at a time. You have conditional logic scattered throughout to handle different workflow states. Some steps have their own internal state that conflicts with the centralized state, leading to bugs where the UI gets out of sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good architecture:&lt;/strong&gt; Each step is a self-contained component that manages its own internal state. A workflow manager component handles navigation between steps and maintains only the minimal shared state (current step index, data that needs to persist across steps). When you navigate to a step, that step mounts, initializes its state from any persisted data, and operates independently. When the step completes, it calls a callback with its final data, which the workflow manager stores. Previous steps unmount—they're not kept in the DOM. This keeps memory usage down and ensures only the current step re-renders when its state changes. The workflow manager doesn't know or care about the internal workings of each step; it just coordinates the flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Truth Developers Don't Want to Hear
&lt;/h2&gt;

&lt;p&gt;Here's where I'm going to be blunt, because I think this needs to be said: blaming React for performance problems is often intellectual laziness. It's easier to say "React is slow" than to admit "I structured this poorly" or "I don't fully understand how React works" or "I made architectural decisions that seemed fine at the time but don't scale."&lt;/p&gt;

&lt;p&gt;I get it. I really do. When you're under pressure to ship features, when you're working with tight deadlines, when you're learning React while building production applications, you make mistakes. You take shortcuts. You do things the quick way instead of the right way. You think "I'll refactor this later" but later never comes because there's always another feature, another deadline, another fire to put out. Before you know it, you have an application with architectural problems baked into its foundation, and those problems are now incredibly difficult to fix because so much code depends on the current structure.&lt;/p&gt;

&lt;p&gt;But here's the thing: acknowledging this is the first step to actually getting better. Every senior React developer I know has built terrible React applications. We've all created components that were too big, state that was too global, re-renders that were too frequent. We've all looked at our own code six months later and wondered what we were thinking. The difference between a developer who grows and one who stagnates is whether they learn from these mistakes or just keep blaming the tools.&lt;/p&gt;

&lt;p&gt;React gives you an enormous amount of rope to hang yourself with. It's not opinionated enough to prevent you from making bad architectural decisions. It won't stop you from putting everything in global state. It won't force you to split up your 1000-line component. It won't automatically optimize away your unnecessary re-renders. This is both a strength and a weakness. The flexibility that makes React powerful for building complex UIs also makes it easy to build poorly-performing UIs if you don't understand what you're doing.&lt;/p&gt;

&lt;p&gt;Some frameworks are more opinionated and thus harder to misuse. Svelte compiles away a lot of potential performance problems. Solid.js has fine-grained reactivity that makes unnecessary re-renders much less common. Angular has strong conventions about how to structure applications. These frameworks make certain classes of mistakes harder to make, and that's genuinely valuable, especially for teams that don't have deep expertise in frontend performance.&lt;/p&gt;

&lt;p&gt;But switching frameworks isn't a magic solution. If you don't understand why your React application is slow, you'll probably build a slow Svelte application too, just with different performance characteristics and different footguns. The fundamental principles of good frontend architecture—component design, state management, data flow, performance-conscious rendering—apply regardless of which framework you use. A developer who doesn't understand these principles will struggle with any framework.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftee4y6z7nqtpjn2ymdct.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftee4y6z7nqtpjn2ymdct.gif" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I've seen teams rewrite applications from React to something else, and you know what happens? Sometimes they get better performance, because they're effectively doing a full architectural redesign and they learn from their mistakes. Sometimes they get worse performance, because they bring their bad habits to the new framework. And sometimes they get the same performance, because the framework was never the bottleneck—their API design, their data fetching strategy, their component architecture, those were the real problems, and those problems follow you regardless of which view layer you use.&lt;/p&gt;

&lt;p&gt;React's performance characteristics are well-understood. The rendering model is documented. The reconciliation algorithm is explained. The profiling tools exist. The best practices are written down, discussed in conferences, debated in blog posts. If your React application is slow, the information you need to fix it is available. What's required is the intellectual honesty to diagnose the real problem instead of blaming the framework, and the discipline to actually refactor your architecture instead of just slapping useMemo on everything and hoping it gets better.&lt;/p&gt;

&lt;p&gt;I say this with empathy, not judgment, because I've been there. I've been the developer who thought Redux would solve all my state management problems, only to discover I'd just moved the complexity to a different place. I've been the developer who wrapped every component in React.memo because I read that it improves performance, only to discover I'd made my code harder to maintain for negligible benefit. I've been the developer who blamed React's re-rendering model for my application's slowness when the real problem was that I was fetching data inefficiently and calculating expensive derived state on every render.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft16e97rjk5075y2gi7wx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft16e97rjk5075y2gi7wx.png" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The path to building fast React applications isn't about learning secret optimization tricks or advanced techniques that only senior developers know. It's about understanding the fundamentals deeply, thinking carefully about architecture before you build, measuring instead of guessing, and being willing to refactor when you realize your initial approach doesn't scale. It's about being honest about what you don't know and taking the time to learn it properly instead of cargo-culting patterns you don't understand.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing: React Is Not the Problem
&lt;/h2&gt;

&lt;p&gt;So let's bring this all back to where we started. React is not slow. React is a tool, and like any tool, its performance depends on how you use it. A hammer isn't defective because you tried to use it to cut wood. A car isn't slow because you left the parking brake on. And React isn't slow because you structured your application in a way that causes excessive re-renders, poor data flow, and architectural complexity that works against React's design.&lt;/p&gt;

&lt;p&gt;The React applications you've used that feel fast—and there are plenty of them, from complex enterprise applications to consumer products used by millions—aren't fast because their developers discovered some secret optimization technique. They're fast because they were built with solid architectural principles: components with clear responsibilities, state that lives at the appropriate level, data flow that's predictable and efficient, and optimization applied judiciously where measurement shows it matters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7aj8wfd77aujxojkua1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7aj8wfd77aujxojkua1.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you encounter a performance problem in a React application, the question shouldn't be "Is React the right choice?" The question should be "What architectural decision led to this problem?" Is state lifted too high? Are components doing too much? Is data being fetched inefficiently? Are expensive calculations running on every render? Is the component tree structure causing excessive re-renders? These are the real questions, and these are the questions that have actionable answers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b5xzemhb5p9bspq0sei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b5xzemhb5p9bspq0sei.png" alt=" " width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Building fast React applications is a skill, and like any skill, it requires learning, practice, and sometimes painful mistakes. You have to build something slow to really understand why it's slow. You have to debug performance problems to internalize how React's rendering model works. You have to refactor bad architecture to appreciate good architecture. This learning process is frustrating, but it's also how you get better.&lt;/p&gt;

&lt;p&gt;The good news is that React gives you all the tools you need to build performant applications. The reconciliation algorithm is efficient. The hooks API gives you fine-grained control over rendering and side effects. The profiling tools let you see exactly what's happening. The component model encourages good architectural patterns if you're thoughtful about how you use it. React's not perfect—no framework is—but it's more than capable of handling complex, performant UIs when used well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgmjg6r4yunovg81mb72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdgmjg6r4yunovg81mb72.png" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the next time you're tempted to blame React for performance problems, take a step back. Profile your application. Identify what's actually slow. Understand why it's slow. Then fix your architecture. Split up that massive component. Move that state closer to where it's used. Stop re-rendering components that don't need to update. Optimize your data fetching. Apply memoization where it actually matters. Make conscious, measured architectural decisions instead of reaching for patterns because you saw them in a tutorial.&lt;/p&gt;

&lt;p&gt;React is not slow. Your architecture is. And the beautiful thing about architecture is that you can change it. It takes work, it takes learning, it takes discipline, but you can do it. You can build fast React applications. Millions of developers have done it before you, and you can too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7b4ht2zuo71nj8bk67a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7b4ht2zuo71nj8bk67a.png" alt=" " width="500" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The framework isn't the limitation. Your understanding and application of good architectural principles is the limitation. Invest in deepening that understanding, building that skill, developing that discipline. Measure, learn, refactor, improve. That's the path to building React applications that perform well.&lt;/p&gt;

&lt;p&gt;React is a powerful, flexible, and yes, fast tool for building user interfaces. Use it well.&lt;/p&gt;


&lt;div class="ltag__user ltag__user__id__3615540"&gt;
    &lt;a href="/hanzla" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=150,height=150,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3615540%2Fe3e2334c-afc4-44a7-a3f5-a3b66f0658e0.png" alt="hanzla image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/hanzla"&gt;Hanzla Baig&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/hanzla"&gt;👨‍💻 I turn coffee-free ideas into bugs 🐛  
🚀 Co-founder @ TheBitForge  
✍️ Sharing dev adventures, wins &amp;amp; fails  
🇵🇰 Building, breaking &amp;amp; learning daily 😅&lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Thanks for reading! 🙏🏻 &lt;br&gt; I hope you found this useful ✅ &lt;br&gt; Please react and follow for more 😍 &lt;br&gt; Made with 💙 by &lt;a href="https://dev.to/hanzla"&gt;Hanzla Baig&lt;/a&gt;
&lt;/th&gt;
&lt;th&gt;
&lt;a href="https://www.hanzla-beig.netlify.app" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu48q29oef3l4a6eow30h.png" alt="LinkedIn" width="40" height="40"&gt;&lt;/a&gt; &lt;a href="https://github.com/wecoded-dev" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhuvszgj6eun7xfvnwv51.png" alt="GitHub" width="50" height="50"&gt;&lt;/a&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>programming</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
