January 13, 2026

Rewarding the Top 5 Sales Executives Across 3 Quarters (Java + Map + Streams)

This post shows an interview-ready way to solve a common “aggregate + filter + rank” problem in Java. We’ll go from the problem statement to a clean final solution, and then cover tips, pitfalls, and improvements that interviewers typically look for.


✅ Question

A company wants to reward the Top 5 sales executives based on performance over the last 3 quarters.

Data Files

  • q1_sales.csv

  • q2_sales.csv

  • q3_sales.csv

Each Record

(employeeId, dealsClosed, revenue)

Selection Criteria

An employee is eligible only if all conditions are met:

  1. Total deals closed ≥ 30 across all quarters

  2. Average revenue per deal > $5,000

  3. Deals closed ≥ 5 in each quarter

  4. From eligible employees, select Top 5 by total revenue (descending)

Output

Return a list of employeeIds sorted by total revenue DESC.


๐Ÿง  Approach

This is an “aggregation + eligibility + ranking” problem. The clean approach is:

  1. Aggregate quarter data into one summary per employee.

    • Track: deals and revenue per quarter, and totals.

  2. Apply filters:

    • totalDeals ≥ 30

    • dealsQ1 ≥ 5, dealsQ2 ≥ 5, dealsQ3 ≥ 5

    • averageRevenuePerDeal > 5000

  3. Sort by total revenue descending

  4. Take top 5

  5. Return list of employeeIds

Why Map?

A HashMap<String, EmployeeSalesSummary> gives you O(1) updates per record and keeps the code straightforward.


✅ Final Answer (Clean Java Solution)

This is a polished version of the code you started, with small improvements:

  • Uses constants for rule thresholds

  • Fixes the placeholder return (now returns top list)

  • Ensures filters match the problem statement

  • Adds a tie-breaker for consistent sorting (optional but good practice)

  • Removes unused imports

import java.util.*;
import java.util.stream.Collectors;

public class TopSalesExecutivesFinder {

    // ---------------- SAMPLE INPUT (Hardcoded) ----------------
    static List<SalesRecord> q1Sales = Arrays.asList(
            new SalesRecord("E1", 10, 60000),
            new SalesRecord("E2", 8, 30000),
            new SalesRecord("E3", 12, 80000)
    );

    static List<SalesRecord> q2Sales = Arrays.asList(
            new SalesRecord("E1", 9, 55000),
            new SalesRecord("E2", 12, 70000),
            new SalesRecord("E3", 10, 60000)
    );

    static List<SalesRecord> q3Sales = Arrays.asList(
            new SalesRecord("E1", 11, 65000),
            new SalesRecord("E2", 10, 50000),
            new SalesRecord("E3", 9, 55000)
    );

    public static List<String> findTopSalesExecutives() {

        // ---------------- RULE THRESHOLDS ----------------
        final int MIN_TOTAL_DEALS = 30;
        final int MIN_DEALS_EACH_QUARTER = 5;
        final double MIN_AVG_REV_PER_DEAL = 5000.0; // must be strictly greater
        final int TOP_K = 5;

        // ---------------- AGGREGATION ----------------
        Map<String, EmployeeSalesSummary> summaryMap = new HashMap<>();

        q1Sales.forEach(r ->
                summaryMap.computeIfAbsent(r.employeeId, EmployeeSalesSummary::new)
                          .addQ1(r.dealsClosed, r.revenue)
        );

        q2Sales.forEach(r ->
                summaryMap.computeIfAbsent(r.employeeId, EmployeeSalesSummary::new)
                          .addQ2(r.dealsClosed, r.revenue)
        );

        q3Sales.forEach(r ->
                summaryMap.computeIfAbsent(r.employeeId, EmployeeSalesSummary::new)
                          .addQ3(r.dealsClosed, r.revenue)
        );

        // ---------------- FILTER + SORT + PICK ----------------
        return summaryMap.values().stream()
                // 1) total deals >= 30
                .filter(s -> s.totalDeals() >= MIN_TOTAL_DEALS)
                // 2) deals >= 5 in each quarter
                .filter(s -> s.dealsEachQuarterAtLeast(MIN_DEALS_EACH_QUARTER))
                // 3) avg revenue per deal > 5000
                .filter(s -> s.averageRevenuePerDeal() > MIN_AVG_REV_PER_DEAL)
                // 4) sort by total revenue desc (tie-breaker optional)
                .sorted(Comparator.comparingDouble(EmployeeSalesSummary::totalRevenue).reversed()
                        .thenComparing(EmployeeSalesSummary::totalDeals, Comparator.reverseOrder())
                        .thenComparing(s -> s.employeeId))
                // 5) top 5
                .limit(TOP_K)
                // return employeeIds
                .map(s -> s.employeeId)
                .collect(Collectors.toList());
    }

