How to Calculate Expected Value

Expected value is the single most important concept in probability and decision theory. It tells you what outcome to expect on average if you could repeat a scenario infinitely. More practically,...

Key Insights

  • Expected value is the probability-weighted average of all possible outcomes, calculated as E(X) = Σ(xᵢ × P(xᵢ)), giving you a single number that represents the long-run average result
  • The formula applies universally across discrete scenarios (dice, business decisions) and continuous distributions, but expected value alone doesn’t tell you about risk—you need variance and standard deviation for that
  • In practice, expected value drives decisions in finance, insurance, product development, and A/B testing, but it only becomes reliable over many iterations due to the law of large numbers

Introduction to Expected Value

Expected value is the single most important concept in probability and decision theory. It tells you what outcome to expect on average if you could repeat a scenario infinitely. More practically, it’s the tool that lets you compare options with uncertain outcomes on equal footing.

The mathematical definition is straightforward: multiply each possible outcome by its probability, then sum everything up. If you’re deciding whether to launch a product, play a casino game, or run an A/B test, expected value gives you the rational baseline for your decision.

Here’s the basic implementation:

def expected_value(outcomes, probabilities):
    """
    Calculate expected value given outcomes and their probabilities.
    
    Args:
        outcomes: List of possible numerical outcomes
        probabilities: List of probabilities (must sum to 1.0)
    
    Returns:
        Expected value as a float
    """
    if len(outcomes) != len(probabilities):
        raise ValueError("Outcomes and probabilities must have same length")
    
    if not abs(sum(probabilities) - 1.0) < 1e-10:
        raise ValueError(f"Probabilities must sum to 1.0, got {sum(probabilities)}")
    
    return sum(x * p for x, p in zip(outcomes, probabilities))

# Example: Fair coin flip where heads = $1, tails = -$1
ev = expected_value([1, -1], [0.5, 0.5])
print(f"Expected value: ${ev}")  # Output: $0.0

This function is deceptively simple but handles the core calculation you’ll use everywhere.

The Mathematical Foundation

The formula E(X) = Σ(xᵢ × P(xᵢ)) breaks down into three components:

  • E(X): The expected value we’re calculating
  • xᵢ: Each possible outcome value
  • P(xᵢ): The probability of that outcome occurring

For discrete probability distributions (countable outcomes), you sum across all possibilities. For continuous distributions, you integrate instead of sum, but the concept remains identical: weight each value by its likelihood.

Let’s walk through a six-sided die to see the summation in action:

def dice_expected_value():
    """Calculate expected value of a fair six-sided die roll."""
    outcomes = [1, 2, 3, 4, 5, 6]
    probability_each = 1/6
    
    # Show the step-by-step calculation
    total = 0
    print("Calculating expected value step by step:")
    for outcome in outcomes:
        contribution = outcome * probability_each
        total += contribution
        print(f"  {outcome} × {probability_each:.4f} = {contribution:.4f}")
    
    print(f"\nExpected value: {total:.4f}")
    return total

dice_expected_value()
# Output: 3.5000

The die’s expected value of 3.5 makes intuitive sense—it’s the midpoint between 1 and 6. You’ll never actually roll a 3.5, which highlights an important point: expected value represents the long-run average, not a guaranteed outcome.

Discrete Probability Scenarios

Most real-world decisions involve discrete outcomes. You either win or lose the contract. The product succeeds or fails. The user clicks or doesn’t click.

Here’s a casino game analyzer that shows expected value in action:

def analyze_casino_game(bet_amount, win_payout, win_probability):
    """
    Analyze expected value of a casino game.
    
    Args:
        bet_amount: How much you wager
        win_payout: Total amount returned if you win (includes original bet)
        win_probability: Probability of winning (0 to 1)
    
    Returns:
        Dictionary with analysis results
    """
    lose_probability = 1 - win_probability
    
    # Outcomes: net gain if win, net loss if lose
    win_outcome = win_payout - bet_amount
    lose_outcome = -bet_amount
    
    ev = (win_outcome * win_probability) + (lose_outcome * lose_probability)
    
    return {
        'expected_value': ev,
        'expected_return_pct': (ev / bet_amount) * 100,
        'house_edge': -ev / bet_amount if ev < 0 else 0
    }

