<?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: Billy Okeyo</title>
    <description>The latest articles on DEV Community by Billy Okeyo (@billy_de_cartel).</description>
    <link>https://dev.to/billy_de_cartel</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%2F363015%2Fb7cad6bd-0cc8-4b82-b298-5fcdfcfe48cf.jpg</url>
      <title>DEV Community: Billy Okeyo</title>
      <link>https://dev.to/billy_de_cartel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/billy_de_cartel"/>
    <language>en</language>
    <item>
      <title>Distributed Locks Explained: Technologies That Implement Distributed Locks</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/distributed-locks-explained-technologies-that-implement-distributed-locks-30l6</link>
      <guid>https://dev.to/billy_de_cartel/distributed-locks-explained-technologies-that-implement-distributed-locks-30l6</guid>
      <description>&lt;p&gt;Now that we understand how distributed locks work conceptually, the next question becomes:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Where do these locks actually live?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Unlike database transactions, distributed locks cannot rely on the memory of a single application server. Remember, our application might be running on five, ten, or even hundreds of machines, and every server must consult the same source of truth before deciding whether it may perform a particular operation.&lt;/p&gt;

&lt;p&gt;Over the years, several technologies have emerged to solve this coordination problem. Although they all provide distributed locking capabilities, they were designed with slightly different goals in mind. Let’s look at the most common ones.&lt;/p&gt;





&lt;h2 id="redis"&gt;Redis&lt;/h2&gt;

&lt;p&gt;For most web applications, &lt;strong&gt;Redis&lt;/strong&gt; is by far the most popular choice. Originally designed as an in-memory data store, Redis is incredibly fast, making it an excellent candidate for lightweight coordination tasks.&lt;/p&gt;

&lt;p&gt;Acquiring a lock in Redis is surprisingly straightforward. A server attempts to create a key using an atomic command that says, in effect:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“Create this key only if it doesn’t already exist.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If Redis successfully creates the key, the server owns the lock; if the key already exists, another server is already performing the work. Because Redis executes this operation atomically, two servers can never successfully create the same lock at the same time.&lt;/p&gt;

&lt;p&gt;A simplified example looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SET invoice-generation

Server-A

NX

