Module 3: The Opportunity Solution Tree
Mini-project: Mercado's recommendations opportunity solution tree
Overview
This module's seven lessons built Mercado's tree bit by bit: the four-level shape (lesson 2), a verified outcome at the root (lesson 3), the criterion for telling real opportunities apart from disguised solutions (lesson 4), solutions properly hung with no orphans (lesson 5), experiments as a candidate menu (lesson 6), and a criterion for branching and pruning (lesson 7). This mini-project brings the four pieces together —looksLikeSolution, validateTree, pruneTree— into a single file, and adds the final missing piece: a decision. Not just "the tree is well-formed," but "this week, the team is going to tackle this opportunity, with this solution, tested with this experiment first."
How this connects to the module. There's no new model in this project — validateTree() and pruneTree() are exactly the same functions from lessons 5 and 7, unchanged. What the project adds is the full, end-to-end assembly, and the final decision every previous lesson left pending for the next: what to build first, backed by what evidence.
An analogy: the complete puzzle, assembled for the first time
Every lesson in this module gave you a different puzzle piece: the shape (lesson 2), the root (lesson 3), the opportunity filter (lesson 4), the solution branches (lesson 5), the experiment leaves (lesson 6), the pruning (lesson 7). Having them all separately, each verified in its own lesson, isn't the same as seeing them together, forming the complete picture. This project is the moment to put all the pieces on the table at once — and, once the full puzzle is assembled, point a finger exactly at which part of the picture the team is going to build first.
The reference solution, verified
Part 1 — Assemble and validate the full tree
We reuse validateTree() exactly as it stood in lesson 5, with no changes at all, and build this week's full tree: four real opportunities, discovered in module 2's interviews — product discovery (the one directly underpinning the recommendations bet), trust in new sellers, forgotten carts, and price comparison —, plus the distractor someone from the business team slipped in at the last planning meeting ("add a one-click buy button"), and the orphaned idea that arrived over Slack ("redesign the Mercado logo"). We add each opportunity's RICE impact, inherited from product-thinking-for-engineers.
// Part 1: the same validateTree from lesson 5, with no changes at all.
// We assemble, in a single file, Mercado's full tree with this week's
// real interview notes -- including the distractor someone from the
// business team slipped into the meeting ("add a button...") and the
// orphaned idea that arrived over Slack ("redesign the logo").
const SOLUTION_VERBS = ['add', 'show', 'build', 'create', 'redesign', 'implement', 'send', 'put', 'launch', 'design'];
function looksLikeSolution(text) {
const firstWord = text.trim().split(' ')[0].toLowerCase();
return SOLUTION_VERBS.includes(firstWord);
}
function validateTree(tree) {
const lines = [];
const flags = [];
lines.push('outcome: "' + tree.outcome + '"');
tree.opportunities.forEach((opp) => {
const suspicious = looksLikeSolution(opp.problem);
lines.push('');
lines.push('opportunity: "' + opp.problem + '"' + (suspicious ? ' [SUSPICIOUS]' : ''));
if (suspicious) flags.push('opportunity "' + opp.problem + '" looks like a solution disguised as a problem');
opp.solutions.forEach((sol) => {
lines.push(' solution "' + sol.idea + '" -> opportunity "' + opp.problem + '" -> outcome "' + tree.outcome + '": valid');
if (sol.experiments.length === 0) {
lines.push(' (no experiments defined yet)');
} else {
sol.experiments.forEach((exp) => lines.push(' experiment: "' + exp + '"'));
}
});
});
(tree.orphanSolutionIdeas || []).forEach((idea) => {
lines.push('');
lines.push('solution "' + idea + '" -> (no opportunity): ORPHANED');
flags.push('solution "' + idea + '" is not hanging off any real opportunity');
});
return { structure: lines.join('\n'), flags };
}
// pruneTree: exactly the same function from lesson 7, unchanged.
function pruneTree(tree, keep) {
const sorted = [...tree.opportunities].sort((a, b) => b.impact - a.impact);
return { outcome: tree.outcome, opportunities: sorted.slice(0, keep) };
}
const mercadoTree = {
outcome: '+GMV via checkout conversion',
opportunities: [
{
problem: "I don't discover products I'd like without searching for the exact name",
impact: 5,
solutions: [
{ idea: 'personalized recommendations on the home page and checkout', experiments: ["fake door: 'see my recommendations' button at checkout", 'clickable prototype with 20 buyers'] },
{ idea: 'hand-curated categories by trend', experiments: ['category landing page prototype with 10 buyers'] },
],
},
{
problem: "I don't trust new sellers without reviews",
impact: 3,
solutions: [
{ idea: 'verified-seller badge on the product page', experiments: ['clickable prototype of the product page with the badge'] },
],
},
{
problem: 'I forget about my cart and never go back to finish it',
impact: 4,
solutions: [
{ idea: 'abandoned-cart email reminder', experiments: ['wizard-of-oz: send the reminder by hand to 30 users'] },
],
},
{
problem: 'add a one-click buy button',
impact: 2,
solutions: [
{ idea: 'one-click buy button on the product page', experiments: [] },
],
},
{
problem: 'comparing prices between similar products takes me a long time',
impact: 3,
solutions: [
{ idea: 'comparison table between similar products', experiments: ['clickable prototype of the comparison table', 'fake door: compare button on the product page'] },
],
},
],
orphanSolutionIdeas: ['redesign the Mercado logo'],
};
console.log('=== Part 1: Mercado\'s full tree, validated ===\n');
const result = validateTree(mercadoTree);
console.log(result.structure);
console.log('\n=== flags ===');
console.log(result.flags.length === 0 ? 'none' : result.flags.map((f) => '- ' + f).join('\n'));
console.log('\n\n=== Part 2: pruned to the 2 highest-impact opportunities ===\n');
const pruned = pruneTree(mercadoTree, 2);
pruned.opportunities.forEach((opp) => console.log('impact ' + opp.impact + ' -- "' + opp.problem + '"'));
What to expect. When you run the full file with Node, the output is exactly this:
=== Part 1: Mercado's full tree, validated ===
outcome: "+GMV via checkout conversion"
opportunity: "I don't discover products I'd like without searching for the exact name"
solution "personalized recommendations on the home page and checkout" -> opportunity "I don't discover products I'd like without searching for the exact name" -> outcome "+GMV via checkout conversion": valid
experiment: "fake door: 'see my recommendations' button at checkout"
experiment: "clickable prototype with 20 buyers"
solution "hand-curated categories by trend" -> opportunity "I don't discover products I'd like without searching for the exact name" -> outcome "+GMV via checkout conversion": valid
experiment: "category landing page prototype with 10 buyers"
opportunity: "I don't trust new sellers without reviews"
solution "verified-seller badge on the product page" -> opportunity "I don't trust new sellers without reviews" -> outcome "+GMV via checkout conversion": valid
experiment: "clickable prototype of the product page with the badge"
opportunity: "I forget about my cart and never go back to finish it"
solution "abandoned-cart email reminder" -> opportunity "I forget about my cart and never go back to finish it" -> outcome "+GMV via checkout conversion": valid
experiment: "wizard-of-oz: send the reminder by hand to 30 users"
opportunity: "add a one-click buy button" [SUSPICIOUS]
solution "one-click buy button on the product page" -> opportunity "add a one-click buy button" -> outcome "+GMV via checkout conversion": valid
(no experiments defined yet)
opportunity: "comparing prices between similar products takes me a long time"
solution "comparison table between similar products" -> opportunity "comparing prices between similar products takes me a long time" -> outcome "+GMV via checkout conversion": valid
experiment: "clickable prototype of the comparison table"
experiment: "fake door: compare button on the product page"
solution "redesign the Mercado logo" -> (no opportunity): ORPHANED
=== flags ===
- opportunity "add a one-click buy button" looks like a solution disguised as a problem
- solution "redesign the Mercado logo" is not hanging off any real opportunity
=== Part 2: pruned to the 2 highest-impact opportunities ===
impact 5 -- "I don't discover products I'd like without searching for the exact name"
impact 4 -- "I forget about my cart and never go back to finish it"
Part 1 confirms something reassuring: of the five entries in opportunities, four are real, well-formed opportunities, with solutions hanging correctly off them. Only the planning-meeting distractor comes out marked [SUSPICIOUS], and only the Slack idea comes out ORPHANED — exactly the two cases an attentive team should catch and leave out of this week's serious conversation. Part 2 sails right past that suspicious branch without anyone having to discuss it explicitly: with impact: 2, it doesn't even compete for the top 2 —although, as you learned in lesson 7, that's a coincidence of this example, not a general guarantee; you always have to look at both checks.
Part 3 — The decision: which branch to tackle, with which experiment first
Here the project leaves arithmetic behind and asks for judgment — the same kind of reasoning you're going to formalize with rigor in module 4, with pickTest.
The pruned top 2 leaves two opportunities: "I don't discover products I'd like..." (impact 5) and "I forget about my cart..." (impact 4). The first wins with the higher impact, and not by accident — it's, literally, the opportunity underpinning the recommendations bet since product-thinking-for-engineers, the same one that was left with tested: false and RICE's confidence capped at 0.3 at that guide's close. We pick that opportunity as this week's priority.
Within that opportunity there are two candidate solutions: "personalized recommendations on the home page and checkout" and "hand-curated categories by trend." We pick the first — not because the second is bad (it stays in the tree, available if the first doesn't work out), but because it's specifically the bet the team already prioritized with RICE in the previous guide, with a score of risk × impact higher than any untested alternative.
And within that solution there are two candidate experiments: the fake door at checkout, and the clickable prototype with 20 buyers. This week's decision is to run the fake door first — it's cheaper and faster to set up than a full clickable prototype, and it measures a real behavior signal (real clicks, in the real checkout flow) instead of a controlled group's reaction to a prototype. The clickable prototype remains the next step, useful mainly if the fake door gives an ambiguous signal and it becomes necessary to observe in more detail how people interact with the idea, not just whether they're interested in it.
This decision —cheap first, more expensive later, always choosing the one that best answers the specific question at stake, not just the most convenient one— is exactly the reasoning module 4 is going to formalize with a complete algorithm: pickTest(assumption, candidateTests).
Common mistakes
Treating today's tree as a final, closed deliverable. What happens: after running validateTree() and pruneTree() once, the team files the result away as if it were the tree's definitive version, with no future update planned. Why it happens: a validated, pruned tree, with a clear decision at the end, feels like finished work — and in a sense it is, for this week. How to spot it: ask when the tree's next review is planned — if the answer is "I don't know" or "whenever it's needed," the tree is probably going to stay frozen. How to fix it: remember lesson 1 — the tree is a living artifact. The next round of interviews (module 2) can bring a new opportunity; the result of today's chosen experiment (modules 4 and 7) is going to shift the branches' priority. Plan to revisit it, not just to build it once.
Skipping validateTree() and pruneTree(), and deciding straight from gut instinct. What happens: someone on the team, with good product intuition, directly proposes "let's tackle recommendations this week" without going through the tree's full assembly and validation. Why it happens: when the "right" answer seems obvious ahead of time, running the full process feels like an unnecessary formality. How to spot it: the team's decision happens to match what the full process would have produced — but nobody can explain, with the tree in front of them, why that was the right choice and not another. How to fix it: even when intuition is right almost every time, the process's value isn't just reaching the right answer — it's being able to defend it, and being able to catch the cases (like this lesson's distractor) where intuition would have gotten it wrong without the explicit check.
Confusing "we picked the fake door" with "we've already designed the experiment". What happens: after Part 3's decision, the team treats the fake door as if it were already fully specified —ready to build— when in reality only what type of experiment to run got decided, not its design details. Why it happens: Part 3 of this project feels like the end of a process, and it's easy to forget it's actually the starting point for the next module. How to spot it: if you ask the team what exact hypothesis the fake door is going to test, or what click rate would count as a sufficient signal, the answer doesn't exist yet. How to fix it: this lesson's choice —"fake door, not prototype, for this solution"— is the input module 4 needs to design the experiment with rigor: turning the assumption into a falsifiable hypothesis, and precisely defining what result counts as evidence.
Exercises
Exercise 1 — Add a new opportunity from this week. Imagine a new round of interviews (module 2) brought this note: "I don't know if a product is still available when I add it to my cart." Apply looksLikeSolution() by hand to confirm whether it's a valid opportunity, and if it is, add it to the tree with at least one solution and one candidate experiment, in the correct format.
See solution
The note doesn't start with any of the SOLUTION_VERBS verbs — it passes the check, it's a valid opportunity. A reasonable entry for the tree:
{
problem: "I don't know if a product is still available when I add it to my cart",
impact: 4,
solutions: [
{
idea: 'validate stock in real time when adding to cart',
experiments: ['clickable prototype simulating a "low stock" warning when adding to cart'],
},
],
}
With a reasonable impact: 4, this new opportunity would tie with "I forget about my cart..." for second place in the pruned top 2 — a good exercise in how a new interview can literally change next week's priority.
Exercise 2 — Recalculate the pruning with all five real opportunities. Without running Node, if pruneTree(mercadoTree, 4) instead of 2 — keeping the four highest-impact opportunities—, which four would survive, and which of the five original entries (counting the suspicious one) would fall out?
See solution
Sorting the five by impact from highest to lowest: 5 (product discovery), 4 (forgotten carts), 3 (trust in sellers), 3 (price comparison), 2 (the "add a button..." distractor). The top 4 keeps the first four —exactly the tree's four real opportunities—, and the only one that falls out is, precisely, the suspicious one. This result isn't a coincidence unique to this exercise: it reinforces lesson 7's warning that the distractor's low impact is a useful coincidence of this case, not a general guarantee — in a different tree, a disguised opportunity could have high impact and survive the pruning without validateTree()'s check.
Exercise 3 — Write the sprint close-out message. In 4-5 sentences, addressed to the rest of Mercado's team, write the message summarizing this lesson's decision: which opportunity was chosen, which solution, which experiment runs first, and why.
See solution
A reasonable message: "We assembled the full discovery tree with this week's five notes. Two didn't pass the check —a one-click buy button turned out to be a solution disguised as an opportunity, and redesigning the logo arrived with no real opportunity behind it—, so we left them out of this week's conversation. Of the four real opportunities, the highest-impact one is still that buyers don't discover products they'd like without searching for the exact name — the same one underpinning our recommendations bet. We're going to tackle it with the personalized recommendations solution (not curated categories, which stays as plan B), and the first experiment is going to be a fake door at checkout — cheaper and faster than the clickable prototype, and with a real behavior signal. That experiment's detailed design —what would count as sufficient evidence— is the next step." The message works because it doesn't hide the two discarded branches — it names them explicitly, with the reason they were left out.
Summary and next step
In this mini-project you assembled, end to end, Mercado's full opportunity solution tree: five entries at the opportunity level, of which four passed validateTree()'s two checks and one came out marked [SUSPICIOUS]; one orphaned idea detected and left out; and, with pruneTree(), a top 2 by impact confirming what you'd already suspected since product-thinking-for-engineers — the product-discovery opportunity, the one underpinning recommendations, is this week's priority. And you made the decision that closes the module: tackle that opportunity with the recommendations solution, tested first with a fake door at checkout.
With this, you close module 3. You now have a complete tree, validated and pruned, with a specific branch chosen and a type of experiment decided — but not yet designed with rigor. That design —turning "users are going to buy more if they see personalized recommendations" into a falsifiable hypothesis, and choosing, with an algorithm, the cheapest test among several candidates that can truly refute it— is exactly module 4's job, which picks up right where this lesson leaves off: with a solution and a type of experiment already chosen, ready to be seriously designed.
Resources
- Teresa Torres, Continuous Discovery Habits — producttalk.org/continuous-discovery-habits. Revisit it as the module closes: the full book on how a team maintains, week after week, exactly the kind of living tree you assembled today. In English.
- Product Talk, "Opportunity Solution Trees: Visualize Your Discovery to Stay Aligned and Drive Outcomes" — producttalk.org/opportunity-solution-trees. The complete framework's reference article — worth rereading now that you have your own Mercado tree assembled end to end. In English.
- Marty Cagan (SVPG), "Discovery — Problem vs. Solution" — svpg.com/discovery-problem-vs-solution. As a bridge to module 4: the separation between problem and solution that ran through this whole module is exactly the foundation on which an experiment that truly refutes an assumption gets designed. In English.