# Roulette: Bet $10 on red (pays even money, 18/38 chance on American wheel)
roulette = analyze_casino_game(bet_amount=10, win_payout=20, win_probability=18/38)
print(f"Roulette EV: ${roulette['expected_value']:.2f}")
print(f"House edge: {roulette['house_edge']*100:.2f}%")
# Output: EV: -$0.53, House edge: 5.26%

For investment scenarios with multiple outcomes:

def investment_expected_value(investment, scenarios):
    """
    Calculate expected value for investment with multiple scenarios.
    
    Args:
        investment: Initial investment amount
        scenarios: List of tuples (return_multiple, probability)
    
    Returns:
        Expected profit/loss
    """
    outcomes = [(investment * multiple - investment) for multiple, _ in scenarios]
    probabilities = [prob for _, prob in scenarios]
    
    return expected_value(outcomes, probabilities)

# Startup investment: $50k
# 10% chance of 10x return, 30% chance of 2x, 60% chance of total loss
scenarios = [
    (10.0, 0.10),  # 10x return
    (2.0, 0.30),   # 2x return
    (0.0, 0.60)    # Total loss
]

ev = investment_expected_value(50000, scenarios)
print(f"Expected profit: ${ev:,.2f}")  # Output: $65,000

Real-World Applications

Expected value shines in business contexts where you need to choose between uncertain options.

For A/B testing decisions:

class ABTestDecision:
    def __init__(self, control_revenue, variant_revenue, 
                 variant_probability, implementation_cost):
        self.control_revenue = control_revenue
        self.variant_revenue = variant_revenue
        self.variant_probability = variant_probability
        self.implementation_cost = implementation_cost
    
    def expected_value_of_shipping(self):
        """Calculate EV of shipping the variant vs. staying with control."""
        # If we ship: probability of success × uplift - implementation cost
        uplift = self.variant_revenue - self.control_revenue
        ev_ship = (self.variant_probability * uplift) - self.implementation_cost
        
        # If we don't ship: EV is 0 (stay with current state)
        ev_no_ship = 0
        
        return {
            'ev_ship': ev_ship,
            'ev_no_ship': ev_no_ship,
            'recommendation': 'Ship' if ev_ship > ev_no_ship else 'Do not ship',
            'expected_gain': max(ev_ship, ev_no_ship)
        }

# Test showed variant might increase revenue by $100k/year
# But we're only 70% confident, and implementation costs $20k
test = ABTestDecision(
    control_revenue=500000,
    variant_revenue=600000,
    variant_probability=0.70,
    implementation_cost=20000
)

result = test.expected_value_of_shipping()
print(f"Recommendation: {result['recommendation']}")
print(f"Expected gain: ${result['expected_gain']:,.2f}")

Expected Value vs. Actual Outcomes

Expected value tells you what happens on average, but individual outcomes will vary—sometimes wildly. This is where variance and standard deviation come in.

A Monte Carlo simulation demonstrates this perfectly:

import random
import statistics

def monte_carlo_convergence(outcomes, probabilities, num_trials=10000):
    """
    Simulate repeated trials to show convergence to expected value.
    
    Returns running average at different sample sizes.
    """
    theoretical_ev = expected_value(outcomes, probabilities)
    
    # Create weighted population
    population = []
    for outcome, prob in zip(outcomes, probabilities):
        population.extend([outcome] * int(prob * 10000))
    
    results = []
    running_sum = 0
    checkpoints = [10, 100, 1000, 10000]
    
    for i in range(1, num_trials + 1):
        result = random.choice(population)
        running_sum += result
        running_avg = running_sum / i
        
        if i in checkpoints:
            results.append({
                'trials': i,
                'running_average': running_avg,
                'theoretical_ev': theoretical_ev,
                'difference': abs(running_avg - theoretical_ev)
            })
    
    return results

# Unfair coin: 60% heads ($1), 40% tails (-$1)
convergence = monte_carlo_convergence([1, -1], [0.6, 0.4])

