# How the RealWorld Backend Handles Race Conditions During Concurrent Article Updates

> Learn how the RealWorld backend prevents race conditions during concurrent article updates using SQLite ACID transactions and a unique index for atomic database writes.

- Repository: [Thinkster/realworld](https://github.com/gothinkster/realworld)
- Tags: internals
- Published: 2026-02-28

---

**The RealWorld backend prevents race conditions during concurrent article updates by leveraging SQLite's ACID transactions and a unique database index on the article slug, ensuring that conflicting writes fail atomically at the database level.**

The [gothinkster/realworld](https://github.com/gothinkster/realworld) repository implements a production-grade Medium clone API that demonstrates handling **concurrent article updates** without traditional locking mechanisms. When multiple clients simultaneously attempt to modify the same article via `PUT /api/articles/:slug`, the system relies on database-level guarantees provided by **Prisma** and **SQLite** rather than application-layer mutexes or semaphores.

## The Read-Modify-Write Flow

The update handler in `apps/api/server/routes/api/articles/[slug]/index.put.ts` follows a classic read-modify-write pattern. First, it retrieves the existing article record to verify ownership and compute potential changes. This initial fetch automatically executes within an implicit SQLite transaction boundary, establishing a consistent state view for the subsequent operations.

The handler validates ownership before processing any modifications:

```typescript
const existingArticle = await usePrisma().article.findFirst({ … });
if (existingArticle.author.id !== auth.id) { … }

```

*(see lines 12-31 of the route handler)*

## Defensive Application Checks

### Ownership Verification

The code explicitly compares the authenticated user's ID against the article's author ID before allowing modifications. If the ownership check fails, the handler immediately throws a 403 Forbidden error, preventing unauthorized writes from reaching the database layer.

### Pre-Emptive Slug Collision Detection

When a title change triggers a slug update, the application performs a defensive lookup to catch conflicts early:

```typescript
const existingTitle = await usePrisma().article.findFirst({
    where: { slug: newSlug },
    select: { slug: true },
});
if (existingTitle) {
    throw new HttpException(422, { errors: { title: ['must be unique'] } });
}

```

*(see lines 40-51 of [`index.put.ts`](https://github.com/gothinkster/realworld/blob/main/index.put.ts))*

This application-level validation provides immediate feedback for obvious conflicts, though the database constraint serves as the ultimate authority for race condition prevention.

## Database-Level Atomicity and Constraints

### Unique Index Enforcement

The Prisma schema in `apps/api/prisma/schema.prisma` defines the slug field with a `@unique` attribute:

```prisma
model Article {
    slug String @unique
    …
}

```

*(see lines 13-14 of the schema file)*

SQLite enforces this constraint atomically. If two concurrent requests pass the pre-check simultaneously, the first committed `UPDATE` succeeds while the second encounters a unique constraint violation. Prisma propagates this as a `PrismaClientKnownRequestError`, which the API translates into a 422 Unprocessable Entity response.

### Transactional Isolation Guarantees

Each request operates within a single SQLite transaction started implicitly by Prisma. SQLite's ACID properties ensure that the series of reads and writes—ownership verification, slug collision checks, and the final update—execute as an atomic unit isolated from other concurrent requests. Even if two processes pass the pre-check simultaneously, the database serializes the writes, allowing only one to commit successfully.

## Consistent Tag Update Handling

The handler maintains referential integrity by clearing existing tag relationships before applying updates. The `disconnectArticlesTags` operation and the subsequent `article.update` execute within the same transaction:

```typescript
await disconnectArticlesTags(slug);
const updatedArticle = await usePrisma().article.update({ … });

```

*(see lines 63-78 of the route handler)*

This sequencing prevents orphaned tag relationships if the update fails after the disconnect operation, ensuring that tag modifications cannot be left half-finished due to concurrent interference or partial failures.

## Summary

- **Database constraints** provide the primary defense against race conditions through the unique index on `slug` defined in `apps/api/prisma/schema.prisma`.
- **SQLite transactions** ensure atomic execution of the read-modify-write sequence, isolating concurrent requests via ACID guarantees.
- **Application-level checks** verify ownership and perform pre-emptive slug collision detection to fail fast before attempting database writes.
- **Tag updates** maintain consistency by executing `disconnectArticlesTags` and `article.update` within the same transactional boundary.

## Frequently Asked Questions

### What happens if two users try to update an article title simultaneously?

Both requests may pass the initial slug availability check, but SQLite's unique constraint on the slug field ensures only the first committed transaction succeeds. The second request receives a constraint violation error, which the API returns as a 422 status code with a "must be unique" validation message.

### Does RealWorld use pessimistic locking to prevent race conditions?

No, the implementation relies on optimistic concurrency control through database constraints and transactional atomicity rather than explicit locks. SQLite's isolation levels and the unique index on the slug field provide sufficient protection without the complexity of application-level locking mechanisms.

### How does the system handle concurrent updates to article tags?

Tag modifications are wrapped in the same SQLite transaction as the article update. The handler first calls `disconnectArticlesTags` to remove existing relationships, then performs the `article.update` with new tags. If the update fails, the transaction rolls back atomically, preventing partial or inconsistent tag states.

### Can the ownership check itself be subject to race conditions?

The ownership verification occurs within the database transaction and reads committed state consistent with the transaction's start point. While concurrent ownership transfers are theoretically possible, the subsequent `article.update` operation uses the slug as a stable identifier, and SQLite's transactional isolation prevents phantom reads that could corrupt the authorization check.