How do distributed transactions work and what is two-phase commit (2PC)?

Imagine transferring money between two bank accounts using a mobile banking app. The app needs to debit one account and credit another – and it must do both or neither (you wouldn’t want money to vanish or duplicate!). This all-or-nothing behavior is managed by transactions. In a simple, single-database scenario, transactions are straightforward. But in modern system architecture (like microservices where data is spread across services), ensuring a consistent all-or-nothing update becomes tricky. This is where distributed transactions come in. They coordinate actions across multiple systems so that either every step succeeds or all are rolled back as one unit. In this article, we’ll demystify how distributed transactions work and dive into the Two-Phase Commit (2PC) protocol that makes them possible. We’ll use easy examples (8th-grade reading level) and a conversational tone, so even if you’re new to the topic, you’ll grasp the basics. Along the way, we’ll also share technical interview tips (common in system design interviews) and even touch on mock interview practice ideas to help you ace questions on this topic.

What Are Distributed Transactions?

Distributed transactions are transactions that span multiple systems or databases. In a normal (local) transaction, like one database update, all the changes either commit or roll back together – maintaining the “atomic” (indivisible) nature of the transaction. Distributed transactions extend this guarantee across different services or storage systems. In other words, a distributed transaction coordinates multiple parts (on different nodes or microservices) so that the entire set of operations succeeds or fails as one. This ensures data consistency across the whole system, even if the data is in separate places.

Let’s break that down with a simple analogy: imagine a travel booking that involves a flight service and a hotel service. You want either both reservations to be confirmed or none at all – a half-booked trip isn’t acceptable. A distributed transaction can ensure that if the flight reservation succeeds but the hotel reservation fails, both will be rolled back (the flight booking will be canceled) so you don’t end up with inconsistent state.

In practice, each service in a distributed transaction still uses local transactions (ensuring its own ACID properties), but a global coordinator orchestrates these local actions to achieve a single global outcome. This coordination is the tricky part – it requires protocols to communicate between services about whether to commit or abort. One of the most common protocols to achieve this is Two-Phase Commit, which we’ll explain next.

How Two-Phase Commit (2PC) Works

Two-Phase Commit (2PC) is a protocol that ensures atomicity and consistency of a distributed transaction. In simple terms, it makes sure that either all the involved systems commit the transaction or none do, keeping the data consistent everywhere. It’s called “two-phase” because it has two distinct phases of execution. Here’s how it works:

  1. Phase 1 – Prepare: There is a central coordinator (transaction manager) that kicks off the process. In the prepare phase, the coordinator asks all participating nodes (databases or services) if they are able to commit the transaction. Each participant will do everything necessary to get ready – for example, reserve resources, lock any needed records, and write to a temporary log that it’s prepared to commit. Then each participant replies to the coordinator with either “Yes, prepared” or “No, can’t do it.” Essentially, they promise the coordinator they can commit if instructed. If even one participant votes “No” (meaning something went wrong or it can’t commit), the coordinator will tell all participants to abort (roll back) the transaction to maintain consistency. (For instance, if the hotel booking service can’t prepare the reservation due to no availability, the flight service will be instructed to cancel its part too.)

  2. Phase 2 – Commit: If all participants responded “Yes” in Phase 1, it means every part of the transaction is prepared and likely to succeed. Now the coordinator moves to the commit phase. It sends a commit message to all participants, instructing them to finalize the transaction (i.e. actually write the changes permanently). At this point, all parts will commit nearly simultaneously. Each participant, upon successful commit, releases any locks and resources held from the prepare phase. If any participant had said “No” earlier, the coordinator would instead send a rollback/abort message to each participant, telling them to undo any changes (if they had made any partial updates or held any locks). The outcome is that either every participant commits its part or every participant aborts, so the overall distributed transaction is consistent everywhere. As one source puts it, 2PC “ensures that either all the changes are committed or none” across the distributed system.