print("Convergence to Expected Value:")
for r in convergence:
    print(f"After {r['trials']:5d} trials: ${r['running_average']:6.3f} "
          f"(EV: ${r['theoretical_ev']:.3f}, diff: ${r['difference']:.3f})")

This shows the law of large numbers in action: with more trials, your actual average converges to the expected value. But with few trials, you can see significant deviation.

Advanced Considerations

Decision trees handle sequential decisions where later choices depend on earlier outcomes:

class DecisionNode:
    def __init__(self, name):
        self.name = name
        self.branches = []
    
    def add_branch(self, probability, outcome_value=None, next_node=None):
        """Add a branch with probability and either a terminal value or next node."""
        self.branches.append({
            'probability': probability,
            'outcome_value': outcome_value,
            'next_node': next_node
        })
    
    def calculate_ev(self):
        """Recursively calculate expected value of this decision node."""
        total_ev = 0
        
        for branch in self.branches:
            if branch['next_node']:
                # Recursive case: multiply probability by EV of next node
                branch_ev = branch['probability'] * branch['next_node'].calculate_ev()
            else:
                # Terminal case: multiply probability by outcome value
                branch_ev = branch['probability'] * branch['outcome_value']
            
            total_ev += branch_ev
        
        return total_ev

# Example: Product development decision
# First decision: Develop product ($100k cost)
# Market research: 70% positive, 30% negative
# If positive: Launch (60% success = $500k profit, 40% failure = $0)
# If negative: Don't launch ($0)

launch_node = DecisionNode("Launch")
launch_node.add_branch(0.6, outcome_value=500000)  # Success
launch_node.add_branch(0.4, outcome_value=0)        # Failure

research_node = DecisionNode("Research")
research_node.add_branch(0.7, next_node=launch_node)  # Positive research
research_node.add_branch(0.3, outcome_value=0)        # Negative research

ev = research_node.calculate_ev() - 100000  # Subtract development cost
print(f"Expected value of product development: ${ev:,.2f}")

Practical Implementation Guide

When applying expected value analysis in real projects:

  1. Enumerate all meaningful outcomes - Don’t just model best/worst case
  2. Get probability estimates from data - Historical data beats guesses
  3. Run sensitivity analysis - Test how results change with different assumptions
  4. Communicate uncertainty - Show the range, not just the expected value

Here’s a complete implementation:

class ExpectedValueAnalysis:
    def __init__(self, name):
        self.name = name
        self.scenarios = []
    
    def add_scenario(self, outcome, probability, description=""):
        self.scenarios.append({
            'outcome': outcome,
            'probability': probability,
            'description': description
        })
    
    def calculate(self):
        outcomes = [s['outcome'] for s in self.scenarios]
        probabilities = [s['probability'] for s in self.scenarios]
        
        ev = expected_value(outcomes, probabilities)
        variance = sum(p * (x - ev)**2 for x, p in zip(outcomes, probabilities))
        std_dev = variance ** 0.5
        
        return {
            'expected_value': ev,
            'variance': variance,
            'std_deviation': std_dev,
            'min_outcome': min(outcomes),
            'max_outcome': max(outcomes)
        }
    
    def report(self):
        results = self.calculate()
        
        print(f"\n{'='*60}")
        print(f"Expected Value Analysis: {self.name}")
        print(f"{'='*60}")
        print(f"\nExpected Value: ${results['expected_value']:,.2f}")
        print(f"Standard Deviation: ${results['std_deviation']:,.2f}")
        print(f"Range: ${results['min_outcome']:,.2f} to ${results['max_outcome']:,.2f}")
        print(f"\nScenarios:")
        
        for s in self.scenarios:
            print(f"  {s['probability']*100:5.1f}% - ${s['outcome']:>10,.2f} - {s['description']}")

# Example usage
analysis = ExpectedValueAnalysis("New Market Entry")
analysis.add_scenario(2000000, 0.25, "High success")
analysis.add_scenario(500000, 0.45, "Moderate success")
analysis.add_scenario(-200000, 0.30, "Failure")
analysis.report()

Expected value is your rational foundation for decisions under uncertainty. Calculate it, understand its limitations, and use it alongside other metrics like standard deviation and worst-case analysis. The math is simple, but the insight it provides is invaluable.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.