Product design

Notion: recover the content you need after a document goes wrong

Notion is a shared workspace for documents, information and tasks. Version history helps a team compare changes and choose what to recover after an accidental edit.

Break down the problem and try your own design and implementation first. Then explore the reasoning to check states, edge cases, trade-offs, and ways to test your approach. The guide offers a reasoning path, not a complete implementation.

01
CODE EXERCISEAdvanced

How can a restore avoid overwriting edits made after the preview?

Scenario: Design a recovery endpoint for your own document service. Each document has an increasing version number. A restore must complete as a whole operation, never as a partial write.

  1. Inputs and contract: The client sends the document ID, target historical version, current version seen in the preview, and an operation ID. That ID identifies retries of the same submission. Every request must check current permissions.
  2. Work through this example: A user previews restoring version 8 while the document is at version 12. Before submission, a colleague writes version 13. Explain the response and the interface’s next action.
  3. Failure cases: The server finishes the restore but its response is lost. The client retries with the same operation ID. Another request incorrectly reuses that ID with a different target version.
  4. Deliver: A state diagram, the boundary of the all-or-nothing commit, an operation-ID retention policy, and tests for success, conflicts, repeated requests and permission changes. Explain your reasoning without a complete implementation. JS or Rust can be used to discuss data structures.
Explore the reasoning
  1. Check current permissions, then whether the operation already completed. For a completed operation, compare the request’s key fields and return the recorded result. Reject the same ID with a different target. Only new operations proceed to the current-version check; a successful retry must not become a false conflict because the version has advanced.
  2. Commit the version check, write and operation record together. If a new operation finds that version 12 has become 13, keep the current content and return a conflict. The client fetches the differences again instead of silently replacing them. The ID, request fingerprint and result belong in the same atomic commit as the write.
  3. Restoring creates a new version. Version 8 supplies the content; the version number keeps increasing. If recovery creates version 13 and a colleague then writes 14, retrying the original operation returns its original result. It performs no new write and must not claim that 13 is still current.
  4. Distinguish a known failure from an unknown outcome. After a timeout, query or retry by operation ID rather than assuming nothing happened. Define how long records remain available; deduplication cannot be promised forever after they expire.
  5. Test interleaved events. Include another person editing after preview, a lost response, concurrent retries, the same ID with a different request and revoked permission. List the current version and operation record after every step to check for repeated writes, leaks or overwritten new work.