Reported September 2026
Decagondesign

Versioned Recipe Shopping Cart

Reported by candidates from Decagon's online assessment. Pattern, common pitfall, and the honest play if you blank under the timer.

Get StealthCoderRuns invisibly during the live Decagon OA. Under 2s to a working solution.
Founder's read

Decagon's Versioned Recipe Shopping Cart, reported in September 2026, looks like a cute design problem and punishes anyone who treats it that way. It's a cart with undo-style versioning, ingredient counts, and threshold discounts. If you've got an OA in the next few days, the whole question hinges on what checkout does to the history. Get that wrong and examples 2 and 3 fail. StealthCoder sits invisibly on your screen as a safety net if you blank mid-assessment, but the idea here is simple enough to hold in your head before you start.

The problem

You are building a shopping cart for a cooking app. Each recipe has a unique name and a list of ingredients. The cart contains at most one copy of each recipe.
A bulk-discount rule names an ingredient, a minimum quantity, and a discount amount. Count one unit of an ingredient for every cart recipe whose ingredient list contains it. A rule contributes its discount amount exactly once when the current count of its ingredient is at least its minimum quantity. The total discount is the sum contributed by all qualifying rules.
The cart starts empty at version 0. Process the rows in operations in order:
["add_recipe", recipeName] adds an absent recipe and creates the next version.
["remove_recipe", recipeName] removes a present recipe and creates the next version.
["get_total_discounts"] appends the current total discount to the answer without creating a version.
["get_version"] appends the current version number to the answer without creating a version.
["checkout", version] restores the cart to the state at that version and permanently discards every later version. It does not itself create a version. The next add or remove operation creates version + 1 on the new history branch.
Return the results of all get_total_discounts and get_version operations in encounter order.
Store compact add/remove history rather than a complete cart snapshot for every version. A checkout may reconstruct its selected state by replaying the retained history prefix.

Function
processCart(recipeNames: String[], recipeIngredients: String[][], discountedIngredients: String[], discountThresholds: int[], discountAmounts: int[], operations: String[][]) → int[]

Examples
Example 1
recipeNames = ["A","B","C"]
recipeIngredients = [["Chicken","Garlic"],["Garlic","Rice"],["Chicken"]]
discountedIngredients = ["Chicken","Garlic"]
discountThresholds = [2,2]
discountAmounts = [5,3]
operations = [["add_recipe","A"],["get_version"],["add_recipe","B"],["get_total_discounts"],["checkout","1"],["get_version"],["get_total_discounts"]]
return = [1,3,1,0]
Adding A creates version 1. Adding B creates version 2, where Garlic occurs twice and contributes 3. Checkout restores version 1, so only A remains and no discount rule qualifies.
Example 2
recipeNames = ["A","B","C"]
recipeIngredients = [["Chicken","Garlic"],["Garlic","Rice"],["Chicken"]]
discountedIngredients = ["Chicken","Garlic"]
discountThresholds = [2,2]
discountAmounts = [5,3]
operations = [["add_recipe","A"],["add_recipe","B"],["remove_recipe","A"],["get_total_discounts"],["get_version"],["checkout","2"],["get_version"],["add_recipe","C"],["get_total_discounts"],["get_version"]]
return = [0,3,2,8,3]
After removing A, only B remains at version 3, so the total discount is 0. Checkout to version 2 restores A and B. Adding C replaces the discarded branch with a new version 3; both discount rules then qualify for a total of 8.
Example 3
recipeNames = ["A","B"]
recipeIngredients = [["Garlic"],["Garlic"]]
discountedIngredients = ["Garlic"]
discountThresholds = [2]
discountAmounts = [4]
operations = [["add_recipe","A"],["add_recipe","B"],["get_total_discounts"],["checkout","0"],["get_version"],["add_recipe","B"],["get_total_discounts"]]
return = [4,0,0]
At version 2, two recipes contribute Garlic, so the discount is 4. Checkout to version 0 empties the cart and discards both later versions. Adding B creates a new version 1, where the threshold is not met.

