Katkat

Design a Bookshelf with Book CRUD and a Stable Bookmark

GoogleSoftware Engineer · Onsite

Last updated

Tests object-oriented design, separation of entity identity from physical layout, reference integrity during deletions, and optimistic concurrency for content updates.

Problem Statement

Design an in-memory bookshelf management system that supports book lifecycle operations (CRUD), custom positional ordering on the shelf, and stable bookmarks that track a reader's progress.

Your design must cleanly decouple a book's immutable identity (book_id) from its mutable layout position (index on the shelf). In addition, bookmarks must reference books by their stable identifier so that reordering books does not break bookmarks, and deleting a book avoids dangling or corrupt bookmark states.

Implement the Bookshelf class with the following methods:

class Bookshelf:
    def __init__(self):
        pass

    def add_book(self, book_id: str, title: str, content: str, position: int | None = None) -> bool:
        """Adds a book. If position is None, append to the end. Returns False if book_id exists or position is invalid."""
        pass

    def get_book(self, book_id: str) -> dict | None:
        """Returns book data {'id': str, 'title': str, 'content': str, 'version': int} or None."""
        pass

    def update_book(self, book_id: str, new_title: str | None, new_content: str | None, expected_version: int) -> bool:
        """Updates title/content using optimistic concurrency. Increments version on success. Returns False on mismatch or missing book."""
        pass

    def remove_book(self, book_id: str) -> bool:
        """Removes the book and cascades deletion/invalidation to all associated bookmarks. Returns False if not found."""
        pass

    def move_book(self, book_id: str, new_position: int) -> bool:
        """Moves book to a new 0-indexed position [0, N-1]. Returns False if invalid."""
        pass

    def get_order(self) -> list[str]:
        """Returns ordered list of book_id values on the shelf."""
        pass

    def set_bookmark(self, bookmark_id: str, book_id: str, char_offset: int) -> bool:
        """Sets or updates a bookmark. Returns False if book_id does not exist or offset is out of range [0, len(content)]."""
        pass

    def get_bookmark(self, bookmark_id: str) -> dict | None:
        """Returns {'bookmark_id': str, 'book_id': str, 'char_offset': int} or None if not found or invalidated."""
        pass

Example

shelf = Bookshelf()
shelf.add_book("b1", "Title 1", "Hello World")  # Returns True; order: ['b1']
shelf.add_book("b2", "Title 2", "Sample Content") # Returns True; order: ['b1', 'b2']

# Create bookmark at offset 6 ('W')
shelf.set_bookmark("mark1", "b1", 6)            # Returns True

# Move b2 to position 0
shelf.move_book("b2", 0)                        # Returns True; order: ['b2', 'b1']

# Bookmark still correctly points to b1
assert shelf.get_bookmark("mark1")["book_id"] == "b1"

# Optimistic concurrency update
shelf.update_book("b1", None, "Hello Universe", expected_version=1)  # Returns True, version becomes 2
shelf.update_book("b1", None, "Conflict", expected_version=1)        # Returns False (version is now 2)

# Deleting book cleans up bookmarks
shelf.remove_book("b1")                         # Returns True
assert shelf.get_bookmark("mark1") is None     # Bookmark removed/invalidated

Constraints & Edge Cases

  • Book titles and content are non-empty strings. char_offset must be within 0 <= char_offset <= len(content).
  • Shelf positions are 0-indexed. If position is out of bounds [0, len(books)] during add_book or [0, len(books)-1] during move_book, return False.
  • book_id and bookmark_id are unique alphanumeric strings.
  • Calling remove_book must ensure subsequent calls to get_bookmark for associated bookmarks return None.

Hints

Hint 1

Separate storage into an ID-to-entity map for O(1) lookups and an ordered collection (such as a list) for shelf position.

Hint 2

Maintain a reverse index from `book_id` to its set of `bookmark_id`s so that cascading deletion does not require scanning all bookmarks.

Hint 3

Store an integer `version` field initialized to 1 on each book to implement optimistic concurrency control during updates.

Follow-up questions

  • How would you migrate or adjust bookmark character offsets if book content is edited in-place rather than overwritten completely (e.g., using operational transforms or diff-match-patch)?
  • If the shelf contains millions of books and reordering requires frequent arbitrary insertions, how would you optimize `move_book` to avoid O(N) list shifts?
  • How would you extend this design to support thread-safe read/write operations under high concurrency?