EX 30
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The command says:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create the key only if it doesn’t already exist (&lt;code&gt;NX&lt;/code&gt;).&lt;/li&gt;
  &lt;li&gt;Automatically expire it after thirty seconds (&lt;code&gt;EX 30&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In one atomic operation, Redis both acquires the lock and ensures it won’t remain forever if the server crashes. For many applications, this is all that’s needed.&lt;/p&gt;





&lt;h2 id="why-redis-is-so-popular"&gt;Why Redis Is So Popular&lt;/h2&gt;

&lt;p&gt;Redis has become the default choice for distributed locks because it satisfies three important requirements. First, it’s extremely fast: since Redis stores data in memory rather than on disk, lock acquisition usually takes only a few milliseconds. Second, many applications already use Redis for caching, sessions, queues, or rate limiting, so adding distributed locking often requires little additional infrastructure. Finally, Redis has mature client libraries for virtually every programming language, with Laravel, Django, Spring Boot, .NET, Node.js, and Go all providing excellent Redis support.&lt;/p&gt;

&lt;p&gt;For the vast majority of business applications, Redis offers an excellent balance between simplicity, performance, and reliability.&lt;/p&gt;





&lt;h2 id="the-challenge-with-a-single-redis-instance"&gt;The Challenge with a Single Redis Instance&lt;/h2&gt;

&lt;p&gt;Suppose your entire application depends on one Redis server. Everything works perfectly, then Redis crashes. Suddenly, no application server can acquire new locks, and even worse, if Redis loses its in-memory state during a restart, locks may disappear unexpectedly.&lt;/p&gt;

&lt;p&gt;This introduces a new challenge: the coordinator itself has become a single point of failure. For many applications, this risk is acceptable. For others, particularly financial systems or globally distributed services, it isn’t. This challenge led to one of the most discussed topics in distributed systems: the &lt;strong&gt;Redlock algorithm&lt;/strong&gt;.&lt;/p&gt;





&lt;h2 id="the-redlock-algorithm"&gt;The Redlock Algorithm&lt;/h2&gt;

&lt;p&gt;Redlock was proposed by Redis creator &lt;strong&gt;Salvatore Sanfilippo&lt;/strong&gt; as a way to make Redis-based distributed locks more resilient. Instead of relying on one Redis server, Redlock uses multiple independent Redis instances.&lt;/p&gt;

&lt;p&gt;Imagine five Redis servers.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Redis 1

Redis 2

Redis 3

Redis 4

Redis 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When acquiring a lock, the application attempts to obtain it from all five servers, and the lock is considered successful only if a majority agree.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Acquire Lock

↓

Redis 1 ✅

Redis 2 ✅

Redis 3 ✅

Redis 4 ❌

Redis 5 ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Three out of five succeeded, so the application proceeds. If only two servers grant the lock, the operation fails because a majority wasn’t reached. This approach significantly reduces the impact of individual Redis failures.&lt;/p&gt;

&lt;p&gt;However, Redlock is also one of the most debated algorithms in distributed systems. Some engineers argue that it’s sufficient for many practical systems, while others, including Martin Kleppmann, have published detailed critiques explaining situations where Redlock may not provide the guarantees developers expect. The important lesson isn’t that Redlock is good or bad; it’s that distributed systems involve trade-offs, and understanding those trade-offs matters more than memorizing algorithms.&lt;/p&gt;





&lt;h2 id="zookeeper"&gt;ZooKeeper&lt;/h2&gt;

&lt;p&gt;Long before Redis became popular for distributed locking, many large distributed systems relied on &lt;strong&gt;Apache ZooKeeper&lt;/strong&gt;. ZooKeeper was designed specifically for coordination. Rather than functioning as a cache, it provides services such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Distributed locks&lt;/li&gt;
  &lt;li&gt;Leader election&lt;/li&gt;
  &lt;li&gt;Configuration management&lt;/li&gt;
  &lt;li&gt;Service discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of ZooKeeper as a highly reliable coordinator for distributed applications. Its primary goal isn’t speed, it’s correctness. Large systems such as Apache Kafka, Hadoop, and HBase have historically relied on ZooKeeper to coordinate clusters of machines. If your application requires complex distributed coordination rather than simple locking, ZooKeeper remains an excellent choice.&lt;/p&gt;





&lt;h2 id="etcd"&gt;etcd&lt;/h2&gt;

&lt;p&gt;If you’ve worked with Kubernetes, you’ve already encountered &lt;strong&gt;etcd&lt;/strong&gt;, even if you didn’t realize it. Every Kubernetes cluster stores its configuration inside etcd. Like ZooKeeper, etcd is a distributed key-value store designed for coordination rather than caching. It provides:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Distributed locks&lt;/li&gt;
  &lt;li&gt;Leader election&lt;/li&gt;
  &lt;li&gt;Configuration storage&lt;/li&gt;
  &lt;li&gt;Consensus&lt;/li&gt;
  &lt;li&gt;Service coordination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unlike Redis, etcd prioritizes consistency over raw performance. Its API is also designed around long-lived leases, making lock management particularly elegant. Modern cloud-native applications frequently choose etcd when they already operate within Kubernetes ecosystems.&lt;/p&gt;





&lt;h2 id="consul"&gt;Consul&lt;/h2&gt;

&lt;p&gt;HashiCorp &lt;strong&gt;Consul&lt;/strong&gt; occupies a similar space. Although many developers know Consul for service discovery, it also provides distributed locking capabilities through sessions. Organizations using Consul often rely on it for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Service registration&lt;/li&gt;
  &lt;li&gt;Health checks&lt;/li&gt;
  &lt;li&gt;Distributed configuration&lt;/li&gt;
  &lt;li&gt;Leader election&lt;/li&gt;
  &lt;li&gt;Distributed locks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like ZooKeeper and etcd, Consul focuses on reliable coordination across distributed infrastructure.&lt;/p&gt;





&lt;h2 id="which-technology-should-you-choose"&gt;Which Technology Should You Choose?&lt;/h2&gt;

&lt;p&gt;There isn’t a universal answer. Instead, the right choice depends on your application’s requirements. If you’re building a typical web application that already uses Redis, implementing distributed locks with Redis is usually the simplest and most practical solution. If you’re coordinating hundreds of services across a Kubernetes cluster, etcd may integrate more naturally with your infrastructure. If your organization already uses Consul or ZooKeeper for service coordination, leveraging those existing systems often makes more sense than introducing Redis solely for locking.&lt;/p&gt;

&lt;p&gt;Choosing a technology isn’t just about features. It’s also about operational complexity, existing infrastructure, and the guarantees your business requires. The important thing to remember is this: distributed locks are a concept, while Redis, ZooKeeper, etcd, and Consul are simply different tools for implementing that concept. Understanding the underlying idea is far more valuable than becoming attached to a specific technology.&lt;/p&gt;





&lt;h2 id="do-you-always-need-a-distributed-lock"&gt;Do You Always Need a Distributed Lock?&lt;/h2&gt;

&lt;p&gt;Reading this article, it might be tempting to conclude that distributed locks are the solution to every concurrency problem. They’re not. In fact, many applications never need them. If your application runs on a single server, ordinary in-memory locks are often sufficient, and if your database transaction already guarantees correctness, introducing a distributed lock may simply add unnecessary complexity.&lt;/p&gt;

&lt;p&gt;Distributed locks are powerful, but they should be introduced only when multiple independent application instances genuinely need to coordinate shared work. Like every distributed systems technique, they solve a very specific class of problems, and the best engineering decision is often knowing when &lt;strong&gt;not&lt;/strong&gt; to use them.&lt;/p&gt;

&lt;h2 id="common-mistakes-when-using-distributed-locks"&gt;Common Mistakes When Using Distributed Locks&lt;/h2&gt;

&lt;p&gt;Like many distributed systems concepts, distributed locks appear deceptively simple: acquire a lock, perform some work, release the lock. In practice, however, there are several subtle mistakes that can introduce bugs that are even harder to diagnose than the problem the lock was intended to solve. Understanding these pitfalls is just as important as understanding distributed locks themselves.&lt;/p&gt;





&lt;h3 id="assuming-a-lock-lasts-forever"&gt;Assuming a Lock Lasts Forever&lt;/h3&gt;

&lt;p&gt;One of the most common mistakes is forgetting that distributed locks usually have an expiration time. Suppose a server acquires a lock with a TTL of thirty seconds, and the developer assumes the operation will always finish within that window. Months later, a new feature makes the operation take forty-five seconds. The lock expires while the first server is still working, another server acquires the same lock and begins executing the exact same task, and suddenly, duplicate work appears again.&lt;/p&gt;

&lt;p&gt;Choosing an appropriate TTL, and renewing it for long-running tasks when necessary, is essential.&lt;/p&gt;





&lt;h3 id="forgetting-to-release-the-lock"&gt;Forgetting to Release the Lock&lt;/h3&gt;

&lt;p&gt;Although expiration protects against permanent deadlocks, applications should still release locks as soon as the protected work finishes. Holding a lock longer than necessary reduces concurrency and delays other servers waiting to perform legitimate work.&lt;/p&gt;

&lt;p&gt;A good rule is simple:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Hold the lock only for the work that genuinely requires exclusive access.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Everything else should happen outside the lock whenever possible.&lt;/p&gt;





&lt;h3 id="protecting-too-much-code"&gt;Protecting Too Much Code&lt;/h3&gt;

&lt;p&gt;Developers sometimes wrap entire workflows inside a distributed lock. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Acquire Lock

↓

Call External Payment API

↓

Generate PDF

↓

Upload File

↓

Send Email

↓

Release Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This means every other server waits while network requests, file generation, and email delivery are taking place. Often, only a small portion of the workflow actually requires exclusive access. A better approach is to keep the critical section as short as possible.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Acquire Lock

↓

Update Shared Resource

↓

Release Lock

↓

Generate PDF

↓

Send Email
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The shorter the lock, the better the system scales.&lt;/p&gt;





&lt;h2 id="distributed-locks-vs-database-locks"&gt;Distributed Locks vs Database Locks&lt;/h2&gt;

&lt;p&gt;At first glance, distributed locks and database locks appear very similar. Both prevent concurrent operations and both coordinate access to shared resources, but they operate at completely different levels.&lt;/p&gt;

&lt;p&gt;A database lock protects &lt;strong&gt;data inside the database&lt;/strong&gt;. For example, when two transactions attempt to update the same customer record, the database can lock that row until one transaction completes. Everything happens within the database engine itself.&lt;/p&gt;

&lt;p&gt;A distributed lock protects &lt;strong&gt;work performed by application servers&lt;/strong&gt;. Instead of preventing two transactions from updating the same row, it prevents two application instances from starting the same business process. Consider generating monthly invoices: before any invoice rows even exist in the database, every server must first decide whether it should begin the job. That decision happens outside the database, and a distributed lock coordinates that decision.&lt;/p&gt;

&lt;p&gt;An easy way to remember the difference is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Database locks protect data. Distributed locks protect business operations.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In many systems, you’ll use both together. A distributed lock ensures only one server begins generating invoices, and database transactions then ensure every invoice is written consistently.&lt;/p&gt;





&lt;h2 id="distributed-locks-vs-optimistic-concurrency"&gt;Distributed Locks vs Optimistic Concurrency&lt;/h2&gt;

&lt;p&gt;Another concept frequently confused with distributed locks is optimistic concurrency. Optimistic concurrency assumes conflicts are relatively rare. Instead of preventing multiple users from editing the same record, it detects whether someone else changed the data before saving.&lt;/p&gt;

&lt;p&gt;Suppose two employees open the same customer profile, and each begins editing. The system stores a version number alongside the record. When Employee A saves, the version changes from &lt;strong&gt;5&lt;/strong&gt; to &lt;strong&gt;6&lt;/strong&gt;. When Employee B later attempts to save, the application notices that the version has already changed. Rather than silently overwriting Employee A’s work, it rejects the update and asks Employee B to refresh the page. No locking was required; the conflict was simply detected before committing.&lt;/p&gt;

&lt;p&gt;Distributed locks take a different approach. Instead of detecting conflicts afterward, they prevent conflicting work from starting in the first place. Neither technique is universally better: optimistic concurrency works well when conflicts are uncommon, while distributed locks work best when duplicate execution would be expensive or dangerous.&lt;/p&gt;





&lt;h2 id="best-practices"&gt;Best Practices&lt;/h2&gt;

&lt;p&gt;As with most distributed systems techniques, simplicity is your friend. If you’re considering introducing distributed locks into your application, the following guidelines will help you avoid many common problems.&lt;/p&gt;

&lt;h4 id="keep-critical-sections-small"&gt;Keep Critical Sections Small&lt;/h4&gt;

&lt;p&gt;Acquire the lock immediately before modifying shared resources, and release it immediately afterward. The less work performed while holding the lock, the better your system scales.&lt;/p&gt;





&lt;h4 id="always-use-lock-expiration"&gt;Always Use Lock Expiration&lt;/h4&gt;

&lt;p&gt;Servers crash, containers restart, and networks fail. Never assume your application will always release its lock correctly. Expiration protects the rest of the system from waiting forever.&lt;/p&gt;





&lt;h4 id="verify-lock-ownership"&gt;Verify Lock Ownership&lt;/h4&gt;

&lt;p&gt;Before releasing a lock, ensure your application still owns it. Ownership checks prevent one server from accidentally deleting another server’s lock after an expiration or retry.&lt;/p&gt;





&lt;h4 id="design-for-failure"&gt;Design for Failure&lt;/h4&gt;

&lt;p&gt;Distributed systems should always assume that something will eventually fail. Ask yourself:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;What happens if Redis becomes unavailable?&lt;/li&gt;
  &lt;li&gt;What happens if the server crashes?&lt;/li&gt;
  &lt;li&gt;What happens if the network partitions?&lt;/li&gt;
  &lt;li&gt;What happens if the lock expires unexpectedly?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thinking through failure scenarios early often prevents painful production incidents later.&lt;/p&gt;





&lt;h4 id="combine-reliability-patterns"&gt;Combine Reliability Patterns&lt;/h4&gt;

&lt;p&gt;Distributed locks are rarely used in isolation. Production systems often combine multiple reliability techniques. For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; prevents duplicate requests.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt; guarantee atomic database updates.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Isolation Levels&lt;/strong&gt; provide predictable views of data.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Distributed Locks&lt;/strong&gt; coordinate multiple application servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each technique solves a different problem. Together, they create systems that continue behaving correctly even under heavy load and unexpected failures.&lt;/p&gt;





&lt;h2 id="bringing-it-all-together"&gt;Bringing It All Together&lt;/h2&gt;

&lt;p&gt;At this point in the series, we’ve explored several concepts that all contribute to building reliable backend systems. Each one addresses a different question.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Concept&lt;/th&gt;
      &lt;th&gt;Question It Answers&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;What if the same request is sent twice?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Race Conditions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;What if multiple requests modify the same data simultaneously?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Database Transactions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;What if part of my operation fails?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Isolation Levels&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;What should transactions be allowed to see while others are running?&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Distributed Locks&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;How do multiple servers agree who should perform a task?&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Notice how these concepts complement one another. None replaces the others; reliable systems are built by combining the right tools for the right problems.&lt;/p&gt;





&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;As applications grow, the biggest challenges often aren’t writing business logic. They’re coordinating work across multiple users, multiple requests, multiple transactions, and eventually multiple servers.&lt;/p&gt;

&lt;p&gt;Distributed locks exist because modern applications are no longer confined to a single machine. They’re deployed across clusters, containers, cloud regions, and worker nodes that all need to cooperate without constantly stepping on each other’s toes.&lt;/p&gt;

&lt;p&gt;Like transactions and isolation levels, distributed locks aren’t something you’ll use for every feature. But when you do need them, they can be the difference between a system that behaves predictably and one that quietly creates duplicate invoices, repeated payments, or inconsistent business data.&lt;/p&gt;

&lt;p&gt;The next time you design a background job, scheduled task, or critical workflow, ask yourself one simple question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“What happens if two servers try to do this at exactly the same time?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is “something bad,” you’ve probably found a place where a distributed lock belongs.&lt;/p&gt;





&lt;h2 id="whats-next"&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;We’ve now covered:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Contract Testing&lt;/li&gt;
  &lt;li&gt;Idempotency&lt;/li&gt;
  &lt;li&gt;Race Conditions&lt;/li&gt;
  &lt;li&gt;Database Transactions&lt;/li&gt;
  &lt;li&gt;Database Concurrency&lt;/li&gt;
  &lt;li&gt;Database Isolation Levels&lt;/li&gt;
  &lt;li&gt;Distributed Locks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, every concept has focused on keeping operations consistent &lt;strong&gt;while they’re happening&lt;/strong&gt;. But there’s another challenge waiting. Imagine you’ve successfully updated your database inside a transaction and now need to publish an event to Kafka, RabbitMQ, or another message broker. What happens if the database commit succeeds, but publishing the event fails? Or worse, what if the event is published but the transaction rolls back?&lt;/p&gt;

&lt;p&gt;This problem has caused countless production incidents in distributed systems. In the next article, we’ll explore &lt;strong&gt;The Outbox Pattern Explained: Publishing Events Without Losing Data&lt;/strong&gt;, one of the most widely used patterns for ensuring your database and message broker stay in sync.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Distributed Locks Explained: Coordinating Work Across Multiple Servers</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/distributed-locks-explained-coordinating-work-across-multiple-servers-260p</link>
      <guid>https://dev.to/billy_de_cartel/distributed-locks-explained-coordinating-work-across-multiple-servers-260p</guid>
      <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Database locks protect rows. Distributed locks protect systems.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine your application has been wildly successful. What started as a simple web application running on a single server now runs across multiple machines behind a load balancer. Requests are shared between application instances, background workers process jobs independently, and scheduled tasks run on every server.&lt;/p&gt;

&lt;p&gt;From the outside, everything looks better than ever: pages load faster, traffic scales effortlessly, and users are happy. Then one morning, your finance department calls. Every customer has received &lt;strong&gt;four monthly invoices&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing appears wrong with the code. The invoice generation job is scheduled to run once every month, so why did it execute four times? The answer is surprisingly simple: you now have four application servers. At midnight, every server woke up, checked the scheduler, and independently decided that it was responsible for generating invoices. From each server’s perspective, everything was perfectly correct; collectively, however, they created a costly mistake.&lt;/p&gt;

&lt;p&gt;Now imagine a different scenario. Your payment gateway sends a webhook confirming a successful payment. The webhook is delivered to one of your load-balanced servers, but a retry occurs because the payment provider doesn’t receive a response quickly enough, so another server processes the same webhook. Both servers begin updating balances, both create accounting entries, and both generate receipts.&lt;/p&gt;

&lt;p&gt;You’ve already learned how &lt;strong&gt;idempotency&lt;/strong&gt; protects against duplicate requests and how &lt;strong&gt;transactions&lt;/strong&gt; ensure database operations succeed together.&lt;/p&gt;

&lt;p&gt;But neither of those concepts answers a new question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;How do multiple application servers agree that only one of them should perform a particular piece of work?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That problem is solved by &lt;strong&gt;distributed locks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As applications grow beyond a single server, distributed locks become one of the most important coordination mechanisms in modern software engineering.&lt;/p&gt;





&lt;h2 id="why-database-locks-are-no-longer-enough"&gt;Why Database Locks Are No Longer Enough&lt;/h2&gt;

&lt;p&gt;Earlier in this series, we explored database transactions and row-level locking.&lt;/p&gt;

&lt;p&gt;Suppose two transactions attempt to update the same bank account.&lt;/p&gt;

&lt;p&gt;Using a statement such as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT *
FROM accounts
WHERE id = 1
FOR UPDATE;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;the database ensures only one transaction can modify that row at a time.&lt;/p&gt;

&lt;p&gt;This works beautifully because both transactions are coordinating through the same database.&lt;/p&gt;

&lt;p&gt;Now consider a different problem.&lt;/p&gt;

&lt;p&gt;Suppose you have four application servers running the exact same code, and each server has a scheduler responsible for calculating monthly loan interest. Midnight arrives, and every server starts the same scheduled job. None of them are updating the same database row immediately. Instead, they’re deciding whether to begin an entire business process. The database has nothing to lock yet, and by the time one server begins writing data, the others have already started processing. The result is duplicated work.&lt;/p&gt;

&lt;p&gt;Database locks are excellent at protecting individual records, but they are not designed to coordinate entire applications spread across multiple machines. This is the gap distributed locks were created to fill.&lt;/p&gt;





&lt;h2 id="what-is-a-distributed-lock"&gt;What Is a Distributed Lock?&lt;/h2&gt;

&lt;p&gt;A distributed lock is a coordination mechanism that allows multiple independent servers to agree that &lt;strong&gt;only one of them&lt;/strong&gt; may perform a particular operation at a given time. Instead of protecting a single database row, a distributed lock protects an entire business activity.&lt;/p&gt;

&lt;p&gt;Imagine a conference room with a single key. Anyone can use the room, but only the person holding the key may enter, and everyone else must wait until the key is returned. A distributed lock works in much the same way: before performing an operation, a server first attempts to acquire the lock. If the lock is available, the server proceeds; if another server already owns the lock, the operation waits, retries, or exits.&lt;/p&gt;

&lt;p&gt;The important point is that &lt;strong&gt;every server asks the same central authority for permission before beginning work.&lt;/strong&gt; That authority might be Redis, ZooKeeper, etcd, or another distributed coordination system.&lt;/p&gt;





&lt;h2 id="a-simple-example"&gt;A Simple Example&lt;/h2&gt;

&lt;p&gt;Imagine four application servers.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        Load Balancer
              │
   ┌──────────┼──────────┐
   │          │          │
Server A   Server B   Server C
   │          │          │
        Server D
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At midnight, each server checks whether it’s time to generate invoices.&lt;/p&gt;

&lt;p&gt;Without a distributed lock:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Server A → Generate invoices ✅

Server B → Generate invoices ✅

Server C → Generate invoices ✅

Server D → Generate invoices ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Four executions, four invoices, and one very unhappy finance department.&lt;/p&gt;

&lt;p&gt;With a distributed lock:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Server A → Acquire Lock ✅

Server B → Lock Exists

Server C → Lock Exists

Server D → Lock Exists
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Only Server A proceeds, and everyone else exits. The invoices are generated exactly once.&lt;/p&gt;





&lt;h2 id="real-world-examples"&gt;Real-World Examples&lt;/h2&gt;

&lt;p&gt;Distributed locks appear in far more places than most developers realize. Whenever multiple application instances could accidentally perform the same work, a distributed lock becomes a potential solution.&lt;/p&gt;

&lt;h3 id="scheduled-jobs"&gt;Scheduled Jobs&lt;/h3&gt;

&lt;p&gt;Suppose your loan management platform calculates accrued interest every night at midnight. Without coordination, every application server performs the calculation independently, interest may be applied multiple times, and a distributed lock ensures exactly one server performs the calculation.&lt;/p&gt;





&lt;h3 id="payment-processing"&gt;Payment Processing&lt;/h3&gt;

&lt;p&gt;Imagine receiving a payment webhook from M-Pesa. Network retries cause multiple servers to receive the same notification, and without coordination, several servers may attempt to update balances simultaneously. A distributed lock allows only one server to process the payment while the others simply exit.&lt;/p&gt;





&lt;h3 id="inventory-management"&gt;Inventory Management&lt;/h3&gt;

&lt;p&gt;Only one laptop remains in stock, and two servers receive purchase requests simultaneously. Each attempts to reserve the item. Although transactions protect database consistency, a distributed lock can coordinate reservation workflows across multiple application instances before they even begin modifying inventory.&lt;/p&gt;





&lt;h3 id="sending-emails"&gt;Sending Emails&lt;/h3&gt;

&lt;p&gt;Marketing decides to send a promotional email to one million subscribers, and your email scheduler is deployed across five worker nodes. Without coordination, every worker starts sending the campaign and customers receive the same email five times. With a distributed lock, only one worker initiates the campaign while the others remain idle.&lt;/p&gt;





&lt;h3 id="report-generation"&gt;Report Generation&lt;/h3&gt;

&lt;p&gt;Generating annual financial reports may take several minutes. Without coordination, multiple servers might begin generating the exact same report simultaneously, wasting CPU time and increasing database load. A distributed lock ensures only one report generation process is active.&lt;/p&gt;





&lt;h2 id="when-should-you-consider-a-distributed-lock"&gt;When Should You Consider a Distributed Lock?&lt;/h2&gt;

&lt;p&gt;A useful rule of thumb is to ask yourself a simple question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;What would happen if two servers performed this operation at exactly the same time?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Duplicate invoices&lt;/li&gt;
  &lt;li&gt;Duplicate payments&lt;/li&gt;
  &lt;li&gt;Duplicate notifications&lt;/li&gt;
  &lt;li&gt;Duplicate accounting entries&lt;/li&gt;
  &lt;li&gt;Duplicate interest calculations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then the operation is probably a candidate for a distributed lock.&lt;/p&gt;

&lt;p&gt;Not every feature needs one. Most HTTP requests don’t, reading data doesn’t, and serving web pages doesn’t. Distributed locks are primarily useful for &lt;strong&gt;shared background work&lt;/strong&gt; and &lt;strong&gt;critical business processes&lt;/strong&gt; where duplicate execution would produce incorrect results.&lt;/p&gt;





&lt;h2 id="distributed-locks-are-about-coordination"&gt;Distributed Locks Are About Coordination&lt;/h2&gt;

&lt;p&gt;One misconception worth addressing early is that distributed locks replace database transactions.&lt;/p&gt;

&lt;p&gt;They don’t. Transactions guarantee consistency &lt;strong&gt;inside the database&lt;/strong&gt;; distributed locks coordinate &lt;strong&gt;between application servers&lt;/strong&gt;. The two solve different problems.&lt;/p&gt;

&lt;p&gt;In practice, many enterprise systems use both together. A server first acquires a distributed lock, then begins a database transaction. When the transaction completes successfully, the server releases the distributed lock. The lock ensures only one server performs the work, and the transaction ensures the database remains consistent while that work is being performed. Together, they provide a powerful foundation for building reliable distributed systems.&lt;/p&gt;





&lt;p&gt;At this point, we’ve answered &lt;strong&gt;why distributed locks exist&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The next question is equally important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do multiple servers actually agree on who owns the lock?&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id="how-distributed-locks-work"&gt;How Distributed Locks Work&lt;/h2&gt;

&lt;p&gt;At a high level, every distributed lock follows the same basic workflow.&lt;/p&gt;

&lt;p&gt;Before performing a critical operation, an application asks a shared coordination service for permission to proceed. If the lock is available, the application acquires it and begins its work; if another server already owns the lock, the application waits, retries later, or simply exits. Once the work has been completed, the lock is released, allowing another server to acquire it.&lt;/p&gt;

&lt;p&gt;Although different technologies implement this process differently, the underlying idea remains remarkably simple.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Application Server

        │

Request Lock

        │

───────────────
 Lock Service
───────────────

Lock Available?

   │          │

  Yes         No

   │          │

Acquire      Wait / Retry / Exit

   │

Perform Work

   │

Release Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The important detail is that &lt;strong&gt;every application instance talks to the same lock service&lt;/strong&gt;. Without a shared source of truth, each server would simply believe it owned the lock.&lt;/p&gt;





&lt;h2 id="acquiring-a-lock"&gt;Acquiring a Lock&lt;/h2&gt;

&lt;p&gt;Imagine four servers attempting to generate monthly invoices.&lt;/p&gt;

&lt;p&gt;Each server sends a request to Redis asking for a lock called:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;invoice-generation
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Redis receives the requests almost simultaneously. The first request succeeds, and Redis stores something similar to:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;invoice-generation

Owner: Server A

Expires: 30 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When the remaining servers ask for the same lock, Redis responds that the lock already exists. Only Server A continues. Servers B, C, and D either wait, retry after a short delay, or abandon the operation altogether.&lt;/p&gt;

&lt;p&gt;The beauty of distributed locks lies in their simplicity: instead of every server making its own decision, they all trust a single coordinator.&lt;/p&gt;





&lt;h2 id="holding-the-lock"&gt;Holding the Lock&lt;/h2&gt;

&lt;p&gt;Once a server has successfully acquired the lock, it proceeds with the protected operation.&lt;/p&gt;

&lt;p&gt;This might involve:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Generating invoices.&lt;/li&gt;
  &lt;li&gt;Processing a payment.&lt;/li&gt;
  &lt;li&gt;Calculating loan interest.&lt;/li&gt;
  &lt;li&gt;Sending reminder emails.&lt;/li&gt;
  &lt;li&gt;Synchronizing inventory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During this period, every other server attempting the same operation sees that the lock is already owned. Rather than performing duplicate work, those servers simply back off. The lock effectively becomes a reservation saying that someone is already doing this work and others should wait.&lt;/p&gt;





&lt;h2 id="releasing-the-lock"&gt;Releasing the Lock&lt;/h2&gt;

&lt;p&gt;When the protected work completes successfully, the server releases the lock.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Acquire Lock

↓

Process Work

↓

Release Lock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once Redis removes the lock, another server is free to acquire it if necessary. Releasing the lock is just as important as acquiring it. A lock that is never released eventually blocks every future attempt to perform that operation.&lt;/p&gt;





&lt;h2 id="the-problem-with-permanent-locks"&gt;The Problem with Permanent Locks&lt;/h2&gt;

&lt;p&gt;Now consider something less pleasant.&lt;/p&gt;

&lt;p&gt;Server A acquires the invoice-generation lock, but halfway through generating invoices, the server crashes. Perhaps the machine loses power, perhaps Kubernetes terminates the container, or perhaps someone accidentally restarts the application. The important point is that Server A never gets the opportunity to release its lock.&lt;/p&gt;

&lt;p&gt;If the lock remained permanent, every future invoice generation attempt would fail because the system would forever believe Server A still owned the lock. This is one of the biggest differences between traditional application locks and distributed locks: distributed systems must always assume that servers can disappear without warning.&lt;/p&gt;





&lt;h2 id="lock-expiration-ttl"&gt;Lock Expiration (TTL)&lt;/h2&gt;

&lt;p&gt;To solve this problem, distributed locks almost always include an expiration time, often called a &lt;strong&gt;Time-To-Live (TTL).&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of storing only the lock name, the lock service stores something like:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Lock:

invoice-generation

Owner:

Server A

Expires:

30 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If Server A completes successfully, it releases the lock before those thirty seconds expire. If Server A crashes, Redis automatically deletes the lock after the TTL expires, allowing another server to continue the work instead of waiting forever.&lt;/p&gt;

&lt;p&gt;Think of it like borrowing a meeting room: rather than reserving it indefinitely, your booking automatically expires after one hour. If you forget to leave, the reservation eventually disappears and someone else can use the room. TTL prevents abandoned locks from permanently blocking the system.&lt;/p&gt;





&lt;h2 id="choosing-the-right-ttl"&gt;Choosing the Right TTL&lt;/h2&gt;

&lt;p&gt;Choosing a lock duration isn’t as straightforward as it might seem.&lt;/p&gt;

&lt;p&gt;Suppose generating invoices normally takes ten seconds, and a thirty-second TTL provides plenty of room for occasional delays. But what if one month the process unexpectedly takes forty-five seconds? The lock expires after thirty seconds, another server acquires it, and now both servers are generating invoices simultaneously. You’ve accidentally recreated the very problem the lock was supposed to prevent.&lt;/p&gt;

&lt;p&gt;On the other hand, choosing an extremely long TTL isn’t ideal either. If a server crashes while holding a lock that expires after thirty minutes, every other server must wait half an hour before continuing. Finding the right TTL therefore requires understanding how long your operation normally takes, while leaving enough room for occasional delays.&lt;/p&gt;

&lt;p&gt;Some distributed lock implementations even allow servers to periodically renew the TTL while they’re still actively working. This approach, often called a &lt;strong&gt;heartbeat&lt;/strong&gt;, keeps long-running operations alive without requiring excessively long expiration times.&lt;/p&gt;





&lt;h2 id="what-happens-if-two-servers-ask-at-the-same-time"&gt;What Happens If Two Servers Ask at the Same Time?&lt;/h2&gt;

&lt;p&gt;One question naturally arises: what happens if two servers request the lock at exactly the same millisecond? The answer depends on the lock service.&lt;/p&gt;

&lt;p&gt;Redis, ZooKeeper, etcd, and similar systems perform lock acquisition atomically. That means checking whether the lock exists and creating it happen as a single indivisible operation. There is never a moment when both servers successfully acquire the same lock: one request succeeds and the other fails. This atomicity is exactly what makes distributed locks reliable; without it, two servers could both believe they owned the lock, defeating the entire purpose.&lt;/p&gt;





&lt;h2 id="lock-ownership-matters"&gt;Lock Ownership Matters&lt;/h2&gt;

&lt;p&gt;Imagine Server A acquires a lock. Before finishing its work, the lock expires because the TTL was too short, and Server B now acquires the same lock. Moments later, Server A finally finishes and attempts to release it. If the lock service simply deleted the lock without checking ownership, Server A would accidentally remove Server B’s lock, Server C could now acquire it, and suddenly two servers are working simultaneously again.&lt;/p&gt;

&lt;p&gt;To prevent this, distributed lock implementations associate every lock with a unique owner identifier. When releasing a lock, the application must prove that it is still the owner; if ownership has already changed, the release request is ignored. This simple verification prevents one server from accidentally deleting another server’s lock.&lt;/p&gt;





&lt;h2 id="distributed-locks-arent-magic"&gt;Distributed Locks Aren’t Magic&lt;/h2&gt;

&lt;p&gt;It’s important to understand that distributed locks don’t eliminate failures. Servers can still crash, networks can still become partitioned, and Redis instances can still fail. Distributed locks simply provide a coordinated way for multiple application instances to make decisions despite those realities. They reduce duplicate work, improve consistency, and coordinate critical business operations, but like every distributed systems technique, they must be implemented carefully and combined with other reliability mechanisms such as transactions, retries, idempotency, and monitoring.&lt;/p&gt;

&lt;p&gt;In the next section, we’ll explore the most common technologies used to implement distributed locks, including &lt;strong&gt;Redis&lt;/strong&gt;, &lt;strong&gt;Redlock&lt;/strong&gt;, &lt;strong&gt;ZooKeeper&lt;/strong&gt;, &lt;strong&gt;etcd&lt;/strong&gt;, and &lt;strong&gt;Consul&lt;/strong&gt;, along with the strengths and weaknesses of each approach.&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Database Isolation Levels Explained: Choosing the Right Consistency Guarantees</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 10 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/database-isolation-levels-explained-choosing-the-right-consistency-guarantees-1l1m</link>
      <guid>https://dev.to/billy_de_cartel/database-isolation-levels-explained-choosing-the-right-consistency-guarantees-1l1m</guid>
      <description>&lt;h1 id="understanding-the-four-sql-isolation-levels"&gt;Understanding the Four SQL Isolation Levels&lt;/h1&gt;

&lt;p&gt;Now that we’ve seen the kinds of problems concurrent transactions can create, the next question is obvious:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does a database prevent them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The answer lies in isolation levels.&lt;/p&gt;

&lt;p&gt;Rather than enforcing a single set of rules for every application, relational databases allow developers to choose how isolated transactions should be from one another.&lt;/p&gt;

&lt;p&gt;This flexibility exists because different applications have different priorities.&lt;/p&gt;

&lt;p&gt;A banking application transferring millions of shillings every day values consistency above almost everything else.&lt;/p&gt;

&lt;p&gt;A reporting dashboard displaying website traffic may prefer speed over perfect accuracy.&lt;/p&gt;

&lt;p&gt;Isolation levels allow the database to balance these competing requirements.&lt;/p&gt;

&lt;p&gt;As isolation becomes stronger, transactions observe more consistent data, but the database also has to coordinate more aggressively, often reducing concurrency.&lt;/p&gt;

&lt;p&gt;As isolation becomes weaker, transactions execute more freely, improving performance but increasing the likelihood of observing changing data.&lt;/p&gt;

&lt;p&gt;The SQL standard defines four isolation levels.&lt;/p&gt;

&lt;p&gt;Each one builds upon the guarantees of the previous level.&lt;/p&gt;





&lt;h2 id="read-uncommitted"&gt;Read Uncommitted&lt;/h2&gt;

&lt;p&gt;Read Uncommitted is the weakest isolation level defined by the SQL standard.&lt;/p&gt;

&lt;p&gt;At this level, transactions are allowed to read changes made by other transactions even if those changes haven’t yet been committed.&lt;/p&gt;

&lt;p&gt;Returning to our banking example, imagine Alice begins transferring &lt;strong&gt;KES 20,000&lt;/strong&gt; to another account.&lt;/p&gt;

&lt;p&gt;The database deducts the money from her balance but hasn’t yet committed the transaction.&lt;/p&gt;

&lt;p&gt;Another transaction immediately reads Alice’s account.&lt;/p&gt;

&lt;p&gt;Instead of seeing &lt;strong&gt;KES 50,000&lt;/strong&gt;, it now sees &lt;strong&gt;KES 30,000&lt;/strong&gt;, even though the transfer could still fail and be rolled back.&lt;/p&gt;

&lt;p&gt;That second transaction has just performed a dirty read.&lt;/p&gt;

&lt;p&gt;The advantage of Read Uncommitted is that transactions almost never wait for one another.&lt;/p&gt;

&lt;p&gt;Because the database performs very little coordination, throughput can be extremely high.&lt;/p&gt;

&lt;p&gt;The downside is that applications may make decisions using data that never officially existed.&lt;/p&gt;

&lt;p&gt;For most business applications, this is unacceptable.&lt;/p&gt;

&lt;p&gt;Imagine calculating payroll using salaries that are eventually rolled back or approving a loan based on a balance that disappears moments later.&lt;/p&gt;

&lt;p&gt;Fortunately, very few modern relational databases actually encourage Read Uncommitted.&lt;/p&gt;

&lt;p&gt;Many databases either discourage it entirely or internally behave more conservatively even when it’s requested.&lt;/p&gt;

&lt;p&gt;In practice, you’ll rarely choose this isolation level for production systems.&lt;/p&gt;





&lt;h2 id="read-committed"&gt;Read Committed&lt;/h2&gt;

&lt;p&gt;Read Committed is the default isolation level in databases such as PostgreSQL, Oracle, and SQL Server.&lt;/p&gt;

&lt;p&gt;Instead of allowing transactions to read uncommitted changes, the database only exposes data that has already been committed.&lt;/p&gt;

&lt;p&gt;This immediately eliminates dirty reads.&lt;/p&gt;

&lt;p&gt;Returning to Alice’s transfer, suppose another transaction checks her balance while the transfer is still running.&lt;/p&gt;

&lt;p&gt;Instead of seeing the temporary balance of &lt;strong&gt;KES 30,000&lt;/strong&gt;, it continues seeing the previously committed balance of &lt;strong&gt;KES 50,000&lt;/strong&gt; until the transfer completes.&lt;/p&gt;

&lt;p&gt;Only after the transaction commits does the new balance become visible.&lt;/p&gt;

&lt;p&gt;This makes Read Committed an excellent general-purpose isolation level.&lt;/p&gt;

&lt;p&gt;Applications never observe incomplete work, while the database still allows a high degree of concurrency.&lt;/p&gt;

&lt;p&gt;However, Read Committed doesn’t solve every problem.&lt;/p&gt;

&lt;p&gt;Suppose your transaction reads Alice’s balance at the beginning of a report.&lt;/p&gt;

&lt;p&gt;A few seconds later, another transaction deposits &lt;strong&gt;KES 100,000&lt;/strong&gt; into the account and commits.&lt;/p&gt;

&lt;p&gt;If your report queries the balance again before finishing, you’ll now see a different value.&lt;/p&gt;

&lt;p&gt;The same row has changed during your transaction.&lt;/p&gt;

&lt;p&gt;Read Committed prevents dirty reads, but it still allows non-repeatable reads and phantom reads.&lt;/p&gt;

&lt;p&gt;For many applications, that’s a perfectly acceptable trade-off.&lt;/p&gt;





&lt;h2 id="read-committed-timeline"&gt;Read Committed Timeline&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;Transaction A                     Transaction B

BEGIN

Read Balance = 50,000

                               BEGIN

                               Deposit 100,000

                               COMMIT

Read Balance = 150,000

COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Both values are valid.&lt;/p&gt;

&lt;p&gt;The important difference is that Transaction A never sees incomplete or rolled-back data.&lt;/p&gt;

&lt;p&gt;It only observes committed changes.&lt;/p&gt;





&lt;h2 id="repeatable-read"&gt;Repeatable Read&lt;/h2&gt;

&lt;p&gt;Suppose you’re generating an end-of-day financial report.&lt;/p&gt;

&lt;p&gt;Your transaction calculates the total account balance across thousands of customers.&lt;/p&gt;

&lt;p&gt;Halfway through generating the report, another transaction updates several account balances.&lt;/p&gt;

&lt;p&gt;If your report re-reads those accounts later, the totals may no longer match the values used earlier in the report.&lt;/p&gt;

&lt;p&gt;This is exactly the situation Repeatable Read was designed to solve.&lt;/p&gt;

&lt;p&gt;At this isolation level, once a transaction reads a row, subsequent reads of that same row always return the same version for the lifetime of the transaction.&lt;/p&gt;

&lt;p&gt;Even if another transaction updates the row and commits, your transaction continues working with the original version.&lt;/p&gt;

&lt;p&gt;It’s as though your transaction receives its own private snapshot of the database.&lt;/p&gt;

&lt;p&gt;This provides a much more consistent view of the data, making it particularly useful for reporting systems and financial calculations.&lt;/p&gt;

&lt;p&gt;However, Repeatable Read doesn’t necessarily prevent new rows from appearing that satisfy your query conditions.&lt;/p&gt;

&lt;p&gt;Depending on the database implementation, phantom reads may still occur, although databases like PostgreSQL use &lt;strong&gt;Multi-Version Concurrency Control (MVCC)&lt;/strong&gt; to eliminate many of these anomalies without locking every row.&lt;/p&gt;

&lt;p&gt;This is one of the reasons database behavior differs slightly across vendors.&lt;/p&gt;

&lt;p&gt;We’ll return to that shortly.&lt;/p&gt;





&lt;h2 id="repeatable-read-timeline"&gt;Repeatable Read Timeline&lt;/h2&gt;

&lt;pre&gt;&lt;code&gt;Transaction A                     Transaction B

BEGIN

Read Balance = 50,000

                               BEGIN

                               Deposit 100,000

                               COMMIT

Read Balance = 50,000 ✅

COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Although the database now contains &lt;strong&gt;KES 150,000&lt;/strong&gt;, Transaction A continues seeing &lt;strong&gt;KES 50,000&lt;/strong&gt; because it is working from a consistent snapshot.&lt;/p&gt;





&lt;h2 id="serializable"&gt;Serializable&lt;/h2&gt;

&lt;p&gt;Serializable is the strongest isolation level defined by the SQL standard.&lt;/p&gt;

&lt;p&gt;The easiest way to understand it is to imagine that every transaction runs one after another instead of simultaneously.&lt;/p&gt;

&lt;p&gt;Internally, the database may still execute many transactions concurrently, but it guarantees that the final result is identical to some serial execution order.&lt;/p&gt;

&lt;p&gt;Returning to our concert ticket example, suppose only one seat remains.&lt;/p&gt;

&lt;p&gt;Customer A begins purchasing the ticket.&lt;/p&gt;

&lt;p&gt;Customer B attempts to purchase the same seat at exactly the same time.&lt;/p&gt;

&lt;p&gt;Under Serializable isolation, the database ensures that only one transaction succeeds.&lt;/p&gt;

&lt;p&gt;The other transaction must either wait, retry, or fail.&lt;/p&gt;

&lt;p&gt;The database refuses to produce a result that couldn’t happen if the transactions had executed one after another.&lt;/p&gt;

&lt;p&gt;This provides the strongest possible consistency guarantees.&lt;/p&gt;

&lt;p&gt;It also comes at the highest performance cost.&lt;/p&gt;

&lt;p&gt;Serializable transactions often require additional locking, conflict detection, or transaction retries.&lt;/p&gt;

&lt;p&gt;For systems processing large volumes of concurrent requests, this can reduce throughput significantly.&lt;/p&gt;

&lt;p&gt;Because of this, Serializable is typically reserved for situations where correctness is absolutely critical.&lt;/p&gt;

&lt;p&gt;Financial ledgers, securities trading systems, and certain accounting operations are common examples.&lt;/p&gt;





&lt;h2 id="isolation-levels-at-a-glance"&gt;Isolation Levels at a Glance&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;Isolation Level&lt;/th&gt;
      &lt;th&gt;Dirty Reads&lt;/th&gt;
      &lt;th&gt;Non-Repeatable Reads&lt;/th&gt;
      &lt;th&gt;Phantom Reads&lt;/th&gt;
      &lt;th&gt;Performance&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Read Uncommitted&lt;/td&gt;
      &lt;td&gt;❌ Possible&lt;/td&gt;
      &lt;td&gt;❌ Possible&lt;/td&gt;
      &lt;td&gt;❌ Possible&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Read Committed&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;❌ Possible&lt;/td&gt;
      &lt;td&gt;❌ Possible&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Repeatable Read&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;⚠ Depends on database&lt;/td&gt;
      &lt;td&gt;⭐⭐⭐&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Serializable&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;✅ Prevented&lt;/td&gt;
      &lt;td&gt;⭐⭐&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table makes an important pattern clear.&lt;/p&gt;

&lt;p&gt;As isolation increases, the number of concurrency anomalies decreases.&lt;/p&gt;

&lt;p&gt;At the same time, the amount of coordination required by the database increases.&lt;/p&gt;

&lt;p&gt;This is why there is no universally “best” isolation level.&lt;/p&gt;

&lt;p&gt;The appropriate choice always depends on the requirements of your application.&lt;/p&gt;

&lt;h2 id="database-isolation-levels-in-popular-databases"&gt;Database Isolation Levels in Popular Databases&lt;/h2&gt;

&lt;p&gt;One detail that often surprises developers is that not every relational database implements isolation levels in exactly the same way.&lt;/p&gt;

&lt;p&gt;The SQL standard defines the four isolation levels, but database vendors have some flexibility in how they achieve those guarantees.&lt;/p&gt;

&lt;p&gt;For example, PostgreSQL relies heavily on &lt;strong&gt;Multi-Version Concurrency Control (MVCC)&lt;/strong&gt;. Instead of locking rows aggressively, PostgreSQL keeps multiple versions of a row and allows transactions to read a consistent snapshot of the data. This approach provides excellent concurrency while maintaining strong consistency.&lt;/p&gt;

&lt;p&gt;MySQL’s InnoDB storage engine also supports MVCC but implements certain isolation behaviors differently. In particular, its default &lt;strong&gt;Repeatable Read&lt;/strong&gt; isolation level prevents many phantom reads by using a combination of snapshot reads and gap locks.&lt;/p&gt;

&lt;p&gt;SQL Server, on the other hand, traditionally relies more heavily on locking, although it also offers snapshot-based isolation levels that can be enabled when appropriate.&lt;/p&gt;

&lt;p&gt;As a developer, you don’t need to memorize every implementation detail.&lt;/p&gt;

&lt;p&gt;The important lesson is this:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Always understand how your specific database implements isolation before assuming its behavior.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The SQL standard provides the vocabulary, but your database documentation explains the exact behavior.&lt;/p&gt;





&lt;h2 id="choosing-the-right-isolation-level"&gt;Choosing the Right Isolation Level&lt;/h2&gt;

&lt;p&gt;After learning about all four isolation levels, it’s natural to ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“Which one should I actually use?”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The honest answer is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It depends on what you’re building.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Suppose you’re developing a dashboard that displays the number of users currently online.&lt;/p&gt;

&lt;p&gt;If the number changes while someone refreshes the page, that’s perfectly acceptable.&lt;/p&gt;

&lt;p&gt;There’s little value in sacrificing performance just to ensure every count remains identical throughout a transaction.&lt;/p&gt;

&lt;p&gt;Read Committed is usually more than sufficient.&lt;/p&gt;

&lt;p&gt;Now consider a payroll system.&lt;/p&gt;

&lt;p&gt;Calculating employee salaries requires reading thousands of records while ensuring the figures don’t change halfway through the calculation.&lt;/p&gt;

&lt;p&gt;If one employee’s salary is updated while payroll is being processed, the final report could contain inconsistent totals.&lt;/p&gt;

&lt;p&gt;Repeatable Read becomes a much better fit because it provides a stable snapshot throughout the transaction.&lt;/p&gt;

&lt;p&gt;Finally, imagine a securities trading platform or a banking ledger where even a single inconsistency could have significant financial consequences.&lt;/p&gt;

&lt;p&gt;Here, correctness is more important than throughput.&lt;/p&gt;

&lt;p&gt;Serializable isolation is often the safest choice, even if it means transactions occasionally wait or retry.&lt;/p&gt;

&lt;p&gt;The goal isn’t to choose the strongest isolation level.&lt;/p&gt;

&lt;p&gt;The goal is to choose the weakest isolation level that still guarantees the correctness your application requires.&lt;/p&gt;

&lt;p&gt;Doing so allows the database to maximize concurrency without sacrificing business integrity.&lt;/p&gt;





&lt;h2 id="isolation-levels-and-performance"&gt;Isolation Levels and Performance&lt;/h2&gt;

&lt;p&gt;One mistake developers sometimes make is assuming higher isolation is always better.&lt;/p&gt;

&lt;p&gt;In reality, every additional guarantee comes at a cost.&lt;/p&gt;

&lt;p&gt;Higher isolation levels typically require the database to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Coordinate more transactions.&lt;/li&gt;
  &lt;li&gt;Acquire additional locks or maintain more snapshots.&lt;/li&gt;
  &lt;li&gt;Detect conflicts more aggressively.&lt;/li&gt;
  &lt;li&gt;Delay or retry conflicting transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As concurrency increases, these costs become more noticeable.&lt;/p&gt;

&lt;p&gt;A high-traffic e-commerce platform processing thousands of orders every minute cannot afford unnecessary waiting if a lower isolation level already satisfies its business rules.&lt;/p&gt;

&lt;p&gt;Likewise, a financial institution cannot sacrifice correctness simply to process a few extra transactions per second.&lt;/p&gt;

&lt;p&gt;Finding the right balance is part of designing reliable software.&lt;/p&gt;





&lt;h2 id="isolation-levels-vs-transactions-vs-race-conditions"&gt;Isolation Levels vs Transactions vs Race Conditions&lt;/h2&gt;

&lt;p&gt;At this point in the series, we’ve covered three concepts that are closely related but often confused.&lt;/p&gt;

&lt;p&gt;Let’s put them side by side.&lt;/p&gt;

&lt;h3 id="transactions"&gt;Transactions&lt;/h3&gt;

&lt;p&gt;Transactions answer the question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;What happens if my operation fails halfway through?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;They ensure a group of related database operations either all succeed together or all fail together.&lt;/p&gt;

&lt;p&gt;Without transactions, partial updates can leave your data inconsistent.&lt;/p&gt;





&lt;h3 id="race-conditions"&gt;Race Conditions&lt;/h3&gt;

&lt;p&gt;Race conditions answer a different question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;What happens if two requests modify the same data at the same time?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These problems arise because multiple users or systems interact with shared data concurrently.&lt;/p&gt;

&lt;p&gt;The outcome often depends entirely on timing.&lt;/p&gt;

&lt;p&gt;Transactions alone don’t eliminate race conditions.&lt;/p&gt;

&lt;p&gt;Additional mechanisms such as locking, optimistic concurrency, or stronger isolation levels are often required.&lt;/p&gt;





&lt;h3 id="isolation-levels"&gt;Isolation Levels&lt;/h3&gt;

&lt;p&gt;Isolation levels answer yet another question:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;While another transaction is running, what am I allowed to see?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Should your transaction observe unfinished work?&lt;/p&gt;

&lt;p&gt;Should it continue seeing the same data even after another transaction commits?&lt;/p&gt;

&lt;p&gt;Should it behave as though it’s the only transaction running?&lt;/p&gt;

&lt;p&gt;Isolation levels define these rules.&lt;/p&gt;

&lt;p&gt;Together, these three concepts form the foundation of reliable database applications.&lt;/p&gt;

&lt;p&gt;They complement one another rather than compete.&lt;/p&gt;

&lt;p&gt;A payment system, for example, might use:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Idempotency&lt;/strong&gt; to prevent duplicate payment requests.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Transactions&lt;/strong&gt; to ensure payment records and account balances remain synchronized.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Read Committed&lt;/strong&gt; or &lt;strong&gt;Serializable&lt;/strong&gt; isolation to guarantee consistent reads.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Locking&lt;/strong&gt; to prevent concurrent modifications of the same account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No single technique solves every reliability problem.&lt;/p&gt;

&lt;p&gt;Reliable systems combine several techniques, each addressing a different class of failure.&lt;/p&gt;





&lt;h2 id="practical-advice-for-backend-developers"&gt;Practical Advice for Backend Developers&lt;/h2&gt;

&lt;p&gt;If you’re just beginning your backend engineering journey, don’t feel pressured to master every isolation level immediately.&lt;/p&gt;

&lt;p&gt;Instead, focus on developing the habit of asking the right questions whenever you design a feature.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Could another user modify this data while I’m reading it?&lt;/li&gt;
  &lt;li&gt;If the same query runs twice, should it return the same result?&lt;/li&gt;
  &lt;li&gt;What happens if another transaction inserts new rows before mine finishes?&lt;/li&gt;
  &lt;li&gt;Is perfect consistency necessary, or is slightly stale data acceptable?&lt;/li&gt;
  &lt;li&gt;Would optimistic concurrency or explicit locking be a better solution?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thinking through these questions early in the design process often prevents bugs that are incredibly difficult to diagnose later in production.&lt;/p&gt;

&lt;p&gt;Many concurrency issues aren’t caused by writing incorrect code.&lt;/p&gt;

&lt;p&gt;They’re caused by making incorrect assumptions about how multiple users interact with the same data simultaneously.&lt;/p&gt;





&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;/h2&gt;

&lt;p&gt;Database isolation levels are often presented as a collection of definitions that developers are expected to memorize.&lt;/p&gt;

&lt;p&gt;In reality, they’re much simpler than they first appear.&lt;/p&gt;

&lt;p&gt;They’re simply different answers to one question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much should one transaction be allowed to observe while another transaction is still working?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lower isolation levels prioritize concurrency, allowing more users to interact with the database simultaneously.&lt;/p&gt;

&lt;p&gt;Higher isolation levels prioritize consistency, ensuring every transaction sees a predictable view of the data.&lt;/p&gt;

&lt;p&gt;Neither approach is universally correct.&lt;/p&gt;

&lt;p&gt;The right choice depends entirely on the problem you’re solving.&lt;/p&gt;

&lt;p&gt;As your applications grow, understanding isolation levels becomes increasingly important because concurrency is no longer the exception—it’s the norm.&lt;/p&gt;

&lt;p&gt;Every online store, banking application, inventory system, booking platform, and loan management system eventually reaches a point where multiple transactions compete for the same data.&lt;/p&gt;

&lt;p&gt;The developers who understand isolation levels don’t simply build applications that work.&lt;/p&gt;

&lt;p&gt;They build applications that continue working correctly under real-world load.&lt;/p&gt;





&lt;h2 id="whats-next"&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;In this series we’ve explored:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Idempotency&lt;/li&gt;
  &lt;li&gt;Race Conditions&lt;/li&gt;
  &lt;li&gt;Database Transactions&lt;/li&gt;
  &lt;li&gt;Database Isolation Levels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ve learned how to protect our systems from duplicate requests, concurrent updates, partial failures, and inconsistent reads.&lt;/p&gt;

&lt;p&gt;But one challenge still remains.&lt;/p&gt;

&lt;p&gt;Everything we’ve discussed assumes our application is running on a single database.&lt;/p&gt;

&lt;p&gt;What happens when your application is running on &lt;strong&gt;ten servers&lt;/strong&gt;, each processing requests simultaneously?&lt;/p&gt;

&lt;p&gt;A normal database lock isn’t always enough.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll explore &lt;strong&gt;Distributed Locks Explained: Coordinating Work Across Multiple Servers&lt;/strong&gt;, where we’ll see how systems like Redis, ZooKeeper, and etcd help ensure that only one application instance performs a critical operation at a time.&lt;/p&gt;

</description>
      <category>database</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Database Isolation Levels Explained: Why Two Transactions Can See Different Data</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 06 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/database-isolation-levels-explained-why-two-transactions-can-see-different-data-1408</link>
      <guid>https://dev.to/billy_de_cartel/database-isolation-levels-explained-why-two-transactions-can-see-different-data-1408</guid>
      <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Transactions guarantee that your work completes correctly. Isolation levels determine what everyone else is allowed to see while that work is happening.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;





&lt;p&gt;In the previous article, we explored database transactions and learned how they ensure multiple database operations either succeed together or fail together. Transactions protect applications from partial execution, preventing situations where money is deducted from one account without being deposited into another or where inventory is reduced without successfully creating an order.&lt;/p&gt;

&lt;p&gt;But transactions solve only part of the problem.&lt;/p&gt;

&lt;p&gt;Modern applications rarely have just one user interacting with the database at a time. Thousands of customers may be placing orders, updating records, making payments, or querying reports simultaneously. Each of these actions runs inside its own transaction, and more often than not, several transactions are accessing the same data at exactly the same time.&lt;/p&gt;

&lt;p&gt;This raises an important question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What should one transaction be allowed to see while another transaction is still running?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Imagine opening your banking application to check your account balance.&lt;/p&gt;

&lt;p&gt;At the exact same moment, your employer’s payroll system is depositing your monthly salary into the same account.&lt;/p&gt;

&lt;p&gt;Should your transaction see the new balance immediately?&lt;/p&gt;

&lt;p&gt;Should it continue seeing the old balance until the salary transaction finishes?&lt;/p&gt;

&lt;p&gt;Should it wait until the payroll transaction completes before showing you anything at all?&lt;/p&gt;

&lt;p&gt;Each answer is technically valid depending on how the database is configured.&lt;/p&gt;

&lt;p&gt;Now imagine an online store where only one laptop remains in stock.&lt;/p&gt;

&lt;p&gt;Customer A begins placing an order.&lt;/p&gt;

&lt;p&gt;Before their transaction finishes, Customer B checks the product page.&lt;/p&gt;

&lt;p&gt;Should Customer B still see one laptop available?&lt;/p&gt;

&lt;p&gt;Should they see zero?&lt;/p&gt;

&lt;p&gt;Should they wait until Customer A’s purchase either succeeds or fails?&lt;/p&gt;

&lt;p&gt;Again, the answer depends on the database’s isolation level.&lt;/p&gt;

&lt;p&gt;Isolation levels define the rules governing how concurrent transactions interact with one another. They determine whether one transaction can observe another transaction’s work before it has been completed, whether repeated reads always return the same result, and whether new rows appearing during a transaction should be visible immediately.&lt;/p&gt;

&lt;p&gt;Although isolation levels are often introduced as an advanced database topic, they’re really about one simple idea:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;How much of another transaction’s work should your transaction be allowed to see?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer has significant consequences for both correctness and performance.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore why isolation levels exist, the concurrency problems they solve, the four SQL standard isolation levels, and how to choose the right one for your application.&lt;/p&gt;





&lt;h1 id="why-isolation-exists"&gt;Why Isolation Exists&lt;/h1&gt;

&lt;p&gt;To understand isolation, imagine you’re reading a book in a library.&lt;/p&gt;

&lt;p&gt;Halfway through chapter three, someone walks over, quietly replaces several pages with new ones, and walks away.&lt;/p&gt;

&lt;p&gt;You continue reading without realizing anything changed.&lt;/p&gt;

&lt;p&gt;The beginning of the chapter describes one story.&lt;/p&gt;

&lt;p&gt;The ending describes another.&lt;/p&gt;

&lt;p&gt;Nothing makes sense.&lt;/p&gt;

&lt;p&gt;Databases can experience a remarkably similar problem.&lt;/p&gt;

&lt;p&gt;When multiple transactions execute simultaneously, each transaction may be reading data while another transaction is actively changing it.&lt;/p&gt;

&lt;p&gt;Without rules governing these interactions, applications could make decisions based on incomplete information, outdated values, or data that is eventually discarded.&lt;/p&gt;

&lt;p&gt;Isolation exists to prevent these situations.&lt;/p&gt;

&lt;p&gt;Rather than allowing every transaction unrestricted access to every change happening in the database, the database controls what each transaction can observe and when it can observe it.&lt;/p&gt;

&lt;p&gt;Think of it as putting walls between transactions.&lt;/p&gt;

&lt;p&gt;Some walls are very thin.&lt;/p&gt;

&lt;p&gt;Transactions can see almost everything happening around them.&lt;/p&gt;

&lt;p&gt;Other walls are much thicker.&lt;/p&gt;

&lt;p&gt;Transactions operate almost as though they’re the only users of the database.&lt;/p&gt;

&lt;p&gt;The thicker the wall, the more isolated the transaction becomes.&lt;/p&gt;





&lt;h1 id="the-trade-off-between-consistency-and-performance"&gt;The Trade-Off Between Consistency and Performance&lt;/h1&gt;

&lt;p&gt;At first glance, it might seem obvious that every database should simply use the highest possible isolation level.&lt;/p&gt;

&lt;p&gt;After all, if stronger isolation produces more consistent data, why wouldn’t every system choose it?&lt;/p&gt;

&lt;p&gt;The answer lies in performance.&lt;/p&gt;

&lt;p&gt;Imagine a supermarket with only one checkout counter.&lt;/p&gt;

&lt;p&gt;Every customer waits patiently in line.&lt;/p&gt;

&lt;p&gt;Because only one cashier is serving customers, inventory updates happen one at a time.&lt;/p&gt;

&lt;p&gt;Mistakes are rare.&lt;/p&gt;

&lt;p&gt;Unfortunately, the queue becomes enormous.&lt;/p&gt;

&lt;p&gt;Now imagine opening ten checkout counters.&lt;/p&gt;

&lt;p&gt;Customers move much faster.&lt;/p&gt;

&lt;p&gt;However, all ten cashiers are now updating the same inventory system simultaneously.&lt;/p&gt;

&lt;p&gt;Keeping everything synchronized becomes much more difficult.&lt;/p&gt;

&lt;p&gt;Databases face exactly the same challenge.&lt;/p&gt;

&lt;p&gt;Higher isolation levels provide stronger guarantees about data consistency, but they often require additional locking, coordination, and waiting.&lt;/p&gt;

&lt;p&gt;Lower isolation levels allow more transactions to execute concurrently, increasing throughput and reducing latency, but they also increase the likelihood that transactions observe changing data.&lt;/p&gt;

&lt;p&gt;Isolation levels are therefore a balancing act between two competing goals:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
&lt;strong&gt;Consistency&lt;/strong&gt;, ensuring every transaction sees predictable and reliable data.&lt;/li&gt;
  &lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;, allowing as many users as possible to interact with the system simultaneously.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different applications make different choices.&lt;/p&gt;

&lt;p&gt;A banking application processing financial transfers typically prioritizes correctness over raw performance.&lt;/p&gt;

&lt;p&gt;An analytics dashboard generating sales reports might tolerate slightly older data if it means thousands of users can run reports simultaneously without slowing the system.&lt;/p&gt;

&lt;p&gt;Neither approach is universally correct.&lt;/p&gt;

&lt;p&gt;The appropriate isolation level depends entirely on your business requirements.&lt;/p&gt;





&lt;h1 id="concurrency-anomalies-the-problems-isolation-levels-exist-to-solve"&gt;Concurrency Anomalies: The Problems Isolation Levels Exist to Solve&lt;/h1&gt;

&lt;p&gt;Isolation levels were not invented simply to make databases more complicated.&lt;/p&gt;

&lt;p&gt;They exist because concurrent transactions can produce behaviors that most developers would consider surprising—or even dangerous.&lt;/p&gt;

&lt;p&gt;These unexpected behaviors are collectively known as &lt;strong&gt;concurrency anomalies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every isolation level is essentially a trade-off between preventing these anomalies and maintaining good performance.&lt;/p&gt;

&lt;p&gt;Before discussing the isolation levels themselves, it’s important to understand the problems they are designed to solve.&lt;/p&gt;

&lt;p&gt;The four anomalies you’ll encounter most often are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Dirty Reads&lt;/li&gt;
  &lt;li&gt;Non-Repeatable Reads&lt;/li&gt;
  &lt;li&gt;Phantom Reads&lt;/li&gt;
  &lt;li&gt;Lost Updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each represents a different way concurrent transactions can interfere with one another.&lt;/p&gt;

&lt;p&gt;Let’s begin with the simplest.&lt;/p&gt;





&lt;h1 id="dirty-reads"&gt;Dirty Reads&lt;/h1&gt;

&lt;p&gt;Imagine Alice has &lt;strong&gt;KES 50,000&lt;/strong&gt; in her account.&lt;/p&gt;

&lt;p&gt;She initiates a transfer of &lt;strong&gt;KES 20,000&lt;/strong&gt; to another account.&lt;/p&gt;

&lt;p&gt;The banking system begins processing the transaction.&lt;/p&gt;

&lt;p&gt;The first step deducts the money from Alice’s balance.&lt;/p&gt;

&lt;p&gt;Before the transaction finishes, another process—perhaps an ATM balance inquiry or an online banking session—checks Alice’s account.&lt;/p&gt;

&lt;p&gt;At that moment, it sees a balance of &lt;strong&gt;KES 30,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything seems perfectly normal.&lt;/p&gt;

&lt;p&gt;Then something unexpected happens.&lt;/p&gt;

&lt;p&gt;The transfer fails because the destination account no longer exists.&lt;/p&gt;

&lt;p&gt;The database rolls back the transaction.&lt;/p&gt;

&lt;p&gt;Alice’s balance immediately returns to &lt;strong&gt;KES 50,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The second transaction has now made a decision based on information that never officially existed.&lt;/p&gt;

&lt;p&gt;It observed data that was eventually discarded.&lt;/p&gt;

&lt;p&gt;This is known as a &lt;strong&gt;Dirty Read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A dirty read occurs when one transaction reads data written by another transaction &lt;strong&gt;before that transaction has been committed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The easiest way to understand it is to imagine reading someone’s unfinished draft before they’ve decided whether to keep or delete it.&lt;/p&gt;

&lt;p&gt;The version you read may never become the final version.&lt;/p&gt;

&lt;p&gt;Making business decisions based on that draft could lead to incorrect outcomes.&lt;/p&gt;

&lt;p&gt;Fortunately, most modern relational databases prevent dirty reads by default because they are rarely desirable in business applications.&lt;/p&gt;

&lt;p&gt;The SQL standard still defines them because they help explain the spectrum of isolation levels.&lt;/p&gt;





&lt;h1 id="timeline-of-a-dirty-read"&gt;Timeline of a Dirty Read&lt;/h1&gt;

&lt;pre&gt;&lt;code&gt;Transaction A                     Transaction B

BEGIN

Balance = 50,000

↓

Update Balance = 30,000

                               Read Balance = 30,000 ❌

↓

Transfer Fails

↓

ROLLBACK

Balance returns to 50,000
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Transaction B has observed a value that disappeared moments later.&lt;/p&gt;

&lt;p&gt;From the perspective of the database, that balance never officially existed.&lt;/p&gt;

&lt;p&gt;Yet another transaction already acted as though it did.&lt;/p&gt;

&lt;p&gt;This is precisely the type of inconsistency isolation levels are designed to prevent.&lt;/p&gt;





&lt;h1 id="non-repeatable-reads"&gt;Non-Repeatable Reads&lt;/h1&gt;

&lt;p&gt;Suppose you’re building an online banking application.&lt;/p&gt;

&lt;p&gt;A customer opens the app and views their account balance. At that moment, the database reports a balance of &lt;strong&gt;KES 50,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The customer decides to transfer &lt;strong&gt;KES 40,000&lt;/strong&gt; to another account, but before confirming the transfer, the application performs one final balance check to ensure sufficient funds are still available.&lt;/p&gt;

&lt;p&gt;This seems like a perfectly reasonable workflow.&lt;/p&gt;

&lt;p&gt;However, between the first balance check and the second, another transaction deposits &lt;strong&gt;KES 100,000&lt;/strong&gt; into the same account.&lt;/p&gt;

&lt;p&gt;When the application performs the second query, the balance is no longer &lt;strong&gt;KES 50,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It’s now &lt;strong&gt;KES 150,000&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing is technically wrong.&lt;/p&gt;

&lt;p&gt;The second transaction committed successfully.&lt;/p&gt;

&lt;p&gt;The balance genuinely changed.&lt;/p&gt;

&lt;p&gt;The surprising part is that &lt;strong&gt;the same transaction read the same row twice and received two different answers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This phenomenon is known as a &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike a dirty read, the second transaction isn’t reading uncommitted data. Every value it sees has been permanently committed to the database.&lt;/p&gt;

&lt;p&gt;The inconsistency comes from the fact that another transaction modified the row while the first transaction was still running.&lt;/p&gt;

&lt;p&gt;Imagine reading yesterday’s newspaper while someone keeps replacing pages with today’s edition.&lt;/p&gt;

&lt;p&gt;The information isn’t incorrect.&lt;/p&gt;

&lt;p&gt;It’s simply inconsistent because the document changed while you were reading it.&lt;/p&gt;

&lt;p&gt;For many applications, this isn’t a problem.&lt;/p&gt;

&lt;p&gt;If you’re refreshing a weather dashboard or checking the number of users currently online, it’s perfectly acceptable for values to change between two queries.&lt;/p&gt;

&lt;p&gt;However, systems that rely on a stable snapshot of data—such as financial reporting, payroll processing, or end-of-day reconciliation—often require the same query to return the same result throughout the entire transaction.&lt;/p&gt;





&lt;h1 id="timeline-of-a-non-repeatable-read"&gt;Timeline of a Non-Repeatable Read&lt;/h1&gt;

&lt;pre&gt;&lt;code&gt;Transaction A                     Transaction B

BEGIN

Read Balance = 50,000

                               BEGIN

                               Deposit 100,000

                               COMMIT

Read Balance = 150,000 ❌

COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Transaction A never modified the balance itself.&lt;/p&gt;

&lt;p&gt;It simply asked the same question twice and received two different answers because another committed transaction changed the underlying data in the meantime.&lt;/p&gt;





&lt;h1 id="real-world-example-updating-a-customer-profile"&gt;Real-World Example: Updating a Customer Profile&lt;/h1&gt;

&lt;p&gt;Consider an insurance application where a customer service representative opens a customer’s profile.&lt;/p&gt;

&lt;p&gt;The representative spends several minutes reviewing the information before approving a policy update.&lt;/p&gt;

&lt;p&gt;Meanwhile, another employee updates the customer’s phone number and address.&lt;/p&gt;

&lt;p&gt;When the representative finally clicks &lt;strong&gt;Save&lt;/strong&gt;, the application may now be working with information that is different from what was originally displayed.&lt;/p&gt;

&lt;p&gt;Depending on how the application handles these changes, it could accidentally overwrite newer data or make decisions using outdated information.&lt;/p&gt;

&lt;p&gt;This isn’t a database bug.&lt;/p&gt;

&lt;p&gt;It’s simply the natural consequence of multiple users interacting with the same record at the same time.&lt;/p&gt;

&lt;p&gt;Applications that require users to work with a consistent view of the data often use higher isolation levels or optimistic concurrency controls to detect these situations before committing changes.&lt;/p&gt;





&lt;h1 id="phantom-reads"&gt;Phantom Reads&lt;/h1&gt;

&lt;p&gt;Now let’s consider a different scenario.&lt;/p&gt;

&lt;p&gt;Instead of reading a single row twice, imagine you’re querying an entire collection of rows.&lt;/p&gt;

&lt;p&gt;Suppose you’re generating a report showing all loan applications submitted today.&lt;/p&gt;

&lt;p&gt;Your first query returns:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Loan Applications Submitted Today

-------------------------------

Loan #101

Loan #102

Loan #103

Total: 3
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;While your report is still running, another loan application is submitted and committed to the database.&lt;/p&gt;

&lt;p&gt;A few moments later, your transaction performs the exact same query again.&lt;/p&gt;

&lt;p&gt;This time the results look different.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Loan Applications Submitted Today

-------------------------------

Loan #101

Loan #102

Loan #103

Loan #104

Total: 4
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice what changed.&lt;/p&gt;

&lt;p&gt;None of the existing rows were modified.&lt;/p&gt;

&lt;p&gt;Instead, an entirely &lt;strong&gt;new row appeared&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is called a &lt;strong&gt;Phantom Read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A phantom read occurs when the same query returns a different set of rows because another transaction inserted, updated, or deleted records that match the query’s search criteria.&lt;/p&gt;

&lt;p&gt;Think of it like counting the number of people in a room.&lt;/p&gt;

&lt;p&gt;You count 20 people.&lt;/p&gt;

&lt;p&gt;While you’re writing the number down, someone walks into the room.&lt;/p&gt;

&lt;p&gt;You count again.&lt;/p&gt;

&lt;p&gt;Now there are 21 people.&lt;/p&gt;

&lt;p&gt;Nothing about the original twenty people changed.&lt;/p&gt;

&lt;p&gt;The difference is that a new “phantom” appeared between your two observations.&lt;/p&gt;





&lt;h1 id="timeline-of-a-phantom-read"&gt;Timeline of a Phantom Read&lt;/h1&gt;

&lt;pre&gt;&lt;code&gt;Transaction A                     Transaction B

BEGIN

SELECT *

WHERE loan_date = TODAY

Returns 3 rows

                               BEGIN

                               INSERT Loan #104

                               COMMIT

SELECT *

WHERE loan_date = TODAY

Returns 4 rows ❌

COMMIT
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Unlike a non-repeatable read, where an existing row changes, phantom reads involve the appearance or disappearance of entire rows.&lt;/p&gt;





&lt;h1 id="why-phantom-reads-matter"&gt;Why Phantom Reads Matter&lt;/h1&gt;

&lt;p&gt;Imagine you’re calculating today’s total revenue for financial reporting.&lt;/p&gt;

&lt;p&gt;Your reporting transaction begins at 5:00 PM and starts aggregating sales.&lt;/p&gt;

&lt;p&gt;While it’s still processing, new sales continue being recorded.&lt;/p&gt;

&lt;p&gt;Different parts of the report may now be working with different datasets.&lt;/p&gt;

&lt;p&gt;The total revenue calculated on page one might not match the detailed transaction list generated on page five because new rows appeared while the report was still executing.&lt;/p&gt;

&lt;p&gt;In reporting systems, this can produce confusing and inconsistent results.&lt;/p&gt;

&lt;p&gt;Higher isolation levels solve this problem by ensuring the transaction sees a consistent snapshot of the data throughout its lifetime, even if other transactions continue inserting new rows.&lt;/p&gt;





&lt;h1 id="lost-updates"&gt;Lost Updates&lt;/h1&gt;

&lt;p&gt;The final concurrency anomaly is perhaps the most dangerous because it silently discards valid work.&lt;/p&gt;

&lt;p&gt;Imagine two warehouse employees looking at the same inventory record.&lt;/p&gt;

&lt;p&gt;The system currently shows:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Laptop Stock = 10
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Employee A sells one laptop.&lt;/p&gt;

&lt;p&gt;Employee B also sells one laptop at almost exactly the same time.&lt;/p&gt;

&lt;p&gt;Both employees read the current stock before making their update.&lt;/p&gt;

&lt;p&gt;Each calculates the new quantity as:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;10 - 1 = 9
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Employee A saves.&lt;/p&gt;

&lt;p&gt;The inventory becomes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;9
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A fraction of a second later, Employee B saves.&lt;/p&gt;

&lt;p&gt;The inventory is still:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;9
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One of the updates has effectively disappeared.&lt;/p&gt;

&lt;p&gt;The correct inventory should now be &lt;strong&gt;8&lt;/strong&gt;, but because both transactions started from the same original value, one update overwrote the other.&lt;/p&gt;

&lt;p&gt;This is known as a &lt;strong&gt;Lost Update&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Unlike the previous anomalies, nothing appears obviously wrong.&lt;/p&gt;

&lt;p&gt;No errors occur.&lt;/p&gt;

&lt;p&gt;No constraints are violated.&lt;/p&gt;

&lt;p&gt;The database happily accepts both updates.&lt;/p&gt;

&lt;p&gt;The problem is that one user’s work has unintentionally replaced another’s.&lt;/p&gt;

&lt;p&gt;Lost updates are one of the primary reasons databases provide row locking, optimistic concurrency control, and stronger isolation levels.&lt;/p&gt;

&lt;p&gt;Without these protections, applications that receive many simultaneous updates—such as inventory systems, banking platforms, or booking applications—can slowly drift away from reality without anyone noticing.&lt;/p&gt;

&lt;p&gt;Now that we understand the problems, the next question is obvious: How do databases prevent them? That’s exactly what we’ll cover in Part 2.”&lt;/p&gt;

</description>
      <category>database</category>
      <category>concurrency</category>
    </item>
    <item>
      <title>Database Transactions Explained: Keeping Data Correct When Things Go Wrong</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/database-transactions-explained-keeping-data-correct-when-things-go-wrong-3hbc</link>
      <guid>https://dev.to/billy_de_cartel/database-transactions-explained-keeping-data-correct-when-things-go-wrong-3hbc</guid>
      <description>&lt;blockquote&gt;
  &lt;p&gt;&lt;em&gt;“Transactions are not just a database feature—they’re one of the fundamental building blocks of reliable software.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Imagine you’re transferring &lt;strong&gt;KES 10,000&lt;/strong&gt; from your savings account to a friend.&lt;/p&gt;

&lt;p&gt;From your perspective, it’s a single action. You tap &lt;strong&gt;“Send Money”&lt;/strong&gt;, authenticate the transaction, and wait for confirmation. Behind the scenes, however, the banking system performs several independent operations. It verifies that you have sufficient funds, deducts the amount from your account, credits your friend’s account, records the transaction in a ledger, updates account balances, and generates a receipt.&lt;/p&gt;

&lt;p&gt;Each of these operations is important, but together they represent a single business action: &lt;strong&gt;a money transfer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now imagine the server crashes immediately after deducting the money from your account but before crediting your friend.&lt;/p&gt;

&lt;p&gt;The result is disastrous. Your balance has decreased, your friend never receives the money, and unless additional recovery mechanisms exist, the system is left in an inconsistent state. From a customer’s perspective, the money has simply disappeared.&lt;/p&gt;

&lt;p&gt;The same problem appears outside banking.&lt;/p&gt;

&lt;p&gt;An e-commerce application might create an order, reduce inventory, process a payment, generate an invoice, and send a confirmation email. If the payment succeeds but the order creation fails, the customer has paid for a product that the system doesn’t believe exists. Likewise, in a loan management system, a repayment may update the outstanding balance, post accounting entries, and generate a receipt. If only some of those updates complete, financial records quickly become unreliable.&lt;/p&gt;

&lt;p&gt;These problems aren’t caused by bad algorithms or poor business logic. They’re caused by &lt;strong&gt;partial execution&lt;/strong&gt; when only part of a larger operation succeeds.&lt;/p&gt;

&lt;p&gt;This is precisely the problem database transactions were designed to solve.&lt;/p&gt;

&lt;p&gt;A transaction ensures that multiple database operations behave as a single unit of work. Either every operation succeeds together, or every operation is rolled back as though nothing ever happened. There is no halfway point where your system is left in an inconsistent state.&lt;/p&gt;

&lt;p&gt;For backend developers, understanding transactions is just as important as understanding APIs or databases themselves. They are the foundation upon which reliable financial systems, booking platforms, inventory systems, healthcare applications, and countless other business-critical systems are built.&lt;/p&gt;





&lt;h1 id="what-is-a-database-transaction"&gt;What Is a Database Transaction?&lt;/h1&gt;

&lt;p&gt;A database transaction is a collection of one or more database operations that the database treats as a single logical operation.&lt;/p&gt;

&lt;p&gt;Instead of thinking about individual SQL statements, think about the business process they represent.&lt;/p&gt;

&lt;p&gt;Suppose a customer purchases the last laptop in your online store. That single purchase might require your application to:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Create an order.&lt;/li&gt;
  &lt;li&gt;Deduct one item from inventory.&lt;/li&gt;
  &lt;li&gt;Reserve the shipment.&lt;/li&gt;
  &lt;li&gt;Record the payment.&lt;/li&gt;
  &lt;li&gt;Create an invoice.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Although these are separate SQL statements, they represent one business event. Either all of them should succeed, or none of them should.&lt;/p&gt;

&lt;p&gt;That’s exactly what a transaction guarantees.&lt;/p&gt;

&lt;p&gt;Without transactions, every statement executes independently. If statement number four fails, the previous three remain committed, leaving your data inconsistent.&lt;/p&gt;

&lt;p&gt;With transactions, the database waits until you’re satisfied that every operation has completed successfully. Only then are the changes permanently saved.&lt;/p&gt;





&lt;h1 id="understanding-transactions-through-a-simple-example"&gt;Understanding Transactions Through a Simple Example&lt;/h1&gt;

&lt;p&gt;Let’s return to the banking example.&lt;/p&gt;

&lt;p&gt;Alice wants to transfer &lt;strong&gt;KES 10,000&lt;/strong&gt; to Bob.&lt;/p&gt;

&lt;p&gt;A simplified version of the SQL might look like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;UPDATE accounts
SET balance = balance - 10000
WHERE account_id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE account_id = 2;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At first glance, this seems perfectly reasonable.&lt;/p&gt;

&lt;p&gt;But imagine the database crashes immediately after the first statement executes.&lt;/p&gt;

&lt;p&gt;Alice’s balance has already been reduced.&lt;/p&gt;

&lt;p&gt;Bob’s balance has not increased.&lt;/p&gt;

&lt;p&gt;The system now contains incorrect financial data.&lt;/p&gt;

&lt;p&gt;This is why production systems rarely execute related operations independently.&lt;/p&gt;

&lt;p&gt;Instead, they wrap them inside a transaction.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;BEGIN;

UPDATE accounts
SET balance = balance - 10000
WHERE account_id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE account_id = 2;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If every statement succeeds, the database executes the &lt;code&gt;COMMIT&lt;/code&gt;, making the changes permanent.&lt;/p&gt;

&lt;p&gt;If any statement fails before that point, the application issues a &lt;code&gt;ROLLBACK&lt;/code&gt;, and the database restores itself to exactly the state it was in before the transaction began.&lt;/p&gt;

&lt;p&gt;To the outside world, it appears as though the failed transfer never happened.&lt;/p&gt;

&lt;p&gt;This “all-or-nothing” behavior is what makes transactions so valuable.&lt;/p&gt;





&lt;h1 id="the-lifecycle-of-a-transaction"&gt;The Lifecycle of a Transaction&lt;/h1&gt;

&lt;p&gt;Every transaction follows a predictable lifecycle.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;BEGIN
   │
Execute SQL Operations
   │
Everything Successful?
   │
 ┌─ Yes ─────────────┐
 │                   │
COMMIT          Changes Saved
 │
 └─ No ──────────────┐
                     │
                 ROLLBACK
                     │
          Database Restored
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The process begins with &lt;code&gt;BEGIN&lt;/code&gt;, which tells the database to temporarily hold all modifications rather than immediately committing them.&lt;/p&gt;

&lt;p&gt;The application then performs one or more operations. These could involve inserting records, updating balances, deleting data, or modifying relationships between tables.&lt;/p&gt;

&lt;p&gt;If every operation succeeds, the application calls &lt;code&gt;COMMIT&lt;/code&gt;. At that point, the database permanently saves all changes.&lt;/p&gt;

&lt;p&gt;If anything goes wrong along the way, perhaps a validation error, a database constraint violation, or even an unexpected server failure, the transaction is rolled back, discarding every change made since &lt;code&gt;BEGIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The beauty of this model is that the database itself guarantees consistency. Developers don’t need to manually undo every failed operation because the database handles that responsibility.&lt;/p&gt;





&lt;h1 id="the-four-acid-properties"&gt;The Four ACID Properties&lt;/h1&gt;

&lt;p&gt;When developers discuss transactions, you’ll almost always hear the term &lt;strong&gt;ACID&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Despite sounding intimidating, ACID simply describes the guarantees that modern relational databases provide when executing transactions.&lt;/p&gt;

&lt;h2 id="atomicity"&gt;Atomicity&lt;/h2&gt;

&lt;p&gt;Atomicity means that a transaction is indivisible.&lt;/p&gt;

&lt;p&gt;Either every operation succeeds, or none of them do.&lt;/p&gt;

&lt;p&gt;Returning to our banking example, it makes no sense for money to be deducted from one account without being added to another. The transaction must succeed completely or fail completely.&lt;/p&gt;

&lt;p&gt;Think of flipping a light switch.&lt;/p&gt;

&lt;p&gt;The light cannot be half on.&lt;/p&gt;

&lt;p&gt;Similarly, a transaction cannot be half completed.&lt;/p&gt;





&lt;h2 id="consistency"&gt;Consistency&lt;/h2&gt;

&lt;p&gt;Consistency ensures that every transaction leaves the database in a valid state.&lt;/p&gt;

&lt;p&gt;Business rules should always remain true.&lt;/p&gt;

&lt;p&gt;If your application enforces that inventory can never become negative, then a successful transaction should never violate that rule.&lt;/p&gt;

&lt;p&gt;Likewise, if your accounting system requires every journal entry to balance, no committed transaction should ever leave the ledger unbalanced.&lt;/p&gt;

&lt;p&gt;Consistency isn’t about preventing bugs in your application logic; it’s about ensuring that completed transactions respect the rules defined by your database and your business.&lt;/p&gt;





&lt;h2 id="isolation"&gt;Isolation&lt;/h2&gt;

&lt;p&gt;Isolation becomes important when multiple users interact with the system simultaneously.&lt;/p&gt;

&lt;p&gt;Imagine two customers attempting to purchase the last available ticket for a concert.&lt;/p&gt;

&lt;p&gt;Without proper isolation, both requests may read the inventory before either updates it. Both believe the ticket is available, and both complete the purchase.&lt;/p&gt;

&lt;p&gt;You’ve now sold the same seat twice.&lt;/p&gt;

&lt;p&gt;Isolation ensures that concurrent transactions don’t interfere with one another in ways that produce inconsistent results.&lt;/p&gt;

&lt;p&gt;In our previous article, we discussed &lt;strong&gt;race conditions&lt;/strong&gt; situations where multiple requests compete to modify the same data. Isolation is one of the database mechanisms used to prevent those concurrency problems.&lt;/p&gt;

&lt;p&gt;We’ll explore isolation levels in greater depth in the next article because they deserve an entire discussion of their own.&lt;/p&gt;





&lt;h2 id="durability"&gt;Durability&lt;/h2&gt;

&lt;p&gt;Durability guarantees that once a transaction has been committed, the changes are permanent.&lt;/p&gt;

&lt;p&gt;Even if the server loses power immediately after the commit, the database ensures that committed data survives.&lt;/p&gt;

&lt;p&gt;Modern databases achieve this through techniques such as write-ahead logging, transaction logs, and crash recovery.&lt;/p&gt;

&lt;p&gt;For developers, the important takeaway is simple: once the database confirms a successful commit, you can trust that the data has been safely stored.&lt;/p&gt;





&lt;h1 id="transactions-solve-partial-failures-not-every-problem"&gt;Transactions Solve Partial Failures: Not Every Problem&lt;/h1&gt;

&lt;p&gt;One misconception among newer developers is that transactions magically solve every data consistency problem.&lt;/p&gt;

&lt;p&gt;They don’t.&lt;/p&gt;

&lt;p&gt;Transactions protect against &lt;strong&gt;partial execution&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Suppose your application updates three tables and crashes after updating the second one. A transaction ensures that the database rolls everything back, preventing inconsistent data. However, transactions don’t automatically solve concurrency problems.&lt;/p&gt;

&lt;p&gt;Imagine two users attempting to withdraw money from the same account simultaneously.
Each transaction independently checks the balance before either completes. If both see the same balance and both proceed, the final result may still be incorrect depending on your isolation level. This isn’t a transaction problem. It’s a concurrency problem.&lt;/p&gt;

&lt;p&gt;That’s why understanding race conditions and transactions together is so important. They solve different classes of reliability issues.&lt;/p&gt;





&lt;h1 id="common-places-youll-use-transactions"&gt;Common Places You’ll Use Transactions&lt;/h1&gt;

&lt;p&gt;Transactions appear almost everywhere in modern backend systems.&lt;/p&gt;

&lt;p&gt;Payment processing is perhaps the most obvious example. Charging a customer’s card, recording the payment, updating invoices, and generating accounting entries should either all succeed or all fail together.&lt;/p&gt;

&lt;p&gt;Inventory management systems use transactions to ensure stock counts remain accurate even when multiple customers are purchasing products simultaneously.&lt;/p&gt;

&lt;p&gt;Booking platforms rely on transactions to reserve hotel rooms, airline seats, or event tickets without creating conflicting reservations.&lt;/p&gt;

&lt;p&gt;Loan management systems use transactions when posting repayments, updating outstanding balances, calculating accrued interest, and recording accounting entries.&lt;/p&gt;

&lt;p&gt;Healthcare systems use them to ensure patient records, prescriptions, billing information, and appointment schedules remain synchronized.&lt;/p&gt;

&lt;p&gt;Any time a business operation spans multiple database changes, a transaction is usually involved.&lt;/p&gt;





&lt;h1 id="common-mistakes-developers-make"&gt;Common Mistakes Developers Make&lt;/h1&gt;

&lt;p&gt;One of the most common mistakes is keeping transactions open for too long.&lt;/p&gt;

&lt;p&gt;Imagine starting a transaction, calling a third-party payment API, waiting several seconds for a response, and only then committing the transaction.&lt;/p&gt;

&lt;p&gt;During that entire period, database resources may remain locked, reducing performance for other users.&lt;/p&gt;

&lt;p&gt;A better approach is to perform external API calls before starting the transaction whenever possible, keeping the transaction focused solely on database operations.&lt;/p&gt;

&lt;p&gt;Another common mistake is assuming transactions automatically protect against concurrent updates. As we’ve already seen, concurrency introduces an entirely different set of challenges that require locking strategies or appropriate isolation levels.&lt;/p&gt;

&lt;p&gt;Finally, developers sometimes forget that transactions should represent business operations not individual SQL statements. Wrapping every single query in its own transaction rarely provides meaningful benefits.&lt;/p&gt;





&lt;h1 id="transactions-in-modern-frameworks"&gt;Transactions in Modern Frameworks&lt;/h1&gt;

&lt;p&gt;Fortunately, most frameworks make transactions straightforward to use.&lt;/p&gt;

&lt;p&gt;Laravel offers the &lt;code&gt;DB::transaction()&lt;/code&gt; helper.&lt;/p&gt;

&lt;p&gt;Django provides &lt;code&gt;transaction.atomic()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Entity Framework supports &lt;code&gt;BeginTransactionAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Spring Boot uses the &lt;code&gt;@Transactional&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;Although the syntax differs, the underlying principle never changes. The framework simply tells the database when to begin the transaction, when to commit it, and when to roll it back if something goes wrong.&lt;/p&gt;

&lt;p&gt;Understanding the concept matters far more than memorizing framework-specific syntax.&lt;/p&gt;





&lt;h1 id="transactions-idempotency-and-race-conditions"&gt;Transactions, Idempotency, and Race Conditions&lt;/h1&gt;

&lt;p&gt;If you’ve been following this series, you may have noticed that each concept addresses a different reliability challenge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency&lt;/strong&gt; protects against duplicate requests by ensuring that repeating the same request doesn’t produce duplicate side effects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race conditions&lt;/strong&gt; occur when multiple requests compete to modify shared data simultaneously, leading to unpredictable outcomes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transactions&lt;/strong&gt; ensure that a group of related database operations either all succeed together or all fail together.&lt;/p&gt;

&lt;p&gt;Reliable backend systems typically rely on all three.&lt;/p&gt;

&lt;p&gt;Imagine a payment API.&lt;/p&gt;

&lt;p&gt;Idempotency prevents customers from being charged twice if they retry a request.&lt;/p&gt;

&lt;p&gt;Transactions ensure that charging the customer, recording the payment, and updating account balances either all succeed or all fail.&lt;/p&gt;

&lt;p&gt;Proper concurrency control ensures that two simultaneous payment requests don’t corrupt shared data.&lt;/p&gt;

&lt;p&gt;Each concept complements the others rather than replacing them.&lt;/p&gt;





&lt;h1 id="final-thoughts"&gt;Final Thoughts&lt;/h1&gt;

&lt;p&gt;Transactions are one of the reasons relational databases remain so powerful. They provide developers with a reliable mechanism for preserving data integrity even when failures occur.&lt;/p&gt;

&lt;p&gt;As systems become larger and more distributed, failures become inevitable. Servers crash, networks fail, APIs time out, and users submit requests simultaneously. Transactions don’t eliminate those realities, but they ensure your database remains consistent when they happen.&lt;/p&gt;

&lt;p&gt;Whenever you’re implementing a feature that modifies multiple pieces of related data, pause for a moment and ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;What happens if this operation fails halfway through?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is “my system ends up in an inconsistent state,” then you’ve almost certainly found a place where a database transaction belongs.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll build on this foundation by exploring &lt;strong&gt;database isolation levels&lt;/strong&gt; and why two perfectly valid transactions can still interfere with one another when they run at the same time.&lt;/p&gt;

</description>
      <category>database</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Race Conditions Explained: The Concurrency Bug Every Backend Developer Should Understand</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Sun, 28 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/race-conditions-explained-the-concurrency-bug-every-backend-developer-should-understand-52l9</link>
      <guid>https://dev.to/billy_de_cartel/race-conditions-explained-the-concurrency-bug-every-backend-developer-should-understand-52l9</guid>
      <description>&lt;p&gt;Imagine you’re trying to buy the last ticket for your favorite concert.&lt;/p&gt;

&lt;p&gt;The website shows:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;Only 1 ticket remaining.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At exactly the same moment, someone else clicks &lt;strong&gt;Buy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Both of you complete payment.&lt;/p&gt;

&lt;p&gt;Both of you receive confirmation emails.&lt;/p&gt;

&lt;p&gt;But there was only one ticket.&lt;/p&gt;

&lt;p&gt;How did two people successfully purchase the same seat?&lt;/p&gt;

&lt;p&gt;Now imagine the same thing happening in a banking application.&lt;/p&gt;

&lt;p&gt;Two ATM withdrawals happen at almost the same time.&lt;/p&gt;

&lt;p&gt;Both check the balance before either transaction finishes.&lt;/p&gt;

&lt;p&gt;Both think there’s enough money.&lt;/p&gt;

&lt;p&gt;Both approve the withdrawal.&lt;/p&gt;

&lt;p&gt;The account ends up with a negative balance.&lt;/p&gt;

&lt;p&gt;Neither application is necessarily “broken.”&lt;/p&gt;

&lt;p&gt;Instead, they suffer from one of the most common problems in software engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Race conditions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Race conditions are among the hardest bugs to reproduce because they don’t happen every time. They may only appear under heavy traffic, high concurrency, or perfect timing. Everything works during testing until your application reaches production.&lt;/p&gt;

&lt;p&gt;In this article, we’ll explore what race conditions are, why they happen, how they relate to idempotency, and the techniques developers use to prevent them.&lt;/p&gt;





&lt;h1 id="what-is-a-race-condition"&gt;What Is a Race Condition?&lt;/h1&gt;

&lt;p&gt;A race condition occurs when &lt;strong&gt;two or more operations access and modify the same piece of data at the same time, and the final result depends on the order in which they execute.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The important phrase is:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;The outcome depends on timing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s what makes race conditions so dangerous.&lt;/p&gt;

&lt;p&gt;Sometimes everything works.&lt;/p&gt;

&lt;p&gt;Sometimes everything breaks.&lt;/p&gt;

&lt;p&gt;The exact same code can produce different results simply because two requests happened a few milliseconds apart.&lt;/p&gt;





&lt;h1 id="a-simple-analogy"&gt;A Simple Analogy&lt;/h1&gt;

&lt;p&gt;Imagine two people standing in front of a cookie jar.&lt;/p&gt;

&lt;p&gt;The jar contains exactly one cookie.&lt;/p&gt;

&lt;p&gt;Both people look inside.&lt;/p&gt;

&lt;p&gt;Both see one cookie.&lt;/p&gt;

&lt;p&gt;Both reach in.&lt;/p&gt;

&lt;p&gt;Both believe they’ll get the cookie.&lt;/p&gt;

&lt;p&gt;Reality says otherwise.&lt;/p&gt;

&lt;p&gt;Only one cookie exists.&lt;/p&gt;

&lt;p&gt;The mistake happened because both people &lt;strong&gt;checked the state before either updated it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Software behaves exactly the same way.&lt;/p&gt;





&lt;h1 id="a-real-banking-example"&gt;A Real Banking Example&lt;/h1&gt;

&lt;p&gt;Suppose an account has:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Balance = $100
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Two withdrawal requests arrive simultaneously.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Request A → Withdraw $80

Request B → Withdraw $50
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Without synchronization:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Request A
↓

Read Balance ($100)

------------

Request B

↓

Read Balance ($100)

------------

Request A

Balance = $20

------------

Request B

Balance = $50
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Depending on timing, the final balance might be:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;$20

or

$50

or

-$30
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;None of these outcomes are guaranteed.&lt;/p&gt;

&lt;p&gt;This is a race condition.&lt;/p&gt;





&lt;h1 id="why-it-works-during-development"&gt;Why It Works During Development&lt;/h1&gt;

&lt;p&gt;Most developers test applications alone.&lt;/p&gt;

&lt;p&gt;One request.&lt;/p&gt;

&lt;p&gt;One browser.&lt;/p&gt;

&lt;p&gt;One user.&lt;/p&gt;

&lt;p&gt;Everything works perfectly.&lt;/p&gt;

&lt;p&gt;Production is different.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;5,000 users&lt;/li&gt;
  &lt;li&gt;Hundreds of requests per second&lt;/li&gt;
  &lt;li&gt;Multiple application servers&lt;/li&gt;
  &lt;li&gt;Database replication&lt;/li&gt;
  &lt;li&gt;Network latency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The probability of two requests colliding becomes much higher.&lt;/p&gt;

&lt;p&gt;Race conditions often appear only after an application becomes successful.&lt;/p&gt;

&lt;p&gt;Ironically, scaling your application can reveal bugs that never existed during development.&lt;/p&gt;





&lt;h1 id="race-conditions-vs-idempotency"&gt;Race Conditions vs Idempotency&lt;/h1&gt;

&lt;p&gt;If you’ve read my previous article on idempotency, if not, read it first &lt;a href="https://billyokeyo.dev/posts/idempotency-explained/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You might wonder whether they’re the same thing.&lt;/p&gt;

&lt;p&gt;They’re related, but they solve different problems.&lt;/p&gt;

&lt;h3 id="idempotency-answers"&gt;Idempotency answers:&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;What if the &lt;strong&gt;same request&lt;/strong&gt; is sent twice?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;POST /payments

↓

Retry

↓

POST /payments
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The solution is an &lt;strong&gt;Idempotency-Key&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The same request produces the same result.&lt;/p&gt;





&lt;h3 id="race-conditions-answer"&gt;Race conditions answer:&lt;/h3&gt;

&lt;blockquote&gt;
  &lt;p&gt;What if &lt;strong&gt;different requests&lt;/strong&gt; happen at the same time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;User A buys last ticket

↓

User B buys last ticket
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These are two legitimate requests from different users.&lt;/p&gt;

&lt;p&gt;An idempotency key won’t help because the requests are not duplicates.&lt;/p&gt;

&lt;p&gt;Race conditions require synchronization, not deduplication.&lt;/p&gt;





&lt;h1 id="real-world-examples"&gt;Real-World Examples&lt;/h1&gt;

&lt;h2 id="airline-seat-booking"&gt;Airline Seat Booking&lt;/h2&gt;

&lt;p&gt;Only one seat remains.&lt;/p&gt;

&lt;p&gt;Two customers purchase it simultaneously.&lt;/p&gt;

&lt;p&gt;Without proper locking:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Seat sold twice&lt;/li&gt;
  &lt;li&gt;Refund required&lt;/li&gt;
  &lt;li&gt;Customer frustration&lt;/li&gt;
&lt;/ul&gt;





&lt;h2 id="e-commerce-inventory"&gt;E-Commerce Inventory&lt;/h2&gt;

&lt;p&gt;Stock:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Laptop

Quantity = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Two customers purchase simultaneously.&lt;/p&gt;

&lt;p&gt;Without protection:&lt;/p&gt;

&lt;p&gt;Inventory becomes:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;-1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now you’ve sold a product you don’t have.&lt;/p&gt;





&lt;h2 id="loan-approval-systems"&gt;Loan Approval Systems&lt;/h2&gt;

&lt;p&gt;Imagine two loan officers reviewing the same application.&lt;/p&gt;

&lt;p&gt;Officer A:&lt;/p&gt;

&lt;p&gt;Approve.&lt;/p&gt;

&lt;p&gt;Officer B:&lt;/p&gt;

&lt;p&gt;Reject.&lt;/p&gt;

&lt;p&gt;If both updates happen simultaneously without coordination, the final loan status depends entirely on timing.&lt;/p&gt;





&lt;h2 id="coupon-redemption"&gt;Coupon Redemption&lt;/h2&gt;

&lt;p&gt;Promotion:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;First 100 customers only
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If multiple requests update the redemption count simultaneously, you might accidentally issue:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;103

105

110

coupons.
&lt;/code&gt;&lt;/pre&gt;





&lt;h1 id="how-race-conditions-happen"&gt;How Race Conditions Happen&lt;/h1&gt;

&lt;p&gt;Most race conditions follow this pattern:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Read

↓

Modify

↓

Write
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The danger lies between &lt;strong&gt;Read&lt;/strong&gt; and &lt;strong&gt;Write&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Read Balance

↓

Calculate New Balance

↓

Update Balance
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If another request changes the balance between those steps, your calculation becomes outdated.&lt;/p&gt;

&lt;p&gt;This is known as a &lt;strong&gt;Lost Update&lt;/strong&gt; problem.&lt;/p&gt;





&lt;h1 id="solution-1-database-transactions"&gt;Solution 1: Database Transactions&lt;/h1&gt;

&lt;p&gt;Transactions ensure multiple operations succeed or fail together.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;BEGIN;

SELECT balance
FROM accounts
WHERE id = 1;

UPDATE accounts
SET balance = balance - 80
WHERE id = 1;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Transactions protect data consistency.&lt;/p&gt;

&lt;p&gt;However, they don’t automatically eliminate every race condition.&lt;/p&gt;

&lt;p&gt;Isolation levels matter too.&lt;/p&gt;





&lt;h1 id="solution-2-row-level-locks"&gt;Solution 2: Row-Level Locks&lt;/h1&gt;

&lt;p&gt;Many relational databases allow locking a row while it’s being updated.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT *
FROM accounts
WHERE id = 1
FOR UPDATE;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now:&lt;/p&gt;

&lt;p&gt;Request A locks the row.&lt;/p&gt;

&lt;p&gt;Request B must wait.&lt;/p&gt;

&lt;p&gt;Only after Request A finishes can Request B continue.&lt;/p&gt;

&lt;p&gt;This guarantees consistent updates.&lt;/p&gt;





&lt;h1 id="solution-3-optimistic-locking"&gt;Solution 3: Optimistic Locking&lt;/h1&gt;

&lt;p&gt;Instead of preventing conflicts, optimistic locking detects them.&lt;/p&gt;

&lt;p&gt;Imagine a version number.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Account

Balance = 100

Version = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Update:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;WHERE Version = 5
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If another request updates the row first:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Version = 6
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Your update fails.&lt;/p&gt;

&lt;p&gt;The client retries with fresh data.&lt;/p&gt;

&lt;p&gt;Optimistic locking works well when collisions are relatively rare.&lt;/p&gt;





&lt;h1 id="solution-4-distributed-locks"&gt;Solution 4: Distributed Locks&lt;/h1&gt;

&lt;p&gt;What if your application runs on multiple servers?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Server A

↓

Database

↑

Server B
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A normal in-memory lock won’t work because each server has its own memory.&lt;/p&gt;

&lt;p&gt;Instead, developers use distributed locking systems like:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Redis (Redlock)&lt;/li&gt;
  &lt;li&gt;ZooKeeper&lt;/li&gt;
  &lt;li&gt;etcd&lt;/li&gt;
  &lt;li&gt;Consul&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These coordinate access across multiple application instances.&lt;/p&gt;





&lt;h1 id="solution-5-atomic-database-operations"&gt;Solution 5: Atomic Database Operations&lt;/h1&gt;

&lt;p&gt;Sometimes you don’t need to:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Read

↓

Calculate

↓

Write
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Instead, let the database perform the update atomically.&lt;/p&gt;

&lt;p&gt;Bad:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;SELECT quantity;

quantity--;

UPDATE products;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Better:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;UPDATE products

SET quantity = quantity - 1

WHERE quantity &amp;gt; 0;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now the database guarantees consistency.&lt;/p&gt;





&lt;h1 id="detecting-race-conditions"&gt;Detecting Race Conditions&lt;/h1&gt;

&lt;p&gt;One reason race conditions are difficult is that they rarely appear during manual testing.&lt;/p&gt;

&lt;p&gt;Ways to uncover them include:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Load testing with concurrent users&lt;/li&gt;
  &lt;li&gt;Stress testing&lt;/li&gt;
  &lt;li&gt;Running parallel integration tests&lt;/li&gt;
  &lt;li&gt;Simulating delayed responses&lt;/li&gt;
  &lt;li&gt;Chaos engineering&lt;/li&gt;
  &lt;li&gt;Monitoring production logs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a bug only appears “sometimes,” concurrency should be one of your first suspects.&lt;/p&gt;





&lt;h1 id="best-practices"&gt;Best Practices&lt;/h1&gt;

&lt;p&gt;When designing systems that handle shared data:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Keep transactions short.&lt;/li&gt;
  &lt;li&gt;Avoid long-running locks.&lt;/li&gt;
  &lt;li&gt;Prefer atomic database operations where possible.&lt;/li&gt;
  &lt;li&gt;Use optimistic locking when contention is low.&lt;/li&gt;
  &lt;li&gt;Use pessimistic locking for critical resources.&lt;/li&gt;
  &lt;li&gt;Test with concurrent requests, not just sequential ones.&lt;/li&gt;
  &lt;li&gt;Understand your database’s transaction isolation levels.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, always ask:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“What happens if two users do this at exactly the same time?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;





&lt;h1 id="final-thoughts"&gt;Final Thoughts&lt;/h1&gt;

&lt;p&gt;Race conditions aren’t caused by bad developers, they’re caused by systems becoming concurrent.&lt;/p&gt;

&lt;p&gt;As applications grow, users interact simultaneously, background jobs overlap, and services communicate in parallel. Timing becomes unpredictable.&lt;/p&gt;

&lt;p&gt;That’s why building reliable software isn’t just about writing correct logic for one request. It’s about ensuring your logic remains correct when hundreds or thousands of requests happen together.&lt;/p&gt;

&lt;p&gt;If idempotency protects you from duplicate requests, race condition handling protects you from competing requests.&lt;/p&gt;

&lt;p&gt;Together, they form two of the most important building blocks for designing resilient APIs and distributed systems.&lt;/p&gt;

&lt;p&gt;The next time you write code that reads, modifies, and writes shared data, pause for a moment and ask:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;“What happens if someone else does this at the exact same time?”&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you don’t know the answer, you’ve just found your next engineering problem to solve.&lt;/p&gt;

</description>
      <category>apidesign</category>
      <category>softwaretesting</category>
      <category>raceconditions</category>
    </item>
    <item>
      <title>Idempotency Explained: Building APIs That Survive Retries</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Thu, 25 Jun 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/idempotency-explained-building-apis-that-survive-retries-1hlk</link>
      <guid>https://dev.to/billy_de_cartel/idempotency-explained-building-apis-that-survive-retries-1hlk</guid>
      <description>&lt;p&gt;Imagine you're purchasing a product online.&lt;/p&gt;

&lt;p&gt;You click the &lt;strong&gt;"Pay Now"&lt;/strong&gt; button.&lt;/p&gt;

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

&lt;p&gt;After a few seconds, you assume the request failed, so you click the button again.&lt;/p&gt;

&lt;p&gt;And again.&lt;/p&gt;

&lt;p&gt;A few minutes later, you discover you've been charged three times.&lt;/p&gt;

&lt;p&gt;What happened?&lt;/p&gt;

&lt;p&gt;From the user's perspective, the payment seemed to fail. From the server's perspective, however, it successfully processed every request it received.&lt;/p&gt;

&lt;p&gt;This is one of the most common problems in distributed systems, and it's exactly why idempotency exists.&lt;/p&gt;

&lt;p&gt;Whether you're building payment systems, booking platforms, inventory management software, or any API that changes data, retries are inevitable. Networks fail, clients time out, mobile connections drop, and users double-click buttons.&lt;/p&gt;

&lt;p&gt;A well-designed API should survive these retries without creating duplicate side effects.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore what idempotency is, why it matters, and how to implement it in your own APIs.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Is Idempotency?
&lt;/h1&gt;

&lt;p&gt;In simple terms, &lt;strong&gt;an idempotent operation can be performed multiple times without changing the final result beyond the first successful execution.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn on the light.
Turn on the light again.
Turn on the light again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The light is still &lt;strong&gt;ON&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nothing new happened after the first request.&lt;/p&gt;

&lt;p&gt;The final state remains the same.&lt;/p&gt;

&lt;p&gt;That's idempotency.&lt;/p&gt;

&lt;p&gt;Now compare it with this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Deposit $100
Deposit $100
Deposit $100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your account balance increases by &lt;strong&gt;$300&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This operation is &lt;strong&gt;not idempotent&lt;/strong&gt; because every request changes the system.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Retries Happen
&lt;/h1&gt;

&lt;p&gt;Many developers assume users only send one request.&lt;/p&gt;

&lt;p&gt;Reality is different.&lt;/p&gt;

&lt;p&gt;Requests are retried because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow internet connections&lt;/li&gt;
&lt;li&gt;Gateway timeouts&lt;/li&gt;
&lt;li&gt;Reverse proxies&lt;/li&gt;
&lt;li&gt;Mobile network interruptions&lt;/li&gt;
&lt;li&gt;Browser refreshes&lt;/li&gt;
&lt;li&gt;Double-clicking buttons&lt;/li&gt;
&lt;li&gt;Client retry mechanisms&lt;/li&gt;
&lt;li&gt;Load balancers&lt;/li&gt;
&lt;li&gt;Microservice communication failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine this timeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client -------- POST /payments --------&amp;gt; API

             Payment succeeds

API -------- 200 OK --------X

(Response never reaches client)

Client waits...

Client retries.

POST /payments again.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The client believes the payment failed.&lt;/p&gt;

&lt;p&gt;The server already completed it.&lt;/p&gt;

&lt;p&gt;Without idempotency...&lt;/p&gt;

&lt;p&gt;The payment happens twice.&lt;/p&gt;




&lt;h1&gt;
  
  
  HTTP Methods and Idempotency
&lt;/h1&gt;

&lt;p&gt;HTTP itself distinguishes between idempotent and non-idempotent methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  GET
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users/10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the user.&lt;/p&gt;

&lt;p&gt;Call it once.&lt;/p&gt;

&lt;p&gt;Call it 100 times.&lt;/p&gt;

&lt;p&gt;Nothing changes.&lt;/p&gt;

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




&lt;h3&gt;
  
  
  PUT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;PUT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/users/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replacing the same resource repeatedly produces the same result.&lt;/p&gt;

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




&lt;h3&gt;
  
  
  DELETE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;DELETE /users/10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the user.&lt;/p&gt;

&lt;p&gt;Deleting an already deleted user doesn't delete them twice.&lt;/p&gt;

&lt;p&gt;The final state is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User does not exist.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h3&gt;
  
  
  POST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a new order.&lt;/p&gt;

&lt;p&gt;Call it twice.&lt;/p&gt;

&lt;p&gt;You now have two orders.&lt;/p&gt;

&lt;p&gt;Not idempotent&lt;/p&gt;

&lt;p&gt;This is why POST requests often require additional protection.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Payment APIs Use Idempotency Keys
&lt;/h1&gt;

&lt;p&gt;Payment providers like Stripe popularized the use of &lt;strong&gt;Idempotency Keys&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The idea is simple.&lt;/p&gt;

&lt;p&gt;The client generates a unique identifier.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every retry sends the same key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /payments

Idempotency-Key:
6ab89d3b-acde-4d71-b20d-483d8d0ef091
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server receives the request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check if this key already exists.&lt;/li&gt;
&lt;li&gt;If not, process the payment.&lt;/li&gt;
&lt;li&gt;Save both the key and the response.&lt;/li&gt;
&lt;li&gt;Return the response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If the same request arrives again with the same key:&lt;/p&gt;

&lt;p&gt;Instead of charging the customer again...&lt;/p&gt;

&lt;p&gt;Return the previously stored response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client

POST /payments
Key: ABC123

↓

Server

Charge customer

↓

Store

ABC123 → Payment #456

↓

Return success

---

Retry

POST /payments
Key: ABC123

↓

Lookup

ABC123 exists

↓

Return Payment #456

No second charge.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Implementing Idempotency
&lt;/h1&gt;

&lt;p&gt;A common workflow looks like this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1
&lt;/h2&gt;

&lt;p&gt;Receive request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /orders
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Idempotency-Key:
XYZ987
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2
&lt;/h2&gt;

&lt;p&gt;Search database.&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;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;idempotency_keys&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'XYZ987'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Found?&lt;/p&gt;

&lt;p&gt;Yes.&lt;/p&gt;

&lt;p&gt;Return stored response.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3
&lt;/h2&gt;

&lt;p&gt;Not found?&lt;/p&gt;

&lt;p&gt;Create the resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4
&lt;/h2&gt;

&lt;p&gt;Store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Key

Response

Status Code

Timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every retry returns the same response.&lt;/p&gt;




&lt;h1&gt;
  
  
  Example in Node.js (Express)
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&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="s2"&gt;/payments&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;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;existing&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;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&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;existing&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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&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;payment&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;processPayment&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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="na"&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="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payment&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;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;payment&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;h2&gt;
  
  
  Python (FastAPI)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.responses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONResponse&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/payments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Idempotency-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find_one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&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="n"&gt;payment&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;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;Idempotency&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment&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="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  C# (ASP.NET Core)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MapPost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/payments"&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="n"&gt;HttpRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IdempotencyStore&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;PaymentService&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;ToString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;FindAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&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="n"&gt;existing&lt;/span&gt; &lt;span class="k"&gt;is&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&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="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFromJsonAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentRequest&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ProcessAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;!);&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Results&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="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;statusCode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;StatusCodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status201Created&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;h2&gt;
  
  
  Go
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;idempotency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FindOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;existing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&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="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;PaymentRequest&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&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="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;idempotency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;IdempotencyRecord&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&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="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&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;h2&gt;
  
  
  Laravel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/payments'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Idempotency-Key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nv"&gt;$existing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Idempotency&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$key&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;first&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="nv"&gt;$existing&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="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$existing&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;Idempotency&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="s1"&gt;'key'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'status'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'response'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$payment&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="nf"&gt;response&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$payment&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logic is surprisingly simple.&lt;/p&gt;

&lt;p&gt;The complexity comes from storing and managing the keys correctly.&lt;/p&gt;




&lt;h1&gt;
  
  
  Where Should Idempotency Keys Be Stored?
&lt;/h1&gt;

&lt;p&gt;Options include:&lt;/p&gt;

&lt;h2&gt;
  
  
  Database
&lt;/h2&gt;

&lt;p&gt;Best for most applications.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent&lt;/li&gt;
&lt;li&gt;Reliable&lt;/li&gt;
&lt;li&gt;Easy to query&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slightly slower&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;Excellent for high-volume APIs.&lt;/p&gt;

&lt;p&gt;Pros:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast&lt;/li&gt;
&lt;li&gt;TTL support&lt;/li&gt;
&lt;li&gt;Easy expiration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many APIs automatically expire keys after 24 hours.&lt;/p&gt;




&lt;h2&gt;
  
  
  In-Memory
&lt;/h2&gt;

&lt;p&gt;Useful only during development.&lt;/p&gt;

&lt;p&gt;Not recommended for production.&lt;/p&gt;

&lt;p&gt;Restarting the server loses everything.&lt;/p&gt;




&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Reusing Keys
&lt;/h2&gt;

&lt;p&gt;Every logical operation should have its own unique key.&lt;/p&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ABC123

used today

used tomorrow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New checkout

↓

Generate new UUID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Ignoring Request Differences
&lt;/h2&gt;

&lt;p&gt;Suppose the first request is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The retry is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same key.&lt;/p&gt;

&lt;p&gt;Different body.&lt;/p&gt;

&lt;p&gt;The server should reject this request because the key is being reused for a different operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Never Expiring Keys
&lt;/h2&gt;

&lt;p&gt;Keeping millions of old keys forever wastes storage.&lt;/p&gt;

&lt;p&gt;Most APIs expire them after:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 hours&lt;/li&gt;
&lt;li&gt;48 hours&lt;/li&gt;
&lt;li&gt;7 days&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;depending on business requirements.&lt;/p&gt;




&lt;h1&gt;
  
  
  Real-World Use Cases
&lt;/h1&gt;

&lt;p&gt;Idempotency is valuable anywhere duplicate requests could have costly consequences.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Payment processing&lt;/li&gt;
&lt;li&gt;Bank transfers&lt;/li&gt;
&lt;li&gt;Order creation&lt;/li&gt;
&lt;li&gt;Hotel reservations&lt;/li&gt;
&lt;li&gt;Flight bookings&lt;/li&gt;
&lt;li&gt;Ticket purchases&lt;/li&gt;
&lt;li&gt;Subscription billing&lt;/li&gt;
&lt;li&gt;Inventory updates&lt;/li&gt;
&lt;li&gt;Webhook processing&lt;/li&gt;
&lt;li&gt;Email sending&lt;/li&gt;
&lt;li&gt;Message queues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If performing the same action twice could create an incorrect outcome, idempotency is worth considering.&lt;/p&gt;




&lt;h1&gt;
  
  
  When You Don't Need Idempotency
&lt;/h1&gt;

&lt;p&gt;Not every endpoint needs an idempotency key.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /posts
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No state changes.&lt;/p&gt;

&lt;p&gt;No duplicates.&lt;/p&gt;

&lt;p&gt;No problem.&lt;/p&gt;

&lt;p&gt;Likewise, endpoints such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Search&lt;/li&gt;
&lt;li&gt;Filtering&lt;/li&gt;
&lt;li&gt;Reading reports&lt;/li&gt;
&lt;li&gt;Viewing profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;are already naturally idempotent.&lt;/p&gt;

&lt;p&gt;Reserve idempotency mechanisms for operations where retries could create unintended side effects.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;Idempotency isn't just an implementation detail—it's a reliability feature.&lt;/p&gt;

&lt;p&gt;In distributed systems, retries are normal. Networks are unreliable, users click buttons more than once, and clients retry requests automatically. Instead of hoping those situations never happen, design your APIs to handle them gracefully.&lt;/p&gt;

&lt;p&gt;By using idempotency keys, storing responses, validating retries, and choosing the right storage strategy, you can prevent duplicate orders, repeated payments, and other costly errors.&lt;/p&gt;

&lt;p&gt;A resilient API isn't one that never receives duplicate requests.&lt;/p&gt;

&lt;p&gt;It's one that produces the correct outcome even when duplicate requests inevitably arrive.&lt;/p&gt;

&lt;p&gt;The next time you design a &lt;code&gt;POST&lt;/code&gt; endpoint, ask yourself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What happens if this request is sent twice?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is "something bad," it's probably time to add idempotency.&lt;/p&gt;

</description>
      <category>apidesign</category>
      <category>softwaretesting</category>
      <category>idempotency</category>
    </item>
    <item>
      <title>Debugging Is a Skill Nobody Teaches You</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/debugging-is-a-skill-nobody-teaches-you-1k2f</link>
      <guid>https://dev.to/billy_de_cartel/debugging-is-a-skill-nobody-teaches-you-1k2f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;You’ve been staring at the same bug for 2 hours.&lt;/em&gt;&lt;br&gt;
You’ve restarted the server. Cleared cache. Added random &lt;code&gt;console.log&lt;/code&gt;s.&lt;br&gt;
Somehow… it still doesn’t work.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At some point, you stop coding and start guessing.&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%2Fbti548ebmr81put20soo.gif" 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%2Fbti548ebmr81put20soo.gif" alt="Frustrated Coding" width="480" height="268"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And that’s the real problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The issue isn’t the bug.&lt;br&gt;
It’s that nobody actually teaches debugging as a skill.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Way Most Developers Debug
&lt;/h2&gt;

&lt;p&gt;Let’s be honest. Most of us learned debugging like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sprinkle &lt;code&gt;console.log&lt;/code&gt; everywhere&lt;/li&gt;
&lt;li&gt;Change random lines and hope something works&lt;/li&gt;
&lt;li&gt;Copy-paste error messages into Google&lt;/li&gt;
&lt;li&gt;Restart everything “just in case”&lt;/li&gt;
&lt;/ul&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%2Fapx6f29x7ibau69ccxs4.gif" 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%2Fapx6f29x7ibau69ccxs4.gif" alt="Random Typing" width="480" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;sometimes&lt;/em&gt; works.&lt;/p&gt;

&lt;p&gt;But it’s slow, frustrating, and unreliable.&lt;/p&gt;

&lt;p&gt;It’s not debugging.&lt;/p&gt;

&lt;p&gt;It’s &lt;strong&gt;trial and error disguised as progress&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Debugging Actually Is
&lt;/h2&gt;

&lt;p&gt;Here’s the mindset shift that changes everything:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Debugging is not about fixing code.&lt;br&gt;
It’s about finding where your mental model diverges from reality.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You &lt;em&gt;think&lt;/em&gt; the system works one way.&lt;/p&gt;

&lt;p&gt;Reality says otherwise.&lt;/p&gt;

&lt;p&gt;Your job is to &lt;strong&gt;close that gap&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Debugging Mindset
&lt;/h2&gt;

&lt;p&gt;Before tools, before techniques, this is what matters most.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Assume Your Assumptions Are Wrong
&lt;/h3&gt;

&lt;p&gt;If something doesn’t work, at least one thing you believe is false.&lt;/p&gt;

&lt;p&gt;Your job is to find it.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Narrow the Problem Space
&lt;/h3&gt;

&lt;p&gt;Bad debugging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Something is wrong with the app”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Good debugging:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The issue happens only when this function runs after login”&lt;/p&gt;
&lt;/blockquote&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%2Fax9kok46nh3vfbkqntku.gif" 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%2Fax9kok46nh3vfbkqntku.gif" alt="Analyzing Clues" width="480" height="304"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Reproduce Before Fixing
&lt;/h3&gt;

&lt;p&gt;If you can’t reliably reproduce the bug, you don’t understand it.&lt;/p&gt;

&lt;p&gt;And if you don’t understand it, your fix is luck—not skill.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. One Change at a Time
&lt;/h3&gt;

&lt;p&gt;If you change 5 things and it works…&lt;br&gt;
which one fixed it?&lt;/p&gt;

&lt;p&gt;You don’t know.&lt;/p&gt;

&lt;p&gt;That’s how bugs come back later.&lt;/p&gt;


&lt;h3&gt;
  
  
  5. Understand Before You Patch
&lt;/h3&gt;

&lt;p&gt;Quick fixes feel good.&lt;/p&gt;

&lt;p&gt;Understanding the root cause makes you dangerous (in a good way).&lt;/p&gt;


&lt;h2&gt;
  
  
  A Repeatable Debugging Process
&lt;/h2&gt;

&lt;p&gt;This is where things become practical.&lt;/p&gt;


&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Reproduce the Bug&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Make it happen consistently.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Click button → error appears  
Refresh → still happens  
Different browser → still happens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it’s inconsistent, your first task is to &lt;strong&gt;find the pattern&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Define Expected vs Actual&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Write it down clearly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected: API returns user data  
Actual: API returns empty array
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This step alone eliminates confusion.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Isolate the Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Shrink the scope.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Comment out unrelated code&lt;/li&gt;
&lt;li&gt;Remove layers (UI → API → DB)&lt;/li&gt;
&lt;li&gt;Test pieces independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ UI ] → [ API ] → [ Database ]

