Scenario advanced

Race Condition in an Async Python Service

Prevent duplicate username registrations in a FastAPI service caused by a check-then-insert race condition, using database constraints and Redis locks.

2 min read ~15 min to complete
Steps
4 Services Used
~15 min Duration
Advanced Difficulty
The Situation

Senior Python developer / backend engineering interviews

Context: You have a FastAPI service where two concurrent requests can both check if a username is available and both register the same username, creating duplicate accounts. How do you fix this?

# BROKEN — race condition between check and insert
@app.post("/register")
async def register(username: str, db: AsyncSession = Depends(get_db)):
    # Request A and B both reach here simultaneously
    existing = await db.execute(select(User).where(User.username == username))
    if existing.scalar():
        raise HTTPException(400, "Username taken")

    # Both requests pass the check above and both insert!
    user = User(username=username)
    db.add(user)
    await db.commit()   # One succeeds, one fails with IntegrityError
# FIX 1 — Database-level unique constraint + handle IntegrityError
# migrations: unique=True on username column

@app.post("/register")
async def register(username: str, db: AsyncSession = Depends(get_db)):
    try:
        user = User(username=username)
        db.add(user)
        await db.commit()
        return {"id": user.id}
    except IntegrityError:
        await db.rollback()
        raise HTTPException(400, "Username already taken")

# FIX 2 — Distributed lock with Redis (for more complex operations)
import redis.asyncio as aioredis

redis = aioredis.from_url("redis://localhost")

@app.post("/register")
async def register(username: str, db: AsyncSession = Depends(get_db)):
    lock_key = f"lock:register:{username}"
    async with redis.lock(lock_key, timeout=5):
        existing = await db.execute(select(User).where(User.username == username))
        if existing.scalar():
            raise HTTPException(400, "Username taken")
        user = User(username=username)
        db.add(user)
        await db.commit()

Best practice: Always rely on database-level constraints as the last line of defence against race conditions. Locks are for coordinating complex multi-step operations.

Services Used
PythonasyncioFastAPIRedis
Prerequisites
  • Python 3.10+
  • Basic understanding of async programming
What You Learned
  • Check-then-act race conditions
  • Database-level unique constraints
  • Distributed locks with Redis

Add More Questions to This Guide

Know a question that should be here? Share it and help the community!

Open Google Form