What Are Transactions?
Learn what transactions are, what ACID means, and when you need transactions in MongoDB.
What Are Transactions?
Some operations in your app require multiple writes to the database. For example, enrolling a student in a course in our school system requires three writes:
- Add the
courseIdto the student'scourseIdsarray - Increment
totalStudentson the course document - Create a new document in the
enrollmentscollection
What happens if the first write succeeds but your server crashes before the second and third writes? Now the student has the course in their list, but the course enrollment count is wrong and there is no enrollment record. Your data is inconsistent.
A transaction solves this. It groups multiple operations together so they either all succeed or all fail — nothing in between.
The Problem Without Transactions
Without a transaction, a failure halfway through leaves your database in a broken state. Some writes happened, some did not.
With a transaction, if any write fails, MongoDB rolls back all previous writes in that transaction. The database stays consistent — as if none of the writes happened at all.
What is ACID?
ACID is a set of four properties that define what a reliable transaction must guarantee. Every database transaction system — including MongoDB — is measured against these four properties.
Atomicity
All or nothing. Either every operation in the transaction succeeds, or none of them do. There is no partial success.
In our enrollment example — either all three writes succeed together, or all three are rolled back. You will never end up with just one or two of them applied.
Consistency
The database moves from one valid state to another valid state. A transaction never leaves the database in a state that violates your rules.
Before enrollment: student has no course, course has 28 students, no enrollment record. After successful enrollment: student has course, course has 29 students, enrollment record exists. After failed transaction: back to the original state — student has no course, course still has 28 students, no enrollment record.
The database is always consistent — never halfway between states.
Isolation
Transactions do not interfere with each other. While a transaction is in progress, other operations cannot see its partial results.
If two students try to enroll in the same course at the exact same time, each transaction runs as if it is the only one. One completes first, the other completes second. They do not step on each other's data.
Durability
Once committed, the data is permanent. Even if the server crashes immediately after a transaction commits, the data is safely stored and will be there when the server comes back up.
Single Document Operations Are Already Atomic
Before reaching for transactions, it is important to know this — MongoDB already guarantees atomicity for single document operations. A single insertOne, updateOne, or deleteOne is always atomic — it either fully succeeds or fully fails.
// This is already atomic — no transaction needed
// The entire update either happens or it does not
db.students.updateOne(
{ name: "Ali Hassan" },
{
$push: { subjects: "Computer Science" },
$set: { lastUpdated: new Date() },
$inc: { subjectCount: 1 }
}
)Even though this update modifies three things at once — pushing to an array, setting a field, incrementing a counter — it is all one operation on one document. MongoDB handles it atomically without a transaction.
This is one of the big advantages of embedding data. When related data lives in the same document, you never need a transaction to keep it consistent — a single update is always atomic.
Good schema design reduces your need for transactions. If you can update one document instead of three, you get atomicity for free. Reach for transactions only when you truly need to update multiple documents atomically.
When You Need Transactions
You need a transaction when:
You must update multiple documents atomically — like our enrollment example where three documents must all be updated together.
You are moving data between collections — like transferring a fee payment from a pendingPayments collection to a confirmedPayments collection. The delete from one and the insert into the other must happen together.
You need to read, check, then write — sometimes called a read-modify-write pattern. You read a value, make a decision based on it, and write the result. If another operation changes the value between your read and write, you get wrong results. A transaction prevents this.
// Without a transaction — race condition risk
const course = await db.courses.findOne({ code: "MATH-10" });
if (course.totalStudents < course.capacity) {
// Another enrollment might happen here before this write
await db.courses.updateOne(
{ code: "MATH-10" },
{ $inc: { totalStudents: 1 } }
);
}
// With a transaction — safe
// The read and write are isolated togetherYou are implementing financial operations — fee payments, scholarship disbursements, any operation where money moves. These must always be atomic.
When You Do NOT Need Transactions
Transactions add complexity and have a performance cost. Do not use them when you do not need to.
Single document updates — always atomic, no transaction needed.
Inserts that do not depend on each other — if you are inserting five independent records, each insert is its own atomic operation. No need to wrap them in a transaction unless all five must succeed together.
Operations where eventual consistency is acceptable — if it is okay for the course count to be slightly off for a few milliseconds, you do not need a transaction. Use $inc on its own — it is atomic for a single document.
Read-only operations — reads do not need transactions unless you need a consistent snapshot across multiple collections at the exact same point in time.
Transactions in MongoDB — Requirements
MongoDB supports multi-document transactions but with some requirements:
Replica set or sharded cluster required — transactions only work on a replica set or sharded cluster. They do not work on a standalone MongoDB instance.
For local development, the easiest way is to run MongoDB as a replica set. If you are using MongoDB Atlas, you are already on a replica set — transactions work out of the box.
Start a replica set locally for development:
# In mongosh — initiate a single-node replica set
rs.initiate()Or use Docker:
docker run -d -p 27017:27017 mongo --replSet rs0
docker exec -it <container> mongosh --eval "rs.initiate()"MongoDB Atlas runs on a replica set by default. If you are connecting to Atlas, you can use transactions immediately without any extra setup.
Our School System — Where We Need Transactions
Here are the operations in our school system that need transactions:
| Operation | Why it needs a transaction |
|---|---|
| Student enrolls in a course | Updates student, course, and creates enrollment record — all three must succeed together |
| Student drops a course | Updates student, course, and deletes enrollment record |
| Fee payment processed | Creates payment record and updates student fee status |
| Student transfers grade | Updates grade on student, updates counts on both old and new grade |
| Course cancelled | Removes enrollments for all students and updates their records |
Quick Summary
| Concept | What it means |
|---|---|
| Transaction | A group of operations that all succeed or all fail together |
| Atomicity | All or nothing — no partial success |
| Consistency | Database always moves between valid states |
| Isolation | Transactions do not see each other's partial results |
| Durability | Committed data survives crashes |
| Single document ops | Always atomic — no transaction needed |
| Multi document ops | Need explicit transactions for atomicity |
Think of a transaction like a database undo button. Before starting, MongoDB takes note of the current state. If anything goes wrong during the transaction, it presses undo and everything goes back to where it was. If everything succeeds, it commits — and there is no going back.