Which layer is lying?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Form a Hypothesis&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Be explicit:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“I think the API is returning empty data because the query filter is wrong.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you’re not guessing—you’re &lt;strong&gt;testing a theory&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 5: Test the Hypothesis&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Use targeted tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs&lt;/li&gt;
&lt;li&gt;Breakpoints&lt;/li&gt;
&lt;li&gt;Network inspector&lt;/li&gt;
&lt;/ul&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%2Fgagro6i73fjmz1sg5umq.gif" 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%2Fgagro6i73fjmz1sg5umq.gif" alt="Experimenting" width="200" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Example:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User ID:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But intentional—not random.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 6: Fix and Verify&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Fix it.&lt;/p&gt;

&lt;p&gt;Then confirm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does it work in all cases?&lt;/li&gt;
&lt;li&gt;Did you break something else?&lt;/li&gt;
&lt;/ul&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%2Flpc2x3xnxsvwvnka1lm2.gif" 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%2Flpc2x3xnxsvwvnka1lm2.gif" alt="Calm Focus" width="350" height="252"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Step 7: Understand the Root Cause&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is where most devs stop too early.&lt;/p&gt;

&lt;p&gt;Don’t just fix it—&lt;strong&gt;explain it&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“The bug happened because the state updated asynchronously, and we read it too early.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now you’ve learned something reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Example: “The API Is Broken” (But It’s Not)
&lt;/h2&gt;