    public static void main(String[] args) {
        List<String> result = findTopSalesExecutives();
        System.out.println("Top Sales Executives: " + result);
    }
}

// ---------------- SUPPORT CLASSES ----------------

class SalesRecord {
    String employeeId;
    int dealsClosed;
    double revenue;

    SalesRecord(String employeeId, int dealsClosed, double revenue) {
        this.employeeId = employeeId;
        this.dealsClosed = dealsClosed;
        this.revenue = revenue;
    }
}

class EmployeeSalesSummary {
    String employeeId;

    int dealsQ1, dealsQ2, dealsQ3;
    double revenueQ1, revenueQ2, revenueQ3;

    EmployeeSalesSummary(String employeeId) {
        this.employeeId = employeeId;
    }

    void addQ1(int deals, double revenue) {
        dealsQ1 += deals;
        revenueQ1 += revenue;
    }

    void addQ2(int deals, double revenue) {
        dealsQ2 += deals;
        revenueQ2 += revenue;
    }

    void addQ3(int deals, double revenue) {
        dealsQ3 += deals;
        revenueQ3 += revenue;
    }

    int totalDeals() {
        return dealsQ1 + dealsQ2 + dealsQ3;
    }

    double totalRevenue() {
        return revenueQ1 + revenueQ2 + revenueQ3;
    }

    double averageRevenuePerDeal() {
        int totalDeals = totalDeals();
        if (totalDeals == 0) return 0.0;
        return totalRevenue() / totalDeals; // safe double division
    }

    boolean dealsEachQuarterAtLeast(int minDeals) {
        return dealsQ1 >= minDeals && dealsQ2 >= minDeals && dealsQ3 >= minDeals;
    }

    @Override
    public String toString() {
        return "EmployeeSalesSummary{" +
                "employeeId='" + employeeId + '\'' +
                ", dealsQ1=" + dealsQ1 +
                ", dealsQ2=" + dealsQ2 +
                ", dealsQ3=" + dealsQ3 +
                ", totalDeals=" + totalDeals() +
                ", totalRevenue=" + totalRevenue() +
                ", avgRevPerDeal=" + averageRevenuePerDeal() +
                '}';
    }
}

๐Ÿงพ What’s the Output for Your Sample Data?

With your sample input:

  • E1 total deals = 10 + 9 + 11 = 30
    avg revenue per deal = (60000+55000+65000)/30 = 180000/30 = 6000
    each quarter deals ≥ 5 ✅
    total revenue = 180000

  • E2 total deals = 8 + 12 + 10 = 30
    avg revenue per deal = (30000+70000+50000)/30 = 150000/30 = 5000 ❌ (must be > 5000)

  • E3 total deals = 12 + 10 + 9 = 31
    avg revenue per deal = (80000+60000+55000)/31 ≈ 6290
    each quarter deals ≥ 5 ✅
    total revenue = 195000

✅ Result:

Top Sales Executives: [E3, E1]

✅ Tips & Interview Notes

1) Don’t mix up “average revenue per deal”

The rule is:

average revenue per deal across ALL quarters

So compute:

totalRevenue / totalDeals

Not quarter averages.

2) Beware >= vs >

  • The problem says average revenue per deal > 5000

  • That means 5000 exactly should FAIL (like E2 above)

3) Avoid integer division

Here we’re safe because totalRevenue is double.
If revenue was int, you’d need:

(double) totalRevenue / totalDeals

4) Sorting tie-breakers (bonus)

If total revenue ties, you can stabilize output:

  • higher total deals wins

  • then alphabetical employeeId

Interviewers like deterministic sorting.

5) Make rules configurable

Putting thresholds in constants makes the function reusable:

  • change to Top 10

  • change min deals from 30 to 20

  • etc.


⏱ Complexity

Let N be total number of sales records across q1, q2, q3, and P employees.

  • Aggregation: O(N)

  • Sorting: O(P log P)

  • Memory: O(P)

This is optimal for the problem.


Optional Enhancements (If Asked)

  • Read from actual CSVs using BufferedReader

  • Support any number of quarters (use arrays instead of dealsQ1/Q2/Q3)

  • Add unit tests:

    • avg exactly 5000

    • missing quarter data

    • fewer than 5 eligible employees