Interview question
Implement an atomic bulk status update
Validates a bounded command set completely before applying all updates in one SaveChanges transaction.
TL;DR
Validates a bounded command set completely before applying all updates in one SaveChanges transaction.
Bulk input bounds, duplicate detection, preflight validation, atomic persistence, missing rows, and conflicts.
Practice the problem like a real interview: restate, reason, implement, and test.
Accept order ids and target statuses. Reject duplicate ids, unsupported transitions, or missing rows before modifying anything. Commit all valid updates together.
Move to the linked follow-up, next path step, prerequisite, or deeper variant.
Practice the next layer of the same subject.
If one of 20 ids is missing, the response identifies it and none of the other 19 orders change.
I validate command shape first, load every target in one query, compare requested and loaded ids, then validate domain transitions. Only after the whole batch passes do I mutate tracked entities and save once.
app.MapPost("/orders/bulk-status", async Task<IResult> (
BulkStatusRequest request, AppDbContext db, CancellationToken ct) =>
{
if (request.Items is not { Count: > 0 and <= 100 })
return Results.ValidationProblem(new() {
["items"] = ["Provide between 1 and 100 updates."]
});
var ids = request.Items.Select(x => x.Id).ToArray();
if (ids.Distinct().Count() != ids.Length)
return Results.ValidationProblem(new() {
["items"] = ["Each order id may appear only once."]
});
var orders = await db.Orders.Where(x => ids.Contains(x.Id)).ToListAsync(ct);
var missing = ids.Except(orders.Select(x => x.Id)).ToArray();
if (missing.Length > 0)
return Results.NotFound(new ProblemDetails {
Title = "Some orders were not found",
Extensions = { ["missingIds"] = missing }
});
var byId = orders.ToDictionary(x => x.Id);
var errors = new Dictionary<string, string[]>();
foreach (var item in request.Items) {
if (!byId[item.Id].CanMoveTo(item.Status))
errors[$"items[{Array.IndexOf(ids, item.Id)}].status"] =
[$"Cannot move from {byId[item.Id].Status} to {item.Status}."];
}
if (errors.Count > 0) return Results.ValidationProblem(errors);
foreach (var item in request.Items) byId[item.Id].MoveTo(item.Status);
try {
await db.SaveChangesAsync(ct); // One SaveChanges is transactional.
} catch (DbUpdateConcurrencyException) {
return Results.Conflict(new ProblemDetails {
Title = "One or more orders changed during the update",
Status = StatusCodes.Status409Conflict
});
}
return Results.NoContent();
}).RequireAuthorization("orders.manage");
The all-or-nothing contract is easier to retry and reason about than an undocumented mix of successes and failures. For intentionally partial workflows, return an explicit per-item result model instead.
Spot a weak answer, missing edge case, or clearer explanation? Send it in.