Quick Overview

This question evaluates data-structure design and state-management competencies, covering CRUD, case-insensitive search/sort semantics, user management, and versioning/rollback for an in-memory Recipe Management System in the Coding & Algorithms domain.

Implement a versioned recipe management system

Company: Coinbase

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Online Assessment

You are asked to implement an in-memory **Recipe Management System** (similar in difficulty to a simple banking/in-memory DB exercise). The system supports incremental feature “levels”. Design the data structures and implement the required APIs. ## Data model Assume each **Recipe** has at least: - `recipeId` (unique string or integer) - `name` (string) - `size` (integer; used only for sorting) Assume each **User** has at least: - `userId` (unique) - `userName` No persistence is required (everything is in memory). --- ## Level 1: Basic CRUD for recipes Implement the following operations: - `addRecipe(recipe)`: add a new recipe (reject or overwrite if `recipeId` already exists—state your choice clearly). - `updateRecipe(recipeId, fields...)`: update an existing recipe. - `getRecipe(recipeId)`: return the recipe or “not found”. - `deleteRecipe(recipeId)`: delete an existing recipe. Define expected behavior for edge cases (e.g., updating/deleting a non-existent recipe). --- ## Level 2: Search and list Implement: - `searchRecipes(query)`: search recipes by **name**, **case-insensitive** (define whether it’s exact match or substring match; choose one and be consistent). - `listRecipes(sortBy)`: return all recipes sorted by either: - `name` (lexicographic, case-insensitive), or - `size` (numeric ascending) Specify tie-breaker behavior (e.g., tie-break by `recipeId`). --- ## Level 3: Add user Implement: - `addUser(user)`: add a user (keep it simple—no extra features unless needed). Define behavior on duplicate `userId`. --- ## Level 4: Version history and rollback Add **versioning** to support: - `versionHistory()`: return stored version metadata (at minimum, a monotonic `versionId` and what changed). - `rollback(versionId)`: restore the system state to exactly how it was at that `versionId`. Guidance: treat each mutating operation (e.g., add/update/delete recipe, add user) as producing a new version; maintain whatever additional structures you need (e.g., a `versionMap`). --- ## Constraints / expectations - Assume up to ~100k recipes/users. - Aim for clean APIs and reasonable time complexity. - You may implement this as a class/module with methods in your preferred language.

Quick Answer: This question evaluates data-structure design and state-management competencies, covering CRUD, case-insensitive search/sort semantics, user management, and versioning/rollback for an in-memory Recipe Management System in the Coding & Algorithms domain.

Part 1: Level 1 - Basic CRUD for Recipes

Implement an in-memory recipe store that supports adding, updating, retrieving, and deleting recipes. Each recipe has recipeId, name, and size. This problem uses a batch API: process the operations in order and return one result for each operation. Duplicate addRecipe calls are rejected and return False. Updating or deleting a missing recipe returns False. getRecipe for a missing recipe returns None.

Constraints

  • 0 <= len(operations) <= 100000
  • recipeId is a non-empty string with length at most 64
  • name is a string with length at most 256
  • 0 <= size <= 1000000000
  • fields only needs to support the keys name and size

Examples

Input: ([['addRecipe', 'r1', 'Pancakes', 3], ['getRecipe', 'r1'], ['updateRecipe', 'r1', {'size': 4}], ['getRecipe', 'r1'], ['deleteRecipe', 'r1'], ['getRecipe', 'r1']],)

Expected Output: [True, {'recipeId': 'r1', 'name': 'Pancakes', 'size': 3}, True, {'recipeId': 'r1', 'name': 'Pancakes', 'size': 4}, True, None]

Explanation: The recipe is added, retrieved, updated, retrieved again, deleted, and then is no longer found.

Input: ([['addRecipe', 'r1', 'Soup', 2], ['addRecipe', 'r1', 'Salad', 1], ['getRecipe', 'r1']],)

Expected Output: [True, False, {'recipeId': 'r1', 'name': 'Soup', 'size': 2}]

Explanation: Duplicate recipe IDs are rejected, so the original recipe remains unchanged.

Hints

  1. Use a hash map keyed by recipeId so that CRUD operations are average O(1).
  2. When returning a recipe, return a copy rather than the internal object to avoid accidental mutation.

Part 2: Level 2 - Search and List Recipes

Extend the in-memory recipe store from Level 1 with search and sorted listing. The CRUD behavior is the same as in Part 1: duplicate addRecipe is rejected, missing updateRecipe and deleteRecipe return False, and missing getRecipe returns None. searchRecipes performs a case-insensitive substring search on recipe names. listRecipes returns all recipes sorted either by case-insensitive name or by numeric size. Ties are broken by recipeId ascending.

Constraints

  • 0 <= len(operations) <= 100000
  • recipeId is a non-empty string with length at most 64
  • name and query are strings with length at most 256
  • 0 <= size <= 1000000000
  • sortBy is either name or size

Examples

Input: ([['addRecipe', 'r1', 'Apple Pie', 5], ['addRecipe', 'r2', 'banana Bread', 3], ['addRecipe', 'r3', 'Pineapple Tart', 4], ['searchRecipes', 'apple']],)

Expected Output: [True, True, True, [{'recipeId': 'r1', 'name': 'Apple Pie', 'size': 5}, {'recipeId': 'r3', 'name': 'Pineapple Tart', 'size': 4}]]

Explanation: The query apple matches Apple Pie and Pineapple Tart case-insensitively.

Input: ([['addRecipe', 'r2', 'banana', 2], ['addRecipe', 'r1', 'Apple', 2], ['addRecipe', 'r3', 'apple', 1], ['listRecipes', 'size'], ['listRecipes', 'name']],)

Expected Output: [True, True, True, [{'recipeId': 'r3', 'name': 'apple', 'size': 1}, {'recipeId': 'r1', 'name': 'Apple', 'size': 2}, {'recipeId': 'r2', 'name': 'banana', 'size': 2}], [{'recipeId': 'r1', 'name': 'Apple', 'size': 2}, {'recipeId': 'r3', 'name': 'apple', 'size': 1}, {'recipeId': 'r2', 'name': 'banana', 'size': 2}]]

Explanation: Size sorting is numeric ascending with recipeId tie-breaks. Name sorting is case-insensitive, so Apple and apple tie and are ordered by recipeId.

Hints

  1. Normalize names with lower() for both searching and name sorting.
  2. Keep the main storage as a dictionary, then sort a list of current recipe records only when searchRecipes or listRecipes is called.

Part 3: Level 3 - Add User Support

Extend the Level 2 recipe system with basic user creation. The recipe CRUD, search, and list behavior is unchanged. Add a separate user store supporting addUser. Duplicate userId values are rejected and return False. User IDs and recipe IDs are separate namespaces, so a recipeId may equal a userId without conflict.

Constraints

  • 0 <= len(operations) <= 100000
  • recipeId and userId are non-empty strings with length at most 64
  • recipe name, userName, and query are strings with length at most 256
  • 0 <= size <= 1000000000
  • sortBy is either name or size

Examples

Input: ([['addUser', 'u1', 'Ann'], ['addUser', 'u1', 'Annie'], ['addRecipe', 'r1', 'Stew', 4], ['listRecipes', 'name']],)

Expected Output: [True, False, True, [{'recipeId': 'r1', 'name': 'Stew', 'size': 4}]]

Explanation: The duplicate user is rejected, while recipe operations continue normally.

Input: ([['addRecipe', 'u1', 'Recipe Named Like User', 7], ['addUser', 'u1', 'Bob'], ['addUser', 'u1', 'Bobby'], ['getRecipe', 'u1']],)

Expected Output: [True, True, False, {'recipeId': 'u1', 'name': 'Recipe Named Like User', 'size': 7}]

Explanation: Recipe IDs and user IDs are separate namespaces.

Hints

  1. Keep users and recipes in separate dictionaries.
  2. Do not let addUser affect recipe search, list, get, update, or delete behavior.

Part 4: Level 4 - Version History and Rollback

Extend the Level 3 system with versioning. The initial empty state is version 0 with change initial. Each successful addRecipe, updateRecipe, deleteRecipe, addUser, and valid rollback creates a new monotonic version. Failed operations, such as duplicate adds or deleting a missing recipe, do not create versions. versionHistory returns metadata for all created versions. rollback(versionId) restores recipes and users to the exact data state represented by that versionId, then records the rollback itself as a new version whose data state equals the target version.

Constraints

  • 0 <= len(operations) <= 100000
  • recipeId and userId are non-empty strings with length at most 64
  • recipe name, userName, and query are strings with length at most 256
  • 0 <= size <= 1000000000
  • The number of versions is at most the number of successful mutating operations plus valid rollbacks plus one

Examples

Input: ([['addRecipe', 'r1', 'Soup', 2], ['updateRecipe', 'r1', {'name': 'Tomato Soup'}], ['getRecipe', 'r1'], ['rollback', 1], ['getRecipe', 'r1'], ['versionHistory']],)

Expected Output: [True, True, {'recipeId': 'r1', 'name': 'Tomato Soup', 'size': 2}, True, {'recipeId': 'r1', 'name': 'Soup', 'size': 2}, [{'versionId': 0, 'change': 'initial'}, {'versionId': 1, 'change': 'addRecipe:r1'}, {'versionId': 2, 'change': 'updateRecipe:r1'}, {'versionId': 3, 'change': 'rollback:1'}]]

Explanation: Rolling back to version 1 restores the original recipe name and creates version 3.

Input: ([['addRecipe', 'r1', 'Bread', 3], ['deleteRecipe', 'r1'], ['getRecipe', 'r1'], ['rollback', 1], ['getRecipe', 'r1']],)

Expected Output: [True, True, None, True, {'recipeId': 'r1', 'name': 'Bread', 'size': 3}]

Explanation: A deleted recipe can be restored by rolling back to a version where it existed.

Hints

  1. Represent each version as a node with a parent pointer and the single delta that created it.
  2. To rollback, undo deltas from the current version up to the lowest common ancestor with the target, then apply deltas down to the target.

Loading coding console...