Constraints
1 <= recipeNames.length == recipeIngredients.length <= 2000.
Recipe names are unique non-empty strings.
1 <= recipeIngredients[i].length <= 20, and each recipe lists an ingredient at most once.
discountedIngredients.length == discountThresholds.length == discountAmounts.length.
0 <= discountedIngredients.length <= 2000, and discounted ingredient names are unique and occur in the recipe catalog.
2 <= discountThresholds[i] <= recipeNames.length.
1 <= discountAmounts[i] <= 10^6, and their sum is at most 10^9.
1 <= operations.length <= 2000.
Every add names a known recipe that is absent from the current cart, and every remove names a recipe that is present.
Every checkout names an integer version from 0 through the current version.
At least one operation is get_total_discounts or get_version.
Do not retain a complete cart snapshot for every version; use space linear in the recipe catalog, current cart state, and compact mutation history.

Reported by candidates. Source: FastPrep

Pattern and pitfall

The trick is a history log plus an ingredient count map. Keep a list of operations (add or remove, with recipe) where index i produces version i+1. Maintain a hash map of ingredient to count, and a running discount total. On add or remove, update counts for that recipe's ingredients only, and adjust the total when a count crosses a rule's threshold up or down. That's O(20) per operation. The naive trap is checkout. Version v means the log has exactly v entries, so you truncate the log to length v and rebuild state by replaying the prefix. Then the next add lands at version v+1 and the old branch is gone. Don't keep stale versions around, and don't increment the version on checkout or on the get calls. With 2000 operations, replaying on every checkout is fine. If you freeze live, StealthCoder is the hedge, but write the log-truncate-replay first.

The honest play: practice the pattern, and have StealthCoder ready for the one you didn't see coming.

If this hits your live OA

You can drill Versioned Recipe Shopping Cart cold, or you can hedge it. StealthCoder runs invisibly during screen share and surfaces a working solution in under 2 seconds. The proctor sees the IDE. They don't see what's behind it. Built for the candidate who saw this exact problem leak two days before his OA and wondered if anyone had a play.

Get StealthCoder

Related leaked OAs

⏵ The honest play

You've seen the question. Make sure you actually pass Decagon's OA.

Decagon reuses patterns across OAs. Built for the candidate who saw this exact problem leak two days before his OA and wondered if anyone had a play. Works on HackerRank, CodeSignal, CoderPad, and Karat.

Versioned Recipe Shopping Cart FAQ

What's the trick in the Decagon Versioned Recipe Shopping Cart problem?+

Store a compact log of add and remove operations, where the log length equals the current version. Checkout truncates the log to that version and replays it to rebuild the ingredient counts. Everything else is a hash map of counts and a running discount sum.

How hard is this OA question really?+

Medium. No fancy algorithm is involved. The difficulty is careful state handling: checkout discarding later versions, get calls not creating versions, and each discount rule counting once. Candidates usually lose points on checkout semantics, not on complexity.

Do I need to snapshot the cart at every version?+

No, and the statement says to avoid it. Keep only the add/remove history. On checkout, cut the history to the target length and replay it from an empty cart. With at most 2000 operations and 20 ingredients per recipe, replay is cheap.

How should I compute the total discount efficiently?+

Map each discounted ingredient to its threshold and amount. When a recipe is added or removed, adjust each of its ingredient counts by one. If a count crosses its threshold upward, add the amount. If it drops below, subtract it. Query then returns the running total in O(1).

How do I prepare for this in 48 hours?+

Hand-trace examples 2 and 3 until checkout feels automatic. Then write the log, count map, and replay function from scratch once. Test checkout to version 0, checkout to the current version, and an add right after a checkout. Those cases catch most bugs.

Problem reported by candidates from a real Online Assessment. Sourced from a publicly-available candidate-aggregated repository. Not affiliated with Decagon.

OA at Decagon?
Invisible during screen share
Get it