Distributed Lock
Problem
Implement a distributed locking service using Redis. Your API should allow clients to acquire exclusive locks on named resources, perform work while holding the lock, and release it when done. If a client crashes while holding a lock, it should automatically expire so other clients aren't blocked forever.
Background
In distributed systems, multiple instances of a service often need to coordinate access to shared resources — a database row, an external API, a file, a scheduled job. Without coordination, two instances might process the same payment, send duplicate emails, or corrupt shared state.
A distributed lock solves this by providing mutual exclusion across processes. Redis is commonly used because it's fast, supports atomic operations, and can automatically expire keys (preventing deadlocks from crashed clients).
The key challenges are: ensuring only one client holds the lock at a time (mutual exclusion), making sure locks eventually expire (liveness), and ensuring only the lock owner can release it (safety).
Requirements
POST /lock/acquire— Acquire a named lock with a TTLPOST /lock/release— Release a lock (only if you own it)GET /lock/status/:resource— Check if a resource is currently locked- Locks must be mutually exclusive — only one client holds a lock at a time
- Locks must auto-expire after their TTL to prevent deadlocks
- Only the lock owner (identified by their
ownerId) can release the lock
Your Server
- Your server receives
PORT(default3000) andREDIS_URLenvironment variables - Redis is provided as a sidecar — it's up when your server starts
- Use Redis for all lock state — don't use in-memory maps
API Contract
`POST /lock/acquire`
Request body:
{
class="text-pass">"resource": class="text-pass">"order-123",
class="text-pass">"ownerId": class="text-pass">"worker-1",
class="text-pass">"ttlMs": 5000
}If lock acquired successfully:
- Status:
200 - Body:
{
class="text-pass">"acquired": true,
class="text-pass">"resource": class="text-pass">"order-123",
class="text-pass">"ownerId": class="text-pass">"worker-1"
}If lock is already held by another client:
- Status:
409 - Body:
{
class="text-pass">"acquired": false,
class="text-pass">"resource": class="text-pass">"order-123",
class="text-pass">"holder": class="text-pass">"worker-2"
}`POST /lock/release`
Request body:
{
class="text-pass">"resource": class="text-pass">"order-123",
class="text-pass">"ownerId": class="text-pass">"worker-1"
}If lock released successfully:
- Status:
200 - Body:
{ "released": true }
If lock is held by a different owner:
- Status:
403 - Body:
{ "released": false, "error": "Lock held by different owner" }
If lock doesn't exist (already expired or never acquired):
- Status:
404 - Body:
{ "released": false, "error": "Lock not found" }
`GET /lock/status/:resource`
If locked:
- Status:
200 - Body:
{
class="text-pass">"locked": true,
class="text-pass">"resource": class="text-pass">"order-123",
class="text-pass">"ownerId": class="text-pass">"worker-1"
}If not locked:
- Status:
200 - Body:
{
class="text-pass">"locked": false,
class="text-pass">"resource": class="text-pass">"order-123"
}Useful APIs
class=class="text-pass">"text-fg-subtle">// Redis: set a key only if it doesnclass="text-pass">'t exist (atomic lock acquire)
const result = await redis.set(key, value, 'PXclass="text-pass">', ttlMs, 'NXclass="text-pass">');
class=class="text-pass">"text-fg-subtle">// result === 'OKclass="text-pass">' if set, null if key already exists
class=class="text-pass">"text-fg-subtle">// Redis: get a value
const value = await redis.get(key);
class=class="text-pass">"text-fg-subtle">// Redis: delete a key only if it has a specific value (atomic lock release)
class=class="text-pass">"text-fg-subtle">// Use a Lua script for atomicity:
const script = class="text-pass">`
if redis.call(class="text-pass">"get", KEYS[1]) == ARGV[1] then
return redis.call(class="text-pass">"del", KEYS[1])
else
return 0
end
`;
const result = await redis.eval(script, 1, key, expectedValue);
class=class="text-pass">"text-fg-subtle">// result === 1 if deleted, 0 if value didn't match
class=class="text-pass">"text-fg-subtle">// Redis: check TTL remaining on a key (milliseconds)
const ttl = await redis.pttl(key);Your solution will be tested against these scenarios plus a hidden set.
- 01Acquire a lock, perform work, and release it
- 02A second request is blocked while the lock is held
- 03Lock is automatically released after TTL expires
- 04A lock can only be released by the client that acquired it
Hints
Click each hint to reveal it. Take your time — try before you peek.