Back to Course|Engineering Manager Interviews: Leadership, Org Design & Strategic Execution
Lab

Build a Roadmap Prioritization Engine

30 min
Advanced
3 Free Attempts

Instructions

Overview

In this lab, you will build a Python Roadmap Prioritization Engine that an Engineering Manager can use to objectively score, rank, and schedule project proposals into a quarterly roadmap. The engine uses the RICE framework (Reach x Impact x Confidence / Effort), handles team capacity constraints, respects inter-project dependencies, and produces a clear roadmap with justifications for every inclusion and exclusion.

Core Concepts

RICE Scoring (created by Sean McBride at Intercom):

  • Reach: Number of users or customers affected per quarter
  • Impact: How much each user is affected (scale: 0.25 = minimal, 0.5 = low, 1 = medium, 2 = high, 3 = massive)
  • Confidence: How certain you are in the estimates (0.0 to 1.0, where 1.0 = 100%)
  • Effort: Person-weeks of engineering work required. Intercom's original RICE and the Module 5 lesson both express effort in person-months; this lab uses person-weeks so that effort and team capacity share one unit. The scores are therefore roughly 4x the lesson's for the same project, which is harmless because RICE scores are only ever compared against each other -- but it means a RICE score is meaningless without its unit, and stating the unit is part of presenting a prioritisation.

RICE Score = (Reach x Impact x Confidence) / Effort

Requirements

Part 1: Project Proposals

Create a class PrioritizationEngine with the following methods:

add_project(project) -- Accepts a dictionary with these required keys:

  • name (str): The project name
  • reach (int or float): Number of users/customers affected per quarter (must be > 0)
  • impact (float): Impact score, must be one of: 0.25, 0.5, 1, 2, 3
  • confidence (float): Confidence percentage as a decimal (0.0 to 1.0)
  • effort (float): Person-weeks required (must be > 0)
  • dependencies (list of str): Names of projects that must be completed first (can be empty)
  • category (str): One of "feature", "tech_debt", "infrastructure", "experiment"

The method must:

  • Validate all required fields and value ranges
  • Calculate and store the RICE score for the project
  • Store the project internally and return the calculated RICE score
  • Raise a ValueError for invalid input (missing fields, out-of-range values, invalid category)

get_project(name) -- Returns the project dictionary by name (including its calculated rice_score), or raises a KeyError if not found.

list_projects(sort_by="rice_score", category=None) -- Returns a list of all projects sorted by the specified field (descending for scores, ascending for effort). If category is provided, filters to only projects of that category.

Part 2: Capacity Allocation

set_capacity(total_weeks, tech_debt_percentage=20) -- Sets the team's total available engineering weeks for the quarter and the percentage reserved for tech debt (default 20%).

The method must:

  • Store total_weeks as the team's total capacity
  • Calculate feature_weeks as total_weeks * (1 - tech_debt_percentage / 100)
  • Calculate tech_debt_weeks as total_weeks * (tech_debt_percentage / 100)
  • Raise a ValueError if total_weeks <= 0 or tech_debt_percentage is not between 0 and 100

get_capacity() -- Returns a dictionary with total_weeks, feature_weeks, tech_debt_weeks, and remaining_weeks (initially equal to total_weeks).

Part 3: Dependency Resolution

validate_dependencies() -- Checks all projects for dependency issues and returns a dictionary with:

  • valid (bool): True if all dependencies can be resolved
  • missing (list of str): Project names referenced as dependencies that do not exist
  • circular (list of list of str): Lists of project names involved in circular dependencies

The engine must detect:

  • Missing dependencies: A project depends on a project that has not been added
  • Circular dependencies: Project A depends on B, and B depends on A (or longer cycles)

Part 4: Roadmap Generation

generate_roadmap() -- Produces a quarterly roadmap. This is the core method. It must:

  1. Validate dependencies (raise a ValueError if there are missing dependencies)
  2. Validate that capacity has been set (raise a ValueError if set_capacity() has not been called)
  3. Separate projects into two pools: tech_debt category projects use tech_debt_weeks; all other categories use feature_weeks
  4. Within each pool, sort projects by RICE score (highest first)
  5. Assign projects to the roadmap in RICE-score order, respecting:
    • Capacity: A project is only included if its effort fits within the remaining weeks for its pool
    • Dependencies: A project is only included if all its dependencies are already included in the roadmap
  6. Return a dictionary with:
    • included (list of dict): Projects included in the roadmap, each with name, rice_score, effort, category, and justification (a string explaining why it was included, e.g., "RICE score: 5714. Fits within feature capacity.")
    • excluded (list of dict): Projects not included, each with name, rice_score, effort, category, and reason (a string explaining why, e.g., "Insufficient remaining capacity (needs 8 weeks, 3 available)" or "Dependency 'Auth Rewrite' not included in roadmap")
    • capacity_summary (dict): total_weeks, feature_weeks_used, feature_weeks_remaining, tech_debt_weeks_used, tech_debt_weeks_remaining

format_roadmap() -- Returns a human-readable string representation of the roadmap. Must call generate_roadmap() internally. Format:

================================================
QUARTERLY ROADMAP
================================================

--- Capacity ---
Total: 60 weeks | Features: 48 weeks | Tech Debt: 12 weeks

--- Included Projects (by priority) ---
1. Auth Rewrite [infrastructure]
   RICE: 6,000 | Effort: 8 weeks
   Justification: RICE score: 6000. Fits within feature capacity.

2. Search Improvement [feature]
   RICE: 5,714 | Effort: 14 weeks
   Justification: RICE score: 5714. Fits within feature capacity.

3. Cache Layer Rebuild [tech_debt]
   RICE: 4,500 | Effort: 6 weeks
   Justification: RICE score: 4500. Fits within tech debt capacity.

4. Payment V2 [feature]
   RICE: 4,200 | Effort: 10 weeks
   Justification: RICE score: 4200. Fits within feature capacity.

--- Excluded Projects ---
* Design System Refresh [feature]
  RICE: 2,000 | Effort: 4 weeks
  Reason: Dependency 'Mobile Redesign' not included in roadmap

* Mobile Redesign [feature]
  RICE: 1,800 | Effort: 20 weeks
  Reason: Insufficient remaining capacity (needs 20 weeks, 16 available)

--- Summary ---
Included: 4 projects | Excluded: 2 projects
Feature capacity: 32/48 weeks used (67%)
Tech debt capacity: 6/12 weeks used (50%)
================================================

Read the two exclusions together, because they are the reason this engine is worth building. Design System Refresh scores higher than Mobile Redesign and is still excluded -- by the project below it, which it depends on and which does not fit. A ranked list cannot express that; only running the capacity and dependency passes together can. It is also the shape of the argument you will have with a stakeholder: their project was not cut for being unimportant, it was cut for being downstream of something that was.

Example Usage

engine = PrioritizationEngine()

engine.add_project({
    "name": "Search Improvement",
    "reach": 50000,
    "impact": 2,
    "confidence": 0.8,
    "effort": 14,
    "dependencies": [],
    "category": "feature",
})

engine.add_project({
    "name": "Cache Layer Rebuild",
    "reach": 30000,
    "impact": 1,
    "confidence": 0.9,
    "effort": 6,
    "dependencies": [],
    "category": "tech_debt",
})

engine.add_project({
    "name": "Payment V2",
    "reach": 20000,
    "impact": 3,
    "confidence": 0.7,
    "effort": 10,
    "dependencies": ["Auth Rewrite"],
    "category": "feature",
})

engine.add_project({
    "name": "Auth Rewrite",
    "reach": 40000,
    "impact": 2,
    "confidence": 0.6,
    "effort": 8,
    "dependencies": [],
    "category": "infrastructure",
})

engine.add_project({
    "name": "Mobile Redesign",
    "reach": 60000,
    "impact": 1,
    "confidence": 0.6,
    "effort": 20,
    "dependencies": [],
    "category": "feature",
})

engine.add_project({
    "name": "Design System Refresh",
    "reach": 20000,
    "impact": 0.5,
    "confidence": 0.8,
    "effort": 4,
    "dependencies": ["Mobile Redesign"],
    "category": "feature",
})

# Set team capacity: 5 engineers x 12 weeks = 60 person-weeks
engine.set_capacity(total_weeks=60, tech_debt_percentage=20)

# Check dependencies
dep_check = engine.validate_dependencies()
print(f"Dependencies valid: {dep_check['valid']}")

# Generate and display roadmap
roadmap = engine.generate_roadmap()
print(engine.format_roadmap())

Grading Rubric

Project management and RICE scoring -- PrioritizationEngine correctly stores projects with all required fields (name, reach, impact, confidence, effort, dependencies, category); add_project() validates all fields including that impact is one of [0.25, 0.5, 1, 2, 3], confidence is between 0 and 1, effort and reach are positive, and category is one of the four valid types; correctly calculates RICE score as (reach * impact * confidence) / effort; get_project() retrieves by name including the calculated rice_score and raises KeyError for missing projects; list_projects() supports sorting by rice_score or effort and filtering by category25 points
Capacity allocation and the tech-debt reservation -- set_capacity() correctly splits total engineering weeks between feature work and tech debt using the provided percentage (default 20%); validates that total_weeks is positive and tech_debt_percentage is between 0 and 100; get_capacity() returns accurate total_weeks, feature_weeks, tech_debt_weeks, and remaining_weeks values; the capacity system correctly tracks remaining capacity as projects are assigned to the roadmap; tech_debt category projects draw from tech_debt_weeks while all other categories draw from feature_weeks25 points
Dependency resolution -- validate_dependencies() correctly identifies missing dependencies (projects referenced that have not been added) and circular dependencies (A depends on B and B depends on A, or longer cycles); returns a structured result with valid boolean, missing list, and circular list; generate_roadmap() raises ValueError when missing dependencies exist; the roadmap respects dependency ordering by only including a project when all of its dependencies are already included; excluded projects that fail dependency checks receive an appropriate reason string25 points
Roadmap generation and formatted output -- generate_roadmap() produces a complete roadmap with included projects (sorted by RICE score within their capacity pool), excluded projects with clear reasons, and an accurate capacity_summary; justifications for included projects reference the RICE score and capacity pool; reasons for excluded projects specify whether it was a capacity issue (with specific numbers) or a dependency issue (naming the missing dependency); format_roadmap() produces a well-structured human-readable output with capacity breakdown, numbered included projects, excluded projects with reasons, and a summary showing utilization percentages; handles edge cases cleanly (no projects, all projects fit, no projects fit)25 points

Checklist

0/11

Your Solution

3 free attempts remaining