&lt;p&gt;Let’s walk through a real scenario.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Bug
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Frontend shows: &lt;strong&gt;No data available&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Initial Assumption
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;“The API is broken.”&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 1: Check Network Tab
&lt;/h3&gt;

&lt;p&gt;You open DevTools → Network:&lt;/p&gt;

&lt;p&gt;API returns correct data&lt;/p&gt;

&lt;p&gt;So… not the API.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 2: Check State
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It logs:&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="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty array.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 3: Trace the Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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="nf"&gt;fetchData&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;Inside &lt;code&gt;fetchData&lt;/code&gt;:&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="nf"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="c1"&gt;// still empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;React state updates are &lt;strong&gt;asynchronous&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You’re logging &lt;strong&gt;before state updates&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&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="nf"&gt;fetchData&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="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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&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%2Fr8w01awu42tbaz2qql4w.gif" alt="Victory" width="260" height="195"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Lesson
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;The bug wasn’t in the API.&lt;br&gt;
It was in your mental model of how state updates work.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Tools That Actually Help
&lt;/h2&gt;

&lt;p&gt;Not everything is about tools—but the right ones matter.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Browser DevTools (Underrated Powerhouse)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network tab&lt;/strong&gt; → verify API calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Console&lt;/strong&gt; → inspect runtime values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application tab&lt;/strong&gt; → check storage&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Breakpoints (Game Changer)
&lt;/h3&gt;

