Implement Recipe Storage CRUD
Company: Circle
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: hard
Interview Round: Technical Screen
Quick Answer: This question evaluates the ability to design and implement in-memory data structures and APIs for CRUD operations, covering skills such as ID generation and parsing, string handling for case-insensitive uniqueness constraints, data modeling, and preserving original value casing.
Constraints
- 0 <= len(operations) <= 2 * 10^5
- A valid recipe id is exactly `'recipe'` followed by a positive integer with no leading zeros, such as `'recipe7'`
- The total number of ingredient and step strings processed is at most 2 * 10^5
Examples
Input: [('create', 'Pasta', ['noodles', 'salt'], ['boil', 'serve']), ('get', 'recipe1'), ('update', 'recipe1', {'id': 'recipe1', 'name': 'pasta', 'ingredients': ['noodles', 'salt', 'oil'], 'steps': ['boil', 'mix', 'serve']}), ('get', 'recipe1'), ('delete', 'recipe1'), ('get', 'recipe1')]
Expected Output: ['recipe1', {'id': 'recipe1', 'name': 'Pasta', 'ingredients': ['noodles', 'salt'], 'steps': ['boil', 'serve']}, True, {'id': 'recipe1', 'name': 'pasta', 'ingredients': ['noodles', 'salt', 'oil'], 'steps': ['boil', 'mix', 'serve']}, True, None]
Explanation: The recipe is created, read, updated with a case-only rename, read again with the new casing, deleted, and then no longer found.
Input: [('create', 'PaSta', ['a'], ['b']), ('create', 'pAsTa', ['x'], ['y']), ('create', 'Soup', ['water'], ['heat']), ('get', 'recipe2')]
Expected Output: ['recipe1', None, 'recipe2', {'id': 'recipe2', 'name': 'Soup', 'ingredients': ['water'], 'steps': ['heat']}]
Explanation: The second create fails because the name already exists ignoring case. Since failed creates do not consume ids, `Soup` becomes `recipe2`.
Input: [('create', 'Salad', ['lettuce'], ['mix']), ('get', 'rec1'), ('get', 'recipe0'), ('update', 'recipe2', {'id': 'recipe2', 'name': 'Salad', 'ingredients': ['lettuce', 'oil'], 'steps': ['mix']}), ('delete', 'recipe999')]
Expected Output: ['recipe1', None, None, False, False]
Explanation: `rec1` and `recipe0` are invalid ids, and `recipe2`/`recipe999` do not exist.
Input: [('create', 'Bread', ['flour'], ['bake']), ('update', 'recipe1', {'id': 'recipe2', 'name': 'bread', 'ingredients': ['flour', 'water'], 'steps': ['mix', 'bake']}), ('update', 'recipe1', {'id': 'recipe1', 'name': 'Toast', 'ingredients': ['flour'], 'steps': ['bake']}), ('get', 'recipe1')]
Expected Output: ['recipe1', False, False, {'id': 'recipe1', 'name': 'Bread', 'ingredients': ['flour'], 'steps': ['bake']}]
Explanation: The first update fails because the ids do not match. The second fails because changing `Bread` to `Toast` is not a case-only rename.
Input: [('create', 'Cake', ['flour', 'sugar'], ['mix', 'bake']), ('delete', 'recipe1'), ('create', 'cAkE', ['flour'], ['bake']), ('get', 'recipe2')]
Expected Output: ['recipe1', True, 'recipe2', {'id': 'recipe2', 'name': 'cAkE', 'ingredients': ['flour'], 'steps': ['bake']}]
Explanation: Deleting a recipe frees its case-insensitive name, so the same logical name can be created again later with the next id.
Input: [('create', 'Tea', [], []), ('get', 'recipe1'), ('update', 'recipe1', {'id': 'recipe1', 'name': 'TEA', 'ingredients': [], 'steps': ['steep']}), ('get', 'recipe1')]
Expected Output: ['recipe1', {'id': 'recipe1', 'name': 'Tea', 'ingredients': [], 'steps': []}, True, {'id': 'recipe1', 'name': 'TEA', 'ingredients': [], 'steps': ['steep']}]
Explanation: Empty ingredient and step lists are valid, and a case-only rename updates the stored casing returned by later reads.
Input: []
Expected Output: []
Explanation: With no operations, the result is an empty list.
Hints
- Use two hash maps: one from numeric recipe id to the stored recipe, and one from a normalized name (such as `casefold()`) to the numeric id.
- Before any `get`, `update`, or `delete`, validate and parse the id string. During `update`, compare the old and new names after normalization to allow only case-only changes.