Implement a Multi-Level Recipe Management Service

Quick Overview

Implement an in-memory recipe service that grows from basic CRUD into deterministic search, sorting, and user-initiated edits. The task evaluates normalization, secondary-index consistency, defensive copying, monotonic identifiers, atomic validation, and mixed-operation testing.

Implement a Multi-Level Recipe Management Service

Company: Airbnb

Role: Software Engineer

Category: Software Engineering Fundamentals

Difficulty: medium

Interview Round: Take-home Project

## Implement a Multi-Level Recipe Management Service Design and implement an in-memory recipe manager. A recipe stores a name, an ordered ingredient list, and an ordered step list. Complete the following three levels while preserving the behavior from earlier levels. For this practice contract, recipe names and ingredient comparisons use one documented case-insensitive normalization. Recipe IDs are `recipe1`, `recipe2`, and so on; the numeric sequence advances only after a successful creation and deleted IDs are never reused. “Recipe ID order” means ascending numeric suffix. ### Part 1 — Create, Read, Update, and Delete Recipes Implement these operations: - `addRecipe(name, ingredients, steps) -> recipeId | null`: create a recipe unless another current recipe has the same normalized name. - `getRecipe(recipeId) -> values`: return `[name, ingredientsJoined, stepsJoined]`, where each list is joined with commas in its stored order; return an empty list when the ID does not exist. - `updateRecipe(recipeId, name, ingredients, steps) -> updated`: replace an existing recipe unless the normalized name belongs to a different recipe. - `deleteRecipe(recipeId) -> deleted`: delete an existing recipe and return whether anything was removed. #### What This Part Should Cover - A primary map by recipe ID and a consistent uniqueness index by normalized name. - Input-list copies so callers cannot mutate stored recipes indirectly. - Conflict checks that allow a recipe to retain its own name. - Correct cleanup of every index on update and deletion. ```hint Treat the name index as state, not a search shortcut Every successful create, rename, or delete must leave the primary records and normalized-name ownership in agreement. ``` ### Part 2 — Search and Sort Recipes Implement: - `searchRecipesByIngredient(ingredient) -> recipeIds`: return recipes containing a case-insensitive ingredient match, sorted by ingredient-list length and then recipe ID. - `listRecipes(sortBy) -> recipeIds`: for `ingredient_count`, sort by ingredient-list length and then recipe ID; for `name`, sort by normalized name and then recipe ID. An unsupported `sortBy` value defaults to `name`. Ingredient-list length counts stored entries. Search tests whether at least one stored entry matches; it does not return a recipe more than once. #### What This Part Should Cover - Case-insensitive membership under the same normalization contract. - Deterministic secondary ordering by the recipe ID's numeric suffix. - Preservation of ingredient and step order in the stored recipe. - Either a correct scan or indexes that are updated atomically with recipes. ```hint Separate filtering from ordering First identify each matching recipe once, then apply the complete two-key comparator to the results. ``` ### Part 3 — Add Users and User-Initiated Edits Implement: - `addUser(userId) -> added`: add an exact, case-sensitive user ID unless it already exists. - `editRecipe(userId, recipeId, newName, newIngredients, newSteps) -> edited`: any registered user may edit any existing recipe. Return `false` for a missing user, missing recipe, or normalized-name conflict with another recipe; otherwise apply the update and return `true`. #### What This Part Should Cover - A user registry independent of recipe ownership. - Reuse of the same recipe-update validation and index maintenance. - No partial mutation when any precondition fails. - Clear behavior when the recipe keeps its current normalized name. ```hint Use one update path The direct update and user-initiated edit should not implement name-conflict and index rules differently. ``` ### Part 4 — Preserve Invariants Across Levels Explain the invariants, complexity, and tests needed for mixed operation sequences. #### What This Part Should Cover - One live record per recipe ID and one owner per normalized recipe name. - Monotonic, non-reused IDs even after deletion. - Immutable snapshots or defensive copies at the API boundary. - Tests that interleave rename, delete, search, sort, user edits, and failed operations. ```hint Test transitions, not isolated methods A rename followed by reuse of the old name and deletion of the new owner exposes index bugs that single-operation tests miss. ``` ### What a Strong Answer Covers - Exact return values and deterministic ordering for every operation. - One normalization policy used consistently for uniqueness and ingredient search. - Atomic maintenance of recipe records and secondary indexes. - Reused validation logic, defensive copying, and stateful regression tests across all three levels. ### Follow-up Questions 1. Which operations change if recipe names must be versioned rather than updated in place? 2. How would you add an ingredient index without making rename and delete inconsistent? 3. What should happen if two requests concurrently try to claim the same normalized name? 4. How would you serialize and restore the service while preserving the next recipe ID?