&lt;p&gt;Instead of spamming logs:&lt;/p&gt;

&lt;p&gt;Pause execution and inspect state &lt;em&gt;live&lt;/em&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Intentional Logging
&lt;/h3&gt;

&lt;p&gt;Bad:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;here&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User after login:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  4. Stack Traces
&lt;/h3&gt;

&lt;p&gt;Read them.&lt;/p&gt;

&lt;p&gt;They literally tell you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where the error happened&lt;/li&gt;
&lt;li&gt;What triggered it&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. Rubber Duck Debugging
&lt;/h3&gt;

&lt;p&gt;Explain the bug out loud.&lt;/p&gt;

&lt;p&gt;Yes, seriously.&lt;/p&gt;

&lt;p&gt;You’ll often solve it mid-explanation.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Problem → Hypothesis → Test → Learn → Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Common Debugging Traps
&lt;/h2&gt;

&lt;p&gt;Avoid these and you’ll already be ahead of most devs:&lt;/p&gt;




&lt;h3&gt;
  
  
  Fixing Symptoms Instead of Causes
&lt;/h3&gt;

&lt;p&gt;You silence the error… but the bug is still there.&lt;/p&gt;




&lt;h3&gt;
  
  
  Changing Too Many Things at Once
&lt;/h3&gt;

&lt;p&gt;Now you don’t know what worked.&lt;/p&gt;




&lt;h3&gt;
  
  
  Ignoring Error Messages
&lt;/h3&gt;

&lt;p&gt;The error is literally telling you what’s wrong.&lt;/p&gt;

&lt;p&gt;Read it.&lt;/p&gt;




&lt;h3&gt;
  
  
  Assuming the Bug Is “Weird”
&lt;/h3&gt;

&lt;p&gt;It’s almost never weird.&lt;/p&gt;

&lt;p&gt;It’s misunderstood.&lt;/p&gt;




&lt;h3&gt;
  
  
  “It Works on My Machine”
&lt;/h3&gt;

&lt;p&gt;This is not a flex.&lt;/p&gt;

&lt;p&gt;It’s a clue.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Better Mental Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expectation ≠ Reality  
        ↓  
Investigate the gap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s debugging.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The best developers aren’t the ones who write perfect code.&lt;br&gt;
They’re the ones who can &lt;strong&gt;quickly understand why things break&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Debugging isn’t a side skill.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; the job.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>career</category>
      <category>debugging</category>
    </item>
    <item>
      <title>Stripe Connect on Accounts v2 — Standard, Express, and Custom, Rebuilt Around Configurations (with HTTP, Python, C#, and PHP)</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/stripe-connect-on-accounts-v2-standard-express-and-custom-rebuilt-around-configurations-with-2ab0</link>
      <guid>https://dev.to/billy_de_cartel/stripe-connect-on-accounts-v2-standard-express-and-custom-rebuilt-around-configurations-with-2ab0</guid>
      <description>&lt;p&gt;This is the &lt;strong&gt;Accounts v2&lt;/strong&gt; companion to the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original Connect guide (Accounts v1)&lt;/a&gt;&lt;/strong&gt;. Same &lt;strong&gt;platform concepts&lt;/strong&gt;—Standard, Express, Custom, money flow, compliance—but the &lt;strong&gt;API shape&lt;/strong&gt; is the one Stripe describes in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Connect and the Accounts v2 API&lt;/a&gt;&lt;/strong&gt;. Use the older post when you must stay on &lt;strong&gt;v1&lt;/strong&gt; (&lt;code&gt;Account.create&lt;/code&gt; with &lt;code&gt;type=express&lt;/code&gt;, OAuth-only Standard flows, etc.). Use &lt;strong&gt;this&lt;/strong&gt; post when you are designing around &lt;strong&gt;one &lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;configurations&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;customer_account&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads-up:&lt;/strong&gt; v2 uses &lt;strong&gt;different endpoints and JSON&lt;/strong&gt; than classic &lt;code&gt;Accounts&lt;/code&gt; CRUD. Always confirm &lt;strong&gt;API version&lt;/strong&gt;, &lt;strong&gt;preview flags&lt;/strong&gt;, and &lt;strong&gt;SDK support&lt;/strong&gt; in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Stripe’s documentation&lt;/a&gt;&lt;/strong&gt; before shipping production code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What is Stripe Connect?
&lt;/h2&gt;

&lt;p&gt;Stripe Connect is for &lt;strong&gt;platforms&lt;/strong&gt; that move money between &lt;strong&gt;buyers&lt;/strong&gt;, &lt;strong&gt;sellers&lt;/strong&gt;, and &lt;strong&gt;your platform&lt;/strong&gt;. You connect &lt;strong&gt;connected accounts&lt;/strong&gt; to your &lt;strong&gt;platform account&lt;/strong&gt; so you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect payments from customers&lt;/li&gt;
&lt;li&gt;Pay out to sellers or service providers&lt;/li&gt;
&lt;li&gt;Take &lt;strong&gt;application fees&lt;/strong&gt; where appropriate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stripe holds the &lt;strong&gt;payments&lt;/strong&gt; rail; you own &lt;strong&gt;product and UX&lt;/strong&gt; choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two ideas at once: “style” (Standard / Express / Custom) and “shape” (v2 configurations)
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original guide&lt;/a&gt;&lt;/strong&gt; compares &lt;strong&gt;Standard&lt;/strong&gt;, &lt;strong&gt;Express&lt;/strong&gt;, and &lt;strong&gt;Custom&lt;/strong&gt; as &lt;strong&gt;who owns onboarding, dashboards, and compliance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts v2&lt;/strong&gt; adds a separate axis: &lt;strong&gt;what capabilities&lt;/strong&gt; a single &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; has, expressed as &lt;strong&gt;configurations&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Configuration (v2)&lt;/th&gt;
&lt;th&gt;Role in plain language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Accept card payments, get paid out—what you usually wanted from a*&lt;em&gt;connected account&lt;/em&gt;* selling on your platform.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Be*&lt;em&gt;billed like a customer&lt;/em&gt;* (subscriptions, invoices to your platform) using the &lt;strong&gt;same&lt;/strong&gt; Account identity—often replacing a separate &lt;strong&gt;Customer&lt;/strong&gt; object for that business.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Receive*&lt;em&gt;transfers&lt;/em&gt;* (e.g. indirect charges), using the v2 &lt;strong&gt;transfer&lt;/strong&gt; capabilities Stripe documents for recipients.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can assign &lt;strong&gt;one or more&lt;/strong&gt; configurations to the &lt;strong&gt;same&lt;/strong&gt; &lt;code&gt;Account&lt;/code&gt;. That is the core promise of v2: &lt;strong&gt;one identity&lt;/strong&gt;, &lt;strong&gt;multiple roles&lt;/strong&gt;, &lt;strong&gt;less manual ID mapping&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt; choice is still &lt;strong&gt;real&lt;/strong&gt;: it describes &lt;strong&gt;OAuth vs Stripe-hosted onboarding vs fully custom UI&lt;/strong&gt;. On v2 you implement those &lt;strong&gt;experiences&lt;/strong&gt; while creating and updating &lt;strong&gt;Accounts&lt;/strong&gt; through the &lt;strong&gt;v2&lt;/strong&gt; surface (where supported).&lt;/p&gt;

&lt;h2&gt;
  
  
  Standard vs Express vs Custom (unchanged product trade-offs)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature / aspect&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Who owns the Stripe relationship&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The user’s*&lt;em&gt;existing&lt;/em&gt;* Stripe user; you connect via &lt;strong&gt;OAuth&lt;/strong&gt;.&lt;/td&gt;
&lt;td&gt;You create*&lt;em&gt;connected accounts&lt;/em&gt;&lt;em&gt;; Stripe hosts **onboarding&lt;/em&gt;* and a &lt;strong&gt;light&lt;/strong&gt; dashboard.&lt;/td&gt;
&lt;td&gt;You own*&lt;em&gt;all&lt;/em&gt;* UX; Stripe is invisible to end users.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;KYC / onboarding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User uses*&lt;em&gt;Stripe Dashboard&lt;/em&gt;*.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Stripe-hosted&lt;/strong&gt; onboarding (Account Links, etc., per current docs).&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;You&lt;/strong&gt; collect data and satisfy Stripe requirements via API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dashboard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full*&lt;em&gt;Stripe&lt;/em&gt;* dashboard for the user.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Express&lt;/strong&gt; dashboard.&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;None&lt;/strong&gt; unless you build it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best when&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sellers*&lt;em&gt;already&lt;/em&gt;* have Stripe.&lt;/td&gt;
&lt;td&gt;You need*&lt;em&gt;speed&lt;/em&gt;* and shared compliance.&lt;/td&gt;
&lt;td&gt;You need*&lt;em&gt;full&lt;/em&gt;* branding and control.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On &lt;strong&gt;v2&lt;/strong&gt;, you still make these &lt;strong&gt;product&lt;/strong&gt; choices—but you attach &lt;strong&gt;merchant&lt;/strong&gt; / &lt;strong&gt;customer&lt;/strong&gt; / &lt;strong&gt;recipient&lt;/strong&gt; &lt;strong&gt;configurations&lt;/strong&gt; to match what each connected business must do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architectural overview
&lt;/h2&gt;

&lt;p&gt;Fund flow is unchanged at a high level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  C[Customer] --&amp;gt; P[Your platform]
  P --&amp;gt; A[Connected Account]
  A --&amp;gt; B[Bank payout]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changes in v2&lt;/strong&gt; is &lt;strong&gt;object modeling&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; can represent &lt;strong&gt;both&lt;/strong&gt; “seller” and “buyer of your SaaS” if you add &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configurations.&lt;/li&gt;
&lt;li&gt;APIs that took &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; may accept &lt;strong&gt;&lt;code&gt;customer_account&lt;/code&gt;&lt;/strong&gt; with an &lt;strong&gt;Account&lt;/strong&gt; ID—see &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accounts v2 primitives (before code)
&lt;/h2&gt;

&lt;p&gt;Stripe’s v2 &lt;strong&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/strong&gt; objects differ from v1’s flat &lt;code&gt;Account&lt;/code&gt; create. You typically send:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; — country, entity type, business details.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;configuration&lt;/code&gt;&lt;/strong&gt; — nested blocks such as &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt;, each with &lt;strong&gt;capabilities&lt;/strong&gt; you request (&lt;code&gt;card_payments&lt;/code&gt;, balance / payout capabilities as named in current docs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;defaults&lt;/code&gt;&lt;/strong&gt; — currency, locales, responsibilities (fees / losses collector), etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;include&lt;/code&gt;&lt;/strong&gt; — ask the API to return nested sections (e.g. &lt;code&gt;configuration.merchant&lt;/code&gt;, &lt;code&gt;requirements&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Responses may &lt;strong&gt;omit&lt;/strong&gt; fields unless you &lt;strong&gt;&lt;code&gt;include&lt;/code&gt;&lt;/strong&gt; them—see Stripe’s note on &lt;strong&gt;&lt;a href="https://docs.stripe.com/api-includable-response-values" rel="noopener noreferrer"&gt;includable response values&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API version:&lt;/strong&gt; v2 calls often require a specific &lt;strong&gt;&lt;code&gt;Stripe-Version&lt;/code&gt;&lt;/strong&gt; header (including &lt;strong&gt;preview&lt;/strong&gt; versions while the API evolves). Set this from the &lt;strong&gt;exact&lt;/strong&gt; value in &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Stripe’s Accounts v2 docs&lt;/a&gt;&lt;/strong&gt; for your integration window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation: creating an Account on v2 (HTTP-first)
&lt;/h2&gt;

&lt;p&gt;Official examples are &lt;strong&gt;REST + JSON&lt;/strong&gt;. Below is &lt;strong&gt;illustrative&lt;/strong&gt;—copy &lt;strong&gt;field names&lt;/strong&gt;, &lt;strong&gt;capability keys&lt;/strong&gt;, and &lt;strong&gt;version&lt;/strong&gt; from the current docs, not from this blog alone.&lt;/p&gt;

&lt;h3&gt;
  
  
  cURL (canonical pattern from Stripe)
&lt;/h3&gt;

&lt;p&gt;Stripe documents &lt;strong&gt;&lt;code&gt;POST /v2/core/accounts&lt;/code&gt;&lt;/strong&gt; with a JSON body. Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.stripe.com/v2/core/accounts &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_test_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Stripe-Version: &amp;lt;version-from-stripe-docs&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "contact_email": "seller@example.com",
    "display_name": "Example Seller",
    "identity": {
      "country": "us",
      "entity_type": "company",
      "business_details": { "registered_name": "Example Co" }
    },
    "configuration": {
      "merchant": {
        "capabilities": {
          "card_payments": { "requested": true }
        }
      },
      "customer": {
        "capabilities": {
          "automatic_indirect_tax": { "requested": true }
        }
      }
    },
    "defaults": {
      "currency": "usd",
      "responsibilities": {
        "fees_collector": "stripe",
        "losses_collector": "stripe"
      },
      "locales": ["en-US"]
    },
    "include": [
      "configuration.customer",
      "configuration.merchant",
      "identity",
      "requirements"
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows the &lt;strong&gt;mental model&lt;/strong&gt;: &lt;strong&gt;one&lt;/strong&gt; create, &lt;strong&gt;multiple&lt;/strong&gt; configurations, &lt;strong&gt;explicit&lt;/strong&gt; includes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python (generic HTTP client)
&lt;/h3&gt;

&lt;p&gt;Until your &lt;strong&gt;&lt;code&gt;stripe&lt;/code&gt;&lt;/strong&gt; SDK exposes stable helpers for every v2 path, &lt;strong&gt;&lt;code&gt;httpx&lt;/code&gt;&lt;/strong&gt; or &lt;strong&gt;&lt;code&gt;requests&lt;/code&gt;&lt;/strong&gt; keeps you aligned with the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_account_v2&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.stripe.com/v2/core/accounts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Stripe-Version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;version-from-stripe-docs&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;# ...same structure as the cURL example...
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP (Laravel-style)
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;Guzzle&lt;/strong&gt; or Laravel &lt;strong&gt;&lt;code&gt;Http::withHeaders([...])-&amp;gt;post(...)&lt;/code&gt;&lt;/strong&gt; with the same URL, &lt;strong&gt;&lt;code&gt;Stripe-Version&lt;/code&gt;&lt;/strong&gt;, and JSON body. Keep &lt;strong&gt;secrets&lt;/strong&gt; in &lt;strong&gt;config&lt;/strong&gt;, not in source control.&lt;/p&gt;

&lt;h3&gt;
  
  
  C# (.NET)
&lt;/h3&gt;

&lt;p&gt;Use &lt;strong&gt;&lt;code&gt;HttpClient&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;StringContent(json, Encoding.UTF8, "application/json")&lt;/code&gt;&lt;/strong&gt; and the same headers. Deserialize the JSON response into your own DTOs that track &lt;strong&gt;&lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;requirements&lt;/code&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;code&gt;configuration.*&lt;/code&gt;&lt;/strong&gt; state.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;OAuth / Standard accounts:&lt;/strong&gt; Stripe currently directs platforms that authenticate with &lt;strong&gt;OAuth&lt;/strong&gt; to connected accounts to &lt;strong&gt;continue using v1&lt;/strong&gt; for that path. Treat &lt;strong&gt;Standard&lt;/strong&gt; as &lt;strong&gt;documented in the &lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 guide&lt;/a&gt;&lt;/strong&gt; until your OAuth + v2 story is explicitly supported for your use case—see &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Accounts v2&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://docs.stripe.com/stripe-apps/api-authentication/oauth" rel="noopener noreferrer"&gt;OAuth&lt;/a&gt;&lt;/strong&gt;.&lt;br&gt;
{: .prompt-tip }&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Onboarding links (Express-style flows)
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;Express-like&lt;/strong&gt; experiences you still send users through &lt;strong&gt;Stripe-hosted onboarding&lt;/strong&gt; where the product allows it. With a &lt;strong&gt;connected account ID&lt;/strong&gt; returned from v2 (&lt;code&gt;acct_...&lt;/code&gt;), &lt;strong&gt;&lt;code&gt;Account Links&lt;/code&gt;&lt;/strong&gt; (v1 resource) remain the usual tool for &lt;strong&gt;&lt;code&gt;account_onboarding&lt;/code&gt;&lt;/strong&gt;—the same pattern as the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 article&lt;/a&gt;&lt;/strong&gt;, but the &lt;strong&gt;&lt;code&gt;account&lt;/code&gt;&lt;/strong&gt; value may come from a &lt;strong&gt;v2&lt;/strong&gt; create. Verify compatibility for your &lt;strong&gt;API version&lt;/strong&gt; in Stripe’s docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python (v1 Account Links API, account id from v2 create):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;
&lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;STRIPE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AccountLink&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;connected_account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;refresh_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/reauth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;return_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yourapp.com/complete&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;account_onboarding&lt;/span&gt;&lt;span class="sh"&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;h2&gt;
  
  
  Custom-style integrations on v2
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Custom&lt;/strong&gt; still means: &lt;strong&gt;you&lt;/strong&gt; own KYC collection, ToS acceptance, and ongoing verification. On v2 you express that by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending &lt;strong&gt;complete &lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; data the API requires.&lt;/li&gt;
&lt;li&gt;Requesting &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; / &lt;strong&gt;&lt;code&gt;recipient&lt;/code&gt;&lt;/strong&gt; capabilities your product needs.&lt;/li&gt;
&lt;li&gt;Handling &lt;strong&gt;&lt;code&gt;requirements&lt;/code&gt;&lt;/strong&gt; and &lt;strong&gt;webhooks&lt;/strong&gt; the same way you would for Custom on v1—only the &lt;strong&gt;payload shape&lt;/strong&gt; differs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You may still use &lt;strong&gt;&lt;code&gt;tos_acceptance&lt;/code&gt;&lt;/strong&gt;-style fields where the v2 schema maps them; follow &lt;strong&gt;Stripe’s v2 reference&lt;/strong&gt; for exact property names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using Accounts as customers (&lt;code&gt;customer_account&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Where you used &lt;strong&gt;&lt;code&gt;customer=cus_...&lt;/code&gt;&lt;/strong&gt;, many flows accept &lt;strong&gt;&lt;code&gt;customer_account=acct_...&lt;/code&gt;&lt;/strong&gt; for an Account that has the &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configuration. Example pattern from Stripe (conceptual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.stripe.com/v1/setup_intents &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"sk_test_...:"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Stripe-Version: &amp;lt;version-from-stripe-docs&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;customer_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;acct_123 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"payment_method_types[]=card"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;confirm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;usage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;off_session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Details and supported objects live under &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking balances
&lt;/h2&gt;

&lt;p&gt;For many &lt;strong&gt;Connect&lt;/strong&gt; operations, &lt;strong&gt;connected account&lt;/strong&gt; scoping is unchanged. &lt;strong&gt;Python:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stripe_account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;PHP:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;\Stripe\Balance&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'stripe_account'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$accountId&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;C#:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;stripe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Balance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BalanceGetOptions&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;RequestOptions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;StripeAccount&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accountId&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;Confirm in Stripe’s docs whether your &lt;strong&gt;v2&lt;/strong&gt; account IDs behave identically for every &lt;strong&gt;Balance&lt;/strong&gt; and &lt;strong&gt;v1&lt;/strong&gt; helper you rely on during migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compliance responsibilities
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt; compliance split from the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;original article&lt;/a&gt;&lt;/strong&gt; still applies &lt;strong&gt;who&lt;/strong&gt; collects KYC and &lt;strong&gt;who&lt;/strong&gt; owns disputes. &lt;strong&gt;v2&lt;/strong&gt; can &lt;strong&gt;reduce duplicate&lt;/strong&gt; identity collection when you &lt;strong&gt;add&lt;/strong&gt; configurations to an &lt;strong&gt;existing&lt;/strong&gt; Account instead of opening a second &lt;strong&gt;Customer&lt;/strong&gt; record.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Standard&lt;/th&gt;
&lt;th&gt;Express&lt;/th&gt;
&lt;th&gt;Custom&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KYC&lt;/td&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;Mostly Stripe&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tax reporting&lt;/td&gt;
&lt;td&gt;Stripe-heavy&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Often you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PCI&lt;/td&gt;
&lt;td&gt;Stripe-hosted elements&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Mostly you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disputes&lt;/td&gt;
&lt;td&gt;Stripe-heavy&lt;/td&gt;
&lt;td&gt;Shared&lt;/td&gt;
&lt;td&gt;Often you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Choosing a path
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Style to favor&lt;/th&gt;
&lt;th&gt;v2 angle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sellers already on Stripe; OAuth&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Standard&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Often*&lt;em&gt;v1 OAuth&lt;/em&gt;* until Stripe supports your OAuth + v2 plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast marketplace onboarding&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Express&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;v2&lt;/strong&gt; &lt;code&gt;Account&lt;/code&gt; + &lt;strong&gt;&lt;code&gt;merchant&lt;/code&gt;&lt;/strong&gt; + Account Links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;White-label, embedded finance&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Custom&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;v2&lt;/strong&gt; full &lt;strong&gt;&lt;code&gt;identity&lt;/code&gt;&lt;/strong&gt; + capabilities + your UI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same business pays you*and* sells on your platform&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Express&lt;/strong&gt; or &lt;strong&gt;Custom&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Same*&lt;em&gt;&lt;code&gt;Account&lt;/code&gt;&lt;/em&gt;&lt;em&gt;, *&lt;/em&gt;&lt;code&gt;merchant&lt;/code&gt;** + &lt;strong&gt;&lt;code&gt;customer&lt;/code&gt;&lt;/strong&gt; configurations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Strategic considerations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time-to-market:&lt;/strong&gt; Standard (when OAuth fits) &amp;lt; Express &amp;lt; Custom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API surface:&lt;/strong&gt; v2 adds &lt;strong&gt;configuration&lt;/strong&gt; discipline—plan for &lt;strong&gt;migration&lt;/strong&gt; from v1, not an eternal split (Stripe &lt;strong&gt;discourages&lt;/strong&gt; maintaining both versions simultaneously). - &lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Reference&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDKs:&lt;/strong&gt; Expect to use &lt;strong&gt;HTTP&lt;/strong&gt; for some &lt;strong&gt;v2&lt;/strong&gt; paths until your language SDK is fully aligned.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Accounts v2&lt;/strong&gt; does not erase &lt;strong&gt;Standard / Express / Custom&lt;/strong&gt;—it &lt;strong&gt;repackages&lt;/strong&gt; how you &lt;strong&gt;represent&lt;/strong&gt; connected users in the API. Start from &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/accounts-v2" rel="noopener noreferrer"&gt;Connect and the Accounts v2 API&lt;/a&gt;&lt;/strong&gt;, add &lt;strong&gt;&lt;a href="https://docs.stripe.com/connect/use-accounts-as-customers" rel="noopener noreferrer"&gt;using Accounts as customers&lt;/a&gt;&lt;/strong&gt; when the same legal entity both &lt;strong&gt;sells&lt;/strong&gt; and &lt;strong&gt;buys&lt;/strong&gt; from your platform, and keep the &lt;strong&gt;&lt;a href="https://dev.to/posts/integrating-to-stripe/"&gt;v1 Connect guide&lt;/a&gt;&lt;/strong&gt; handy for &lt;strong&gt;OAuth&lt;/strong&gt; flows and &lt;strong&gt;legacy&lt;/strong&gt; snippets until you have fully moved.&lt;/p&gt;

&lt;p&gt;Either way, you still trade off &lt;strong&gt;control&lt;/strong&gt;, &lt;strong&gt;compliance&lt;/strong&gt;, and &lt;strong&gt;complexity&lt;/strong&gt;—only the &lt;strong&gt;object model&lt;/strong&gt; got a long-overdue upgrade.&lt;/p&gt;

</description>
      <category>paymentprocessing</category>
      <category>softwaredevelopment</category>
      <category>stripe</category>
    </item>
    <item>
      <title>Contract Testing: Prevent Breaking Changes Before Production</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Thu, 19 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/contract-testing-prevent-breaking-changes-before-production-2f44</link>
      <guid>https://dev.to/billy_de_cartel/contract-testing-prevent-breaking-changes-before-production-2f44</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“It worked locally. Tests passed. But production still broke.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you’re building distributed systems with multiple services and frontends, you’ve likely encountered this (whether using &lt;strong&gt;.NET + Angular&lt;/strong&gt; , &lt;strong&gt;Node.js + React&lt;/strong&gt; , &lt;strong&gt;Python + Vue&lt;/strong&gt; , or any combination):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backend change gets deployed&lt;/li&gt;
&lt;li&gt;The frontend suddenly breaks&lt;/li&gt;
&lt;li&gt;No tests warned you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The issue isn’t always logic.&lt;/p&gt;

&lt;p&gt;It’s often a &lt;strong&gt;broken contract between systems&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Silent API Breakages
&lt;/h2&gt;

&lt;p&gt;In a typical setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service Provider: An API or microservice&lt;/li&gt;
&lt;li&gt;Service Consumer: A frontend, app, or another service&lt;/li&gt;
&lt;li&gt;Communication: JSON over HTTP (or any protocol)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything depends on one thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The consumer and provider agreeing on &lt;strong&gt;what data looks like&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Let’s use a &lt;strong&gt;.NET API&lt;/strong&gt; and &lt;strong&gt;Angular frontend&lt;/strong&gt; as an example (though this applies to any tech stack).&lt;/p&gt;

&lt;h3&gt;
  
  
  API Provider (Initial Version)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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="nf"&gt;HttpGet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{id}"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;IActionResult&lt;/span&gt; &lt;span class="nf"&gt;GetUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;id&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="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;UserDto&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Billy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"billy@example.com"&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;h3&gt;
  
  
  Consumer Interface (Angular Example)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;Everything works perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then a “Small” Change Happens
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserDto&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// renamed&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;h3&gt;
  
  
  Production Result
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;// undefined&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI breaks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Didn’t Traditional Tests Catch This?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unit tests → passed (backend logic is fine)&lt;/li&gt;
&lt;li&gt;Integration tests → passed (they used the old model)&lt;/li&gt;
&lt;li&gt;The API still returns valid JSON&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the &lt;strong&gt;contract between systems changed&lt;/strong&gt; —and traditional tests don’t verify that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Contract Testing?
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Contract testing ensures that your service provider always matches what the consumer expects.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A &lt;strong&gt;contract&lt;/strong&gt; defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request format&lt;/li&gt;
&lt;li&gt;Response structure&lt;/li&gt;
&lt;li&gt;Required fields&lt;/li&gt;
&lt;li&gt;Data types&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  In Simple Terms
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;consumer&lt;/strong&gt; (frontend, app, or service) defines expectations The &lt;strong&gt;provider&lt;/strong&gt; (API or service) must satisfy them&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Consumer vs Provider
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Consumer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Calls the service/API&lt;/li&gt;
&lt;li&gt;Defines expected structure&lt;/li&gt;
&lt;li&gt;Examples: Angular frontend, React app, mobile app, another microservice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Provider
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Returns the data&lt;/li&gt;
&lt;li&gt;Must not break expectations&lt;/li&gt;
&lt;li&gt;Examples: .NET API, Node.js backend, Python service, GraphQL endpoint&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  How Contract Testing Works
&lt;/h1&gt;

&lt;p&gt;Instead of relying only on integration tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Angular defines expectations&lt;/li&gt;
&lt;li&gt;A contract file is generated&lt;/li&gt;
&lt;li&gt;.NET verifies the contract&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Flow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Consumer Test (e.g., Angular)
      ↓
Generates Contract
      ↓
Saved as JSON/YAML
      ↓
Provider verifies against contract (e.g., .NET API)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Practical Example
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;(.NET backend + Angular frontend as an example)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Define Expectations in Consumer (Angular example)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedUser&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Billy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;billy@example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;should fetch user correctly&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="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;user&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;userService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedUser&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;h2&gt;
  
  
  Generated Contract (Simplified)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"request"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/users/1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"billy@example.com"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 2: Verify in Provider (.NET API example)
&lt;/h2&gt;

&lt;p&gt;Install Pact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package PactNet

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Provider Test
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Fact&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;VerifyPact&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pactVerifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PactVerifier&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;pactVerifier&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ServiceProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"UserApi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:5000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithFileSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;FileInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pacts/userapi-angular.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;Verify&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;h2&gt;
  
  
  If Provider Breaks the Contract
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;
&lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FullName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&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;ul&gt;
&lt;li&gt;&lt;p&gt;The test fails immediately&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You catch the issue before deployment&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Where Contract Testing Fits
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E2E Tests
(user journeys)

Integration Tests
(service interactions)

Contract Tests 
(API agreements)

Unit Tests
(business logic)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Why This Matters in Real Projects
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Safer Refactoring
&lt;/h2&gt;

&lt;p&gt;Change DTOs, schema, or API responses without fear. Contract tests verify nothing broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Independent Development
&lt;/h2&gt;

&lt;p&gt;Consumer and provider teams move faster. Changes are caught instantly, not in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster Debugging
&lt;/h2&gt;

&lt;p&gt;Failures clearly show what broke&lt;/p&gt;

&lt;h2&gt;
  
  
  Stronger CI/CD Pipelines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Consumer Build → Generate Contract
Provider Build → Verify Contract
Deploy → Only if both pass

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works with any tech stack.&lt;/p&gt;

&lt;h1&gt;
  
  
  Common Mistakes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Over-Specifying Data
&lt;/h2&gt;

&lt;p&gt;Bad:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Billy Okeyo"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Everything
&lt;/h2&gt;

&lt;p&gt;Only validate fields your frontend actually uses&lt;/p&gt;

&lt;h2&gt;
  
  
  Ignoring Versioning
&lt;/h2&gt;

&lt;p&gt;Breaking contracts without versioning leads to production issues&lt;/p&gt;

&lt;h1&gt;
  
  
  Best Practices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Keep Contracts Minimal
&lt;/h2&gt;

&lt;p&gt;Focus only on required fields&lt;/p&gt;

&lt;h2&gt;
  
  
  Version Your API
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/api/v1/users
/api/v2/users

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Automate in CI/CD
&lt;/h2&gt;

&lt;p&gt;Contracts should be generated and verified automatically&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Realistic Data
&lt;/h2&gt;

&lt;p&gt;Avoid unrealistic mocks&lt;/p&gt;

&lt;h1&gt;
  
  
  Final Takeaway
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Unit tests verify logic Integration tests verify systems E2E tests verify user flows&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract tests verify agreements&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;“Most production bugs aren’t failures… they’re misunderstandings between systems.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Contract testing eliminates those misunderstandings.&lt;/p&gt;

</description>
      <category>contracttesting</category>
      <category>apitesting</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Why Your Django App Needs Redis and Celery in Production</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/why-your-django-app-needs-redis-and-celery-in-production-4jeg</link>
      <guid>https://dev.to/billy_de_cartel/why-your-django-app-needs-redis-and-celery-in-production-4jeg</guid>
      <description>&lt;p&gt;Django is an incredibly powerful framework for building web applications quickly. However, as your application grows, certain tasks begin to slow down request-response cycles.&lt;/p&gt;

&lt;p&gt;Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sending emails&lt;/li&gt;
&lt;li&gt;Generating reports&lt;/li&gt;
&lt;li&gt;Processing uploaded files&lt;/li&gt;
&lt;li&gt;Running background analytics&lt;/li&gt;
&lt;li&gt;Sending notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running these tasks during an HTTP request can make your application slow and unreliable.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Celery and Redis&lt;/strong&gt; come in.&lt;/p&gt;

&lt;p&gt;Together they allow you to run background jobs asynchronously without blocking your main application.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem with Synchronous Tasks
&lt;/h2&gt;

&lt;p&gt;Imagine a user submits a request that triggers an operation that takes &lt;strong&gt;10 seconds&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating a financial report&lt;/li&gt;
&lt;li&gt;Parsing a large document&lt;/li&gt;
&lt;li&gt;Sending multiple emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your application processes this synchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1

User Request → Django → Long Task → Response

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user waits for the entire process to finish.&lt;/p&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow responses&lt;/li&gt;
&lt;li&gt;Poor user experience&lt;/li&gt;
&lt;li&gt;Possible request timeouts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Introducing Celery
&lt;/h2&gt;

&lt;p&gt;Celery is a &lt;strong&gt;distributed task queue&lt;/strong&gt; that allows you to run background jobs outside the request-response cycle.&lt;/p&gt;

&lt;p&gt;Instead of executing tasks immediately, Django sends the job to a queue.&lt;/p&gt;

&lt;p&gt;A worker then processes it asynchronously.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
User Request
    ↓
Django
    ↓
Queue Task
    ↓
Immediate Response
    ↓
Celery Worker
    ↓
Executes Job

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes your application &lt;strong&gt;fast and scalable&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Redis?
&lt;/h2&gt;

&lt;p&gt;Celery requires a &lt;strong&gt;message broker&lt;/strong&gt; to manage task queues.&lt;/p&gt;

&lt;p&gt;Redis is commonly used because it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extremely fast&lt;/li&gt;
&lt;li&gt;Lightweight&lt;/li&gt;
&lt;li&gt;Easy to deploy&lt;/li&gt;
&lt;li&gt;Perfect for queues and caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Redis stores the tasks until workers pick them up.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: Sending Email in the Background
&lt;/h2&gt;

&lt;p&gt;Instead of sending email directly in a Django view:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanks for signing up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noreply@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&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;You create a Celery task:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;celery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shared_task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.core.mail&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_mail&lt;/span&gt;

&lt;span class="nd"&gt;@shared_task&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;send_mail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Welcome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Thanks for signing up&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;noreply@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;email&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;Then call it asynchronously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;send_welcome_email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user gets an immediate response while the email is processed in the background.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common Use Cases for Celery
&lt;/h2&gt;

&lt;p&gt;Celery is useful for many production tasks:&lt;/p&gt;

&lt;h3&gt;
  
  
  Email sending
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Welcome emails&lt;/li&gt;
&lt;li&gt;Notifications&lt;/li&gt;
&lt;li&gt;Password resets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Data processing
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Financial calculations&lt;/li&gt;
&lt;li&gt;AI processing&lt;/li&gt;
&lt;li&gt;Data pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scheduled tasks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Daily reports&lt;/li&gt;
&lt;li&gt;Cleaning expired sessions&lt;/li&gt;
&lt;li&gt;Updating analytics&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Running Celery in Production
&lt;/h2&gt;

&lt;p&gt;A typical Django production stack might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Users
  ↓
Nginx
  ↓
Gunicorn
  ↓
Django App
  ↓
Redis (Broker)
  ↓
Celery Workers

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component plays a role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nginx&lt;/strong&gt; handles web traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gunicorn&lt;/strong&gt; runs Django&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redis&lt;/strong&gt; manages task queues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Celery workers&lt;/strong&gt; execute background jobs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Scaling Celery Workers
&lt;/h2&gt;

&lt;p&gt;One of Celery’s biggest advantages is scalability.&lt;/p&gt;

&lt;p&gt;If tasks increase, you simply add more workers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;celery &lt;span class="nt"&gt;-A&lt;/span&gt; project worker &lt;span class="nt"&gt;--loglevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nt"&gt;--concurrency&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More workers mean faster task processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Celery and Redis are essential tools for running Django applications at scale.&lt;/p&gt;

&lt;p&gt;They allow you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve response times&lt;/li&gt;
&lt;li&gt;Handle heavy workloads&lt;/li&gt;
&lt;li&gt;Build scalable architectures&lt;/li&gt;
&lt;li&gt;Run background processing reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Django application handles tasks that take more than a few seconds, moving them to Celery is one of the best architectural decisions you can make.&lt;/p&gt;

</description>
      <category>django</category>
      <category>redis</category>
      <category>celery</category>
      <category>backgroundtasks</category>
    </item>
    <item>
      <title>A Practical Guide to File Uploads (Images, Excel, CSV) in Angular + Django</title>
      <dc:creator>Billy Okeyo</dc:creator>
      <pubDate>Mon, 23 Feb 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/billy_de_cartel/a-practical-guide-to-file-uploads-images-excel-csv-in-angular-django-15gl</link>
      <guid>https://dev.to/billy_de_cartel/a-practical-guide-to-file-uploads-images-excel-csv-in-angular-django-15gl</guid>
      <description>&lt;p&gt;File uploads are one of those features that look simple… until they aren’t.&lt;/p&gt;

&lt;p&gt;Images need previewing. CSV files need parsing. Excel files need validation. Large files need handling. And suddenly your “simple upload” becomes a full feature.&lt;/p&gt;

&lt;p&gt;In this guide, we’ll build a practical and production-ready file upload system using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Angular (Frontend)&lt;/li&gt;
&lt;li&gt;Django + Django REST Framework (Backend)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll cover:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Image uploads with preview&lt;/li&gt;
&lt;li&gt;CSV uploads and parsing&lt;/li&gt;
&lt;li&gt;Excel uploads and processing&lt;/li&gt;
&lt;li&gt;Validation and security best practices&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Backend Setup (Django + DRF)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Install Dependencies
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;djangorestframework pillow openpyxl pandas

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pillow&lt;/code&gt; → Image processing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;openpyxl&lt;/code&gt; → Excel support&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pandas&lt;/code&gt; → CSV &amp;amp; Excel parsing&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Configure Media Files
&lt;/h2&gt;

&lt;h3&gt;
  
  
  settings.py
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/media/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BASE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  urls.py (project level)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.conf.urls.static&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;static&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# your urls
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;static&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;document_root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Part 1: Image Upload
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;profiles/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Serializer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; __all__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  View
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.parsers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormParser&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ProfileUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FormParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProfileSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_201_CREATED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Angular Frontend (Image Upload)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  HTML
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;(change)=&lt;/span&gt;&lt;span class="s"&gt;"onFileSelected($event)"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;*ngIf=&lt;/span&gt;&lt;span class="s"&gt;"previewUrl"&lt;/span&gt; &lt;span class="na"&gt;[src]=&lt;/span&gt;&lt;span class="s"&gt;"previewUrl"&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;(click)=&lt;/span&gt;&lt;span class="s"&gt;"upload()"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Upload&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Component
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;selectedFile&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nl"&gt;previewUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;ArrayBuffer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&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;onFileSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&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;selectedFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&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;reader&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;FileReader&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&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="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;previewUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nx"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readAsDataURL&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;selectedFile&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;upload&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;formData&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;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&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;Billy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image&lt;/span&gt;&lt;span class="dl"&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;selectedFile&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;http&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;http://localhost:8000/api/upload/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Part 2: CSV Upload and Parsing
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django View for CSV
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIView&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;rest_framework.parsers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MultiPartParser&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CSVUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid file type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Example processing
&lt;/span&gt;        &lt;span class="n"&gt;total_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;columns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;total_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Angular CSV Upload
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;uploadCSV&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;File&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;formData&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;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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;http&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;http://localhost:8000/api/upload-csv/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Part 3: Excel Upload (.xlsx)
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Django View for Excel
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExcelUploadView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parser_classes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MultiPartParser&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FILES&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.xlsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid file type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&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="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Validation &amp;amp; Security Best Practices
&lt;/h1&gt;

&lt;h2&gt;
  
  
  1. Limit File Size
&lt;/h2&gt;

&lt;p&gt;In settings.py:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;DATA_UPLOAD_MAX_MEMORY_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5242880&lt;/span&gt; &lt;span class="c1"&gt;# 5MB
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Validate File Type Properly
&lt;/h2&gt;

&lt;p&gt;Don’t rely only on extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image/jpeg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;image/png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Invalid image format&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Rename Uploaded Files
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;upload_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;uploads/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ext&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Handle Large Files with Streaming
&lt;/h2&gt;

&lt;p&gt;For very large CSV files, process in chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunksize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# process chunk
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Bonus: Returning Processed Data
&lt;/h1&gt;

&lt;p&gt;Sometimes you don’t just upload — you process and return a result file.&lt;/p&gt;

&lt;p&gt;Example: Generate processed CSV&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Content-Disposition&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;attachment; filename=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processed.csv&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Final Thoughts
&lt;/h1&gt;

&lt;p&gt;File uploads are not just about saving files.&lt;/p&gt;

&lt;p&gt;They are about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validation&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;User experience&lt;/li&gt;
&lt;li&gt;Scalability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When done correctly, they become powerful data pipelines inside your application.&lt;/p&gt;

&lt;p&gt;If you’re building admin systems, reporting tools, learning platforms, or fintech dashboards — mastering uploads is essential.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>fullstack</category>
      <category>django</category>
      <category>angular</category>
    </item>
  </channel>
</rss>
