Undo Is Not a Feature You Add Later
The last post argued for soft deletion as a default, on the grounds that reversible operations are cheaper to get wrong. That is easy to say. The part that gets skipped is that “reversible” is a claim about the system, not about the button, and honoring it is considerably more work than setting a flag.
The flag is the easy half. What makes undo hard is everything that happened between the delete and the moment somebody asks for it back.
Consider the simplest possible case. A user deletes a record. Ten minutes later they want it restored. If nothing else in the system touched anything in those ten minutes, restoring is trivial — clear the flag, the row reappears. But things did happen. The record was in a folder, and someone renamed the folder. It was referenced by a report, and the report was rebuilt without it and cached. It contributed to a count that a dashboard has already shown to someone. It was the last item in a container, and the container auto-archived itself because it was empty. Each of these is a small, sensible behavior, and each one means the world the record is being restored into is not the world it left.
So restoration is not the inverse of deletion. It is a new operation that tries to produce a state resembling a previous one, and how well it does that depends on how many downstream effects were derived rather than stored. A product where most things are computed on read has an easy time here. A product that materializes, denormalizes, and caches — which is to say, a product that got fast — has a much harder one, and typically discovers this the first time a restore quietly produces an inconsistent state.
Then there is the question nobody wants to answer, which is how long. Retention for deleted things is usually set by whoever wrote the cleanup job, based on what felt reasonable that afternoon. Thirty days is the folk standard. Nobody checked it against how long it actually takes a customer to notice a mistake, and that number is not thirty days — it is however long until the next monthly close, the next audit, the next time someone opens the thing they only open quarterly. The mismatch shows up as support tickets you cannot fix: the person is asking for something that existed until eleven days ago.
Meanwhile the same retention window is a liability in the other direction. Every deleted-but-retained record is data you still hold, still have to secure, still have to disclose, and still have to hand over if someone exercises a right to erasure. “We keep it for ninety days just in case” is a product decision with legal weight, and it is frequently made by nobody in particular.
The pattern that survives contact with both problems is to stop treating undo as a property of individual records and start treating it as a property of actions. Not “this row is deleted” but “at 14:22, this person deleted these eleven things as one operation.” Store the operation. It costs a little more, and it buys most of what people actually want: a restore that returns everything that went together rather than one row at a time, an audit trail that answers who and when without a separate system, and a place to attach the reason if there was one.
It also gives the interface something honest to say. “Deleted 11 items — Undo” is a real sentence about a real operation. It can be shown right after the action, when undo is nearly free and the user’s intent is fresh, which is where the overwhelming majority of undos happen. Most recoveries are not archaeology; they are somebody clicking the wrong thing and immediately wanting it back. A product that handles the ten-second case gracefully and the ten-day case adequately is in better shape than one that skipped the first and built an elaborate restore console for the second.
The last thing worth saying is that undo is a promise made before it is needed and evaluated only after. Nobody tests it. It sits in the codebase accumulating drift as new fields are added and new side effects are introduced, and it is exercised for the first time in anger by a customer who has already had a bad morning. If restoration matters, it needs the same treatment as any other path that must work on its first attempt: run it, on real shapes of data, on a schedule, before someone else does.