Quick Answer: Implement an in-memory recipe service that grows from basic CRUD into deterministic search, sorting, and user-initiated edits. The task evaluates normalization, secondary-index consistency, defensive copying, monotonic identifiers, atomic validation, and mixed-operation testing.

|Home/Software Engineering Fundamentals/Airbnb
Airbnb logo
Airbnb
Jul 25, 2026, 12:00 AM
mediumSoftware EngineerTake-home ProjectSoftware Engineering Fundamentals
2
0

Implement a Multi-Level Recipe Management Service

Design and implement an in-memory recipe manager. A recipe stores a name, an ordered ingredient list, and an ordered step list. Complete the following three levels while preserving the behavior from earlier levels.

For this practice contract, recipe names and ingredient comparisons use one documented case-insensitive normalization. Recipe IDs are recipe1, recipe2, and so on; the numeric sequence advances only after a successful creation and deleted IDs are never reused. “Recipe ID order” means ascending numeric suffix.

Part 1 — Create, Read, Update, and Delete Recipes

Implement these operations:

  • addRecipe(name, ingredients, steps) -> recipeId | null : create a recipe unless another current recipe has the same normalized name.
  • getRecipe(recipeId) -> values : return [name, ingredientsJoined, stepsJoined] , where each list is joined with commas in its stored order; return an empty list when the ID does not exist.
  • updateRecipe(recipeId, name, ingredients, steps) -> updated : replace an existing recipe unless the normalized name belongs to a different recipe.
  • deleteRecipe(recipeId) -> deleted : delete an existing recipe and return whether anything was removed.

What This Part Should Cover Guidance

  • A primary map by recipe ID and a consistent uniqueness index by normalized name.
  • Input-list copies so callers cannot mutate stored recipes indirectly.
  • Conflict checks that allow a recipe to retain its own name.
  • Correct cleanup of every index on update and deletion.

Part 2 — Search and Sort Recipes

Implement:

  • searchRecipesByIngredient(ingredient) -> recipeIds : return recipes containing a case-insensitive ingredient match, sorted by ingredient-list length and then recipe ID.
  • listRecipes(sortBy) -> recipeIds : for ingredient_count , sort by ingredient-list length and then recipe ID; for name , sort by normalized name and then recipe ID. An unsupported sortBy value defaults to name .

Ingredient-list length counts stored entries. Search tests whether at least one stored entry matches; it does not return a recipe more than once.

What This Part Should Cover Guidance

  • Case-insensitive membership under the same normalization contract.
  • Deterministic secondary ordering by the recipe ID's numeric suffix.
  • Preservation of ingredient and step order in the stored recipe.
  • Either a correct scan or indexes that are updated atomically with recipes.

Part 3 — Add Users and User-Initiated Edits

Implement:

  • addUser(userId) -> added : add an exact, case-sensitive user ID unless it already exists.
  • editRecipe(userId, recipeId, newName, newIngredients, newSteps) -> edited : any registered user may edit any existing recipe. Return false for a missing user, missing recipe, or normalized-name conflict with another recipe; otherwise apply the update and return true .

What This Part Should Cover Guidance

  • A user registry independent of recipe ownership.
  • Reuse of the same recipe-update validation and index maintenance.
  • No partial mutation when any precondition fails.
  • Clear behavior when the recipe keeps its current normalized name.

Part 4 — Preserve Invariants Across Levels

Explain the invariants, complexity, and tests needed for mixed operation sequences.

What This Part Should Cover Guidance

  • One live record per recipe ID and one owner per normalized recipe name.
  • Monotonic, non-reused IDs even after deletion.
  • Immutable snapshots or defensive copies at the API boundary.
  • Tests that interleave rename, delete, search, sort, user edits, and failed operations.

What a Strong Answer Covers Guidance

  • Exact return values and deterministic ordering for every operation.
  • One normalization policy used consistently for uniqueness and ingredient search.
  • Atomic maintenance of recipe records and secondary indexes.
  • Reused validation logic, defensive copying, and stateful regression tests across all three levels.

Follow-up Questions Guidance

  1. Which operations change if recipe names must be versioned rather than updated in place?
  2. How would you add an ingredient index without making rename and delete inconsistent?
  3. What should happen if two requests concurrently try to claim the same normalized name?
  4. How would you serialize and restore the service while preserving the next recipe ID?
Loading comments...