Under the hood, each participant uses a write-ahead log or some form of persistent record to remember its decision (prepared or committed). This way, even if a node crashes mid-transaction, it can recover and still honor the outcome once it comes back online. The coordinator too typically logs the global decision (commit or abort). This logging is important for fault tolerance: for example, if the coordinator crashes after sending some commit messages, the participants can consult the log when it recovers (or a backup coordinator) to determine the outcome and avoid uncertainty.

Summary of 2PC: The first phase is about voting – all services vote on whether they can commit. The second phase is about decision – if the vote was unanimous to commit, everyone commits; if anyone voted to abort, everyone aborts. This protocol guarantees that no partial commits occur: the transaction is truly atomic across all involved nodes. However, it does introduce some overhead and potential waiting: participants must pause after prepare until the coordinator gives the final commit/abort command. We’ll discuss the implications of this in the best practices section.

Real-World Examples of Distributed Transactions

To solidify the concept, let’s look at a couple of real-world scenarios where distributed transactions (and sometimes 2PC) are used:

  • Banking Money Transfer (Microservices): Consider a banking system split into microservices – one service for savings accounts and another for checking accounts. When a user transfers $100 from their checking to savings, two different databases/services are involved. A distributed transaction would debit $100 from the checking account in Service A and credit $100 to the savings account in Service B as a single unit. If either operation fails, the whole transfer is canceled. This prevents the situation where one account was debited but the other wasn’t updated. In practice, implementing this in a microservices architecture is challenging (each service has its own database). One approach is to use a 2PC coordinator that both services trust to orchestrate the commit. However, many modern systems avoid 2PC here (for reasons we’ll discuss) and use alternative techniques like the Saga pattern or event-driven consistency. (For more on strategies for managing database transactions in a microservices architecture, see our guide on managing database transactions in microservices and tips on maintaining data consistency in microservices architecture.)

  • Order Processing in E-Commerce: Imagine an online store with a microservice for orders, one for inventory, and one for payment. Placing an order might involve reserving an item in inventory, charging the customer’s credit card, and creating an order record – three different services/databases. A distributed transaction would ensure that these three actions either all succeed or all fail. For instance, you wouldn’t want to charge the customer but not reserve the item, or vice versa. A 2PC transaction could coordinate the inventory service, payment service, and order service: in the prepare phase each service says “I’m ready” (e.g., inventory confirms the item can be reserved, payment confirms the card can be charged, etc.), and in the commit phase all services apply the changes. If any service cannot prepare (e.g., credit card charge is declined), the coordinator aborts the whole order. This guarantees consistency – the order isn’t created unless payment and inventory steps also succeeded. Some monolithic systems or traditional distributed transaction systems use 2PC for this. However, many cloud-era companies implement this using eventual consistency (e.g., reserve inventory and charge card via separate steps with compensating actions if something fails) rather than a locking 2PC, to keep services decoupled.

  • Strongly Consistent Indexing (Uber’s use-case): Even big tech companies carefully choose where to use 2PC. For example, Uber’s engineering blog mentions using a two-phase commit to maintain strongly consistent indexes in their financial ledger system. Uber processes huge numbers of financial transactions, and they built a custom storage system (LedgerStore) that needs indexes (for quick lookup) to always reflect the latest state of the ledger. They use 2PC so that when a ledger record is written, the index update is committed in sync with that record. This means an index entry never points to a ledger record that didn’t get saved – the index and the data are always in lockstep. It’s an example of using distributed transactions (between the ledger storage and the index storage) to achieve a high level of consistency for a critical use-case. The takeaway: 2PC is used when strong consistency truly matters and is worth the performance cost.

These examples show that distributed transactions and 2PC can apply anywhere multiple systems must act like one. However, they also hint that there’s a trade-off – 2PC can be complex and slow, so engineers use it sparingly and often explore other patterns. Let’s talk about those considerations next.

Best Practices and Tips for Distributed Transactions

