Feature Prioritization with Elote
============================
Overview
--------
Feature prioritization is a critical challenge in product management and software development. With limited resources and time, teams must decide which features to build first to maximize value. Traditional prioritization methods like scoring rubrics or the RICE framework (Reach, Impact, Confidence, Effort) have limitations:
- They often rely on subjective numerical scores
- Different stakeholders may interpret scoring criteria differently
- It's difficult to maintain consistency across many features
- Comparing features directly is often more intuitive than scoring them individually
Elote offers an elegant solution through pairwise comparison-based ranking. By having stakeholders compare features directly against each other, you can build a more reliable and consensus-driven prioritization system.
This guide demonstrates how to use Elote for effective feature prioritization.
Basic Implementation
------------------
Let's start with a simple implementation using the Elo rating system:
.. code-block:: python
from elote import EloCompetitor
import json
class FeaturePrioritizer:
def __init__(self):
self.features = {}
def add_feature(self, feature_id, name, description, initial_rating=1500):
"""Add a feature to the prioritization system"""
if feature_id not in self.features:
self.features[feature_id] = {
'id': feature_id,
'name': name,
'description': description,
'competitor': EloCompetitor(initial_rating=initial_rating),
'comparisons': 0
}
return self.features[feature_id]
def record_comparison(self, winner_id, loser_id):
"""Record the result of a feature comparison"""
if winner_id not in self.features or loser_id not in self.features:
raise ValueError("Both features must be added to the system first")
# Update ratings
self.features[winner_id]['competitor'].beat(self.features[loser_id]['competitor'])
# Update comparison counts
self.features[winner_id]['comparisons'] += 1
self.features[loser_id]['comparisons'] += 1
def get_prioritized_list(self, min_comparisons=3):
"""Get a prioritized list of features"""
# Only include features with sufficient comparisons
valid_features = [f for f in self.features.values() if f['comparisons'] >= min_comparisons]
# Sort by rating (descending)
valid_features.sort(key=lambda f: f['competitor'].rating, reverse=True)
return [
{
'id': f['id'],
'name': f['name'],
'description': f['description'],
'rating': f['competitor'].rating,
'comparisons': f['comparisons']
}
for f in valid_features
]
def get_next_comparison(self):
"""Get the next most informative comparison to make"""
import random
# Simple strategy: find features with similar ratings
features_list = list(self.features.values())
if len(features_list) < 2:
return None
# Sort by number of comparisons (ascending)
features_list.sort(key=lambda f: f['comparisons'])
# Take the two least-compared features
if features_list[0]['comparisons'] < 5:
return (features_list[0]['id'], features_list[1]['id'])
# Otherwise, find features with similar ratings
features_list.sort(key=lambda f: f['competitor'].rating)
# Find adjacent features with closest ratings
min_diff = float('inf')
best_pair = None
for i in range(len(features_list) - 1):
diff = abs(features_list[i]['competitor'].rating - features_list[i+1]['competitor'].rating)
if diff < min_diff:
min_diff = diff
best_pair = (features_list[i]['id'], features_list[i+1]['id'])
return best_pair
# Usage example
prioritizer = FeaturePrioritizer()
# Add features
prioritizer.add_feature("f1", "User Authentication", "Allow users to sign up and log in")
prioritizer.add_feature("f2", "Dashboard", "Create a dashboard with key metrics")
prioritizer.add_feature("f3", "Export to CSV", "Add ability to export data to CSV")
prioritizer.add_feature("f4", "Dark Mode", "Add a dark mode theme option")
prioritizer.add_feature("f5", "Mobile App", "Create a mobile app version")
# Record some comparisons
prioritizer.record_comparison("f1", "f3") # Auth is more important than CSV export
prioritizer.record_comparison("f1", "f4") # Auth is more important than Dark Mode
prioritizer.record_comparison("f2", "f3") # Dashboard is more important than CSV export
prioritizer.record_comparison("f2", "f4") # Dashboard is more important than Dark Mode
prioritizer.record_comparison("f5", "f4") # Mobile App is more important than Dark Mode
prioritizer.record_comparison("f1", "f2") # Auth is more important than Dashboard
prioritizer.record_comparison("f5", "f3") # Mobile App is more important than CSV export
prioritizer.record_comparison("f1", "f5") # Auth is more important than Mobile App
# Get prioritized list
prioritized = prioritizer.get_prioritized_list()
for i, feature in enumerate(prioritized, 1):
print(f"{i}. {feature['name']} (Rating: {feature['rating']:.1f})")
Collecting Comparison Data
------------------------
There are several ways to collect feature comparison data:
1. **Stakeholder Voting**: Have stakeholders vote on pairs of features
2. **Team Meetings**: Conduct structured prioritization sessions
3. **User Research**: Ask users to compare feature importance
4. **Automated Surveys**: Send regular comparison questions to stakeholders
Here's an example of a simple web interface for collecting stakeholder votes:
.. code-block:: html
Which feature is more important?
User Authentication
Allow users to sign up and log in
Dashboard
Create a dashboard with key metrics
Advanced Implementation: Multi-Criteria Prioritization
---------------------------------------------------
In real-world scenarios, features are often evaluated on multiple criteria such as:
- Business value
- User value
- Implementation effort
- Technical risk
- Strategic alignment
We can use Elote's Ensemble rating system to combine these criteria:
.. code-block:: python
from elote import EnsembleCompetitor, EloCompetitor
class MultiCriteriaFeaturePrioritizer:
def __init__(self, criteria=None, weights=None):
self.features = {}
# Default criteria and weights if not provided
self.criteria = criteria or ["business_value", "user_value", "effort", "risk"]
self.weights = weights or [0.4, 0.3, 0.2, 0.1]
# Validate weights sum to 1
if abs(sum(self.weights) - 1.0) > 0.001:
raise ValueError("Weights must sum to 1.0")
if len(self.criteria) != len(self.weights):
raise ValueError("Must provide a weight for each criterion")
def add_feature(self, feature_id, name, description):
"""Add a feature to the prioritization system"""
if feature_id not in self.features:
# Create a competitor for each criterion
rating_systems = []
for criterion, weight in zip(self.criteria, self.weights):
rating_systems.append((EloCompetitor(initial_rating=1500), weight))
self.features[feature_id] = {
'id': feature_id,
'name': name,
'description': description,
'competitor': EnsembleCompetitor(rating_systems=rating_systems),
'comparisons': {criterion: 0 for criterion in self.criteria},
'total_comparisons': 0
}
return self.features[feature_id]
def record_comparison(self, winner_id, loser_id, criterion=None):
"""
Record a comparison result
If criterion is None, update the ensemble rating
If criterion is specified, update only that criterion's rating
"""
if winner_id not in self.features or loser_id not in self.features:
raise ValueError("Both features must be added to the system first")
winner = self.features[winner_id]
loser = self.features[loser_id]
if criterion is None:
# Update the ensemble rating
winner['competitor'].beat(loser['competitor'])
winner['total_comparisons'] += 1
loser['total_comparisons'] += 1
elif criterion in self.criteria:
# Update a specific criterion
criterion_index = self.criteria.index(criterion)
# Get the specific rating system
winner_rating_system = winner['competitor'].rating_systems[criterion_index][0]
loser_rating_system = loser['competitor'].rating_systems[criterion_index][0]
# Update the rating
winner_rating_system.beat(loser_rating_system)
# Update comparison count for this criterion
winner['comparisons'][criterion] += 1
loser['comparisons'][criterion] += 1
else:
raise ValueError(f"Unknown criterion: {criterion}")
def get_prioritized_list(self, min_comparisons=3):
"""Get a prioritized list of features"""
# Only include features with sufficient comparisons
valid_features = [
f for f in self.features.values()
if f['total_comparisons'] >= min_comparisons
]
# Sort by ensemble rating (descending)
valid_features.sort(key=lambda f: f['competitor'].rating, reverse=True)
result = []
for f in valid_features:
# Get individual criterion ratings
criterion_ratings = {}
for i, criterion in enumerate(self.criteria):
rating_system = f['competitor'].rating_systems[i][0]
criterion_ratings[criterion] = rating_system.rating
result.append({
'id': f['id'],
'name': f['name'],
'description': f['description'],
'overall_rating': f['competitor'].rating,
'criterion_ratings': criterion_ratings,
'comparisons': f['comparisons'],
'total_comparisons': f['total_comparisons']
})
return result
Handling Stakeholder Disagreement
-------------------------------
Different stakeholders often have different priorities. Here are strategies to handle this:
1. **Separate Rankings**: Maintain separate rankings for different stakeholder groups
2. **Weighted Voting**: Give different weights to different stakeholders
3. **Consensus Building**: Use the comparison process to drive discussion and consensus
Here's an example of weighted voting:
.. code-block:: python
def record_weighted_comparison(self, winner_id, loser_id, voter_id, voter_weights=None):
"""Record a comparison with different weights for different voters"""
if voter_weights is None:
# Default weights by role
voter_weights = {
"executive": 2.0,
"product_manager": 1.5,
"engineer": 1.0,
"designer": 1.0,
"sales": 0.8
}
# Get voter's role
voter_role = self.get_voter_role(voter_id)
weight = voter_weights.get(voter_role, 1.0)
# Apply the comparison multiple times based on weight
full_weight = int(weight)
for _ in range(full_weight):
self.record_comparison(winner_id, loser_id)
# Handle fractional weight
fractional_weight = weight - full_weight
if random.random() < fractional_weight:
self.record_comparison(winner_id, loser_id)
Visualizing Prioritization Results
--------------------------------
Visualization helps stakeholders understand and buy into the prioritization results:
.. code-block:: python
import matplotlib.pyplot as plt
import numpy as np
def visualize_prioritization(prioritized_features, top_n=10):
"""Create a visualization of the prioritization results"""
# Take top N features
features = prioritized_features[:top_n]
# Extract data
names = [f['name'] for f in features]
ratings = [f['rating'] for f in features]
# Create horizontal bar chart
plt.figure(figsize=(10, 8))
y_pos = np.arange(len(names))
plt.barh(y_pos, ratings, align='center')
plt.yticks(y_pos, names)
plt.xlabel('Rating')
plt.title('Feature Priority Ranking')
# Add rating values at the end of each bar
for i, v in enumerate(ratings):
plt.text(v + 10, i, f"{v:.1f}", va='center')
plt.tight_layout()
plt.savefig('feature_prioritization.png')
plt.close()
return 'feature_prioritization.png'
Real-World Applications
---------------------
Organizations using pairwise comparison for feature prioritization include:
- **Microsoft**: Uses similar techniques for Windows feature prioritization
- **Atlassian**: Incorporates pairwise voting in their product planning
- **Spotify**: Uses comparison-based methods for roadmap planning
- **IBM**: Employs similar techniques for enterprise software features
Best Practices
------------
1. **Start Small**: Begin with a manageable set of features (15-20)
2. **Involve Key Stakeholders**: Ensure all perspectives are represented
3. **Provide Context**: Give voters sufficient information about each feature
4. **Regular Updates**: Re-prioritize regularly as new information emerges
5. **Combine Methods**: Use Elote alongside other prioritization frameworks
6. **Document Decisions**: Record the reasoning behind key prioritization decisions
7. **Consider Dependencies**: Factor in technical dependencies between features
Conclusion
---------
Elote provides a powerful framework for implementing feature prioritization systems based on pairwise comparisons. This approach offers several advantages over traditional scoring methods:
- More intuitive for stakeholders (comparing is easier than scoring)
- Reduces individual bias through multiple comparisons
- Creates a clear, defensible ranking
- Adapts as new features are added
- Builds consensus through the comparison process
By leveraging Elote's implementation of sophisticated rating systems, you can build robust feature prioritization solutions that help your team focus on delivering the highest-value features first.