While distributed transactions (and 2PC in particular) can guarantee a consistent outcome, they come with challenges. Here are some best practices and tips to keep in mind, especially if you’re discussing this topic in an interview or designing a system:

  • Use Distributed Transactions Sparingly: In an ideal world, you design your system boundaries such that most transactions are local to one service or database. Distributed transactions add complexity and latency – all parties have to coordinate, and a slow or failing participant can delay everyone. In fact, in a microservices system architecture, having a single transaction span many services creates tight coupling (each service must pause and wait for others). If high throughput and independence of services are a priority, consider whether you can avoid a distributed transaction by redesigning the workflow or service boundaries.

  • Be Aware of 2PC Trade-offs: The 2PC protocol provides strong consistency (all-or-nothing behavior), but at a cost. It can be slow and can impact availability in certain failure scenarios. For example, if the coordinator crashes at the wrong time, participants might be left waiting (holding locks) until it recovers to tell them what to do. In the worst case – like an unrecoverable coordinator failure – the system could end up in a stuck state (though techniques exist to mitigate this, such as timeouts or having a backup coordinator). In an interview setting, a good technical interview tip is to mention this downside: “2PC guarantees consistency, but it can block progress if a node crashes, and it doesn’t scale well for high-volume, highly distributed scenarios.” Being aware of this shows you understand the real-world implications.

  • Consider the Saga Pattern (as an Alternative): Modern cloud systems often favor the Saga pattern over strict two-phase commits. A Saga breaks a distributed transaction into a series of local transactions, each followed by a next step; if something fails along the way, the Saga executes compensating transactions to undo the changes made so far. This avoids a single locking coordinator and favors eventual consistency over immediate consistency. For example, instead of a 2PC across inventory, payment, and order services, an e-commerce app might create an order, then trigger an event for payment, then an event for inventory. If any step fails, subsequent compensating events try to reverse the earlier steps (like refund the payment or cancel the order). The Saga pattern is more scalable and decoupled (no central coordinator), but you must carefully design the rollback steps. Interview tip: if asked how to handle transactions in microservices, talk about Saga vs. 2PC. (See our discussion on data consistency in microservices architecture for more on Sagas and eventual consistency.)

  • Idempotency and Retries: Whatever approach you use, network calls or services can fail midway. A best practice is to make operations idempotent – meaning if you retry them, they don’t double-apply changes. This is especially important in distributed transactions: e.g., a participant that isn’t sure if it committed might get the commit message again on recovery – and it should be able to execute it safely without doing the operation twice. Designing with idempotency in mind makes your system more robust. It’s a good talking point in interviews if you mention how you’d handle duplicate messages or retries gracefully.

  • Use a Reliable Coordinator or Transaction Manager: If you do implement 2PC (or any atomic commit protocol), the coordinator is a critical piece. Make sure it’s on a reliable node (with backups, if possible) and that it uses persistent storage for the transaction log. Some databases and messaging systems have built-in support for distributed transactions (e.g., the XA standard in SQL databases, or transaction managers like Narayana). Using proven infrastructure can save you from reinventing the wheel. However, remember that even the best coordinator can’t avoid the fundamental limitation: a distributed transaction ties multiple systems together, which can impact your system’s availability (per the CAP theorem trade-offs).

  • Practice Explaining with Simple Terms: (Mock interview practice tip:) When preparing for system design or technical interviews, try explaining distributed transactions to a peer or in a mock interview practice session. Use simple analogies (like the bank transfer or multi-service order examples we used here). Interviewers appreciate clarity. For instance, you might say: “Let’s say Service A and Service B both need to update. We can use a 2PC: a coordinator asks both ‘can you do this if I ask?’ If both say yes, it then tells both to commit. If either says no, it aborts the whole thing.” This shows you understand the sequence and the all-or-nothing outcome. Follow up by noting the pros (consistency) and cons (slower, potential blocking) and maybe mention the Saga alternative for bonus points.

By keeping these best practices in mind, you can leverage distributed transactions where necessary while also being aware of their impact. Remember, there’s no one-size-fits-all – choosing between strong consistency (with something like 2PC) and eventual consistency (with patterns like Saga or event-driven updates) depends on the requirements of your system (e.g., a banking system may favor consistency, while a social media feed might tolerate eventual consistency for higher availability).

FAQs: Distributed Transactions & Two-Phase Commit

Q1: What is a distributed transaction in simple terms? A distributed transaction is an operation that involves multiple systems or databases but needs to act like one transaction. It ensures all parts either succeed together or fail together. For example, if two different services each update their database, a distributed transaction makes sure either both updates happen or none do, keeping data consistent.

Q2: How does the two-phase commit (2PC) protocol work? Two-phase commit works in two steps. First, a coordinator asks all involved systems if they are ready to commit (this is the prepare phase – each system reserves resources and replies “Yes” or “No”). If everyone agrees, the coordinator then sends a commit command to actually finalize the changes (commit phase). If anyone had said “No” in the first phase, the coordinator tells everyone to abort. This way, either every system commits or none of them do.

Q3: Why isn’t 2PC used everywhere in microservices? While 2PC guarantees strong consistency, it can introduce delays and become a single point of failure. In microservices, which prioritize independent scaling and availability, waiting on a coordinator can be impractical. If the coordinator or any service is slow or crashes, it might lock up the others. Because of these downsides, many microservice architectures prefer eventual consistency patterns (like Sagas or event-driven updates) instead of global two-phase commits.

Q4: What are alternatives to two-phase commit for distributed transactions? A common alternative is the Saga pattern, which manages distributed transactions through a series of local transactions with compensating actions on failure (ensuring consistency without a single coordinator). Other approaches include event-driven designs (using events to update other services asynchronously) or simply avoiding multi-service transactions by redesigning the system (e.g., using a shared database for related services or merging services). These alternatives aim to reduce the tight coupling and latency that come with 2PC, trading off immediate consistency for more scalability and fault tolerance.

Conclusion

Distributed transactions are a fundamental concept when you need to maintain atomic operations across multiple systems. The Two-Phase Commit (2PC) protocol is the classic solution to achieve this all-or-nothing consistency, coordinating participants in a structured way to either commit everywhere or abort everywhere. We learned that while 2PC can ensure a consistent state across services (a big plus in critical applications like banking), it also has downsides like added latency and complexity. In modern architectures (think microservices), designers often weigh these trade-offs carefully – sometimes opting for 2PC when strong consistency is a must, and other times favoring patterns like Saga for eventual consistency to keep systems decoupled and scalable.

For technical interview prep, remember to explain the how and why: describe the phases of 2PC in simple terms and discuss when you would (or wouldn’t) use it. Highlighting real examples (like our bank transfer or order processing scenarios) can make your answers concrete. And don’t forget to mention alternatives (interviewers love to see that you know multiple approaches).

Key takeaway: Distributed transactions and 2PC show that designing distributed systems is a balancing act between consistency and complexity. By understanding these concepts, you’ll be better equipped to build reliable systems and answer those tricky system design interview questions.

Ready to master system design and ace your interviews? Check out DesignGurus.io for more resources and in-depth courses. In particular, you might benefit from our Grokking the System Design Interview course, which covers topics like these in a practical, easy-to-digest way. Happy learning, and good luck with your system design journey!

CONTRIBUTOR
Design Gurus Team
-

GET YOUR FREE

Coding Questions Catalog

Design Gurus Newsletter - Latest from our Blog
Boost your coding skills with our essential coding questions catalog.
Take a step towards a better tech career now!
Explore Answers
Related Courses
Grokking the Coding Interview: Patterns for Coding Questions
Grokking the Coding Interview Patterns in Java, Python, JS, C++, C#, and Go. The most comprehensive course with 476 Lessons.
Grokking Modern AI Fundamentals
Master the fundamentals of AI today to lead the tech revolution of tomorrow.
Grokking Data Structures & Algorithms for Coding Interviews
Unlock Coding Interview Success: Dive Deep into Data Structures and Algorithms.
Image
One-Stop Portal For Tech Interviews.
Copyright © 2025 Design Gurus, LLC. All rights reserved.
;