Tail Latency Percentiles (Nearest-Rank)
Problem Statement
Mean latency is the least useful number on an inference dashboard: it hides the tail, and the tail is what users feel and what your SLO is written against. Compute the tail percentiles of a batch of request latencies.
Background
There are several incompatible percentile definitions, and a dashboard that mixes them will disagree with itself. This problem uses nearest-rank, with no interpolation:
rank = ceil(p / 100 * n) # 1-based, clamped into [1, n]
P_p = sorted_samples[rank - 1]
Nearest-rank always returns a value that was actually observed. With 100 samples of 1..100, p99 is exactly 99 — an interpolating definition would report 99.01, a latency no request ever had.
Because p95 picks the ceil(0.95 * n)-th smallest sample, a handful of slow requests move it a long way. That is the point: it is a tail statistic.
Your Task
Implement:
def latency_percentiles(samples):
Return a dict with keys "count", "p50", "p95", "p99", "max", in that order. "count" is an int; the rest are the selected samples rounded to 2 decimal places.
Input Format
- samples: list of latencies in milliseconds (ints or floats), in arbitrary order.
Output Format
- The dict described above.
Sample
print(latency_percentiles([12, 15, 11, 480, 13, 14, 12, 16, 13, 12]))
Output:
{'count': 10, 'p50': 13, 'p95': 480, 'p99': 480, 'max': 480}
The median sits at 13 ms while a single 480 ms outlier owns both tail percentiles — a service whose p50 looks healthy and whose p95 is 37x worse.
Example:
print(latency_percentiles([12, 15, 11, 480, 13, 14, 12, 16, 13, 12]))
{'count': 10, 'p50': 13, 'p95': 480, 'p99': 480, 'max': 480}Sorted: [11, 12, 12, 12, 13, 13, 14, 15, 16, 480]. n = 10, so p50 takes rank ceil(0.5 * 10) = 5 -> 13. p95 takes rank ceil(9.5) = 10 and p99 rank ceil(9.9) = 10, both landing on the single 480 ms outlier.
Constraints:
- 1 <= len(samples) <= 100000
- Samples are non-negative ints or floats, in arbitrary order
- Use the nearest-rank definition:
rank = ceil(p / 100 * n), 1-based, clamped into[1, n]— no interpolation - Every returned percentile is a value that actually appears in
samples - Round the four latency values to 2 decimal places
1. Background Knowledge
In distributed systems and machine learning inference, latency is a critical performance metric. While the mean (average) latency provides a general sense of performance, it is heavily skewed by outliers and fails to capture the user experience for the slowest requests. This is why percentiles are preferred for Service Level Objectives (SLOs). A percentile Pp indicates the value below which p percent of the observations fall. For example, the p95 latency is the value such that 95% of requests are faster than or equal to this value. This metric is crucial for identifying tail latency, which directly impacts user satisfaction and system stability.
There are multiple methods to calculate percentiles, leading to inconsistencies across different tools. This problem specifies the nearest-rank method, which is deterministic and always returns an actual observed data point. Unlike interpolation methods (which might calculate a value like 99.01 ms that never actually occurred), nearest-rank selects a specific element from the sorted dataset. The rank R for a percentile p (where 0<p≤100) in a dataset of size n is calculated as:
R=⌈100p×n⌉This rank is 1-based, meaning the smallest element is at rank 1. If the calculated rank exceeds n, it is clamped to n. If it is less than 1, it is clamped to 1. This ensures that the index is always valid within the bounds of the sorted array.
2. Algorithm Approach
The core algorithmic pattern here is sorting followed by index selection. Since the nearest-rank method depends on the ordered position of elements, the first step is always to sort the input list of latencies in ascending order. Once sorted, the problem reduces to calculating specific indices based on the percentile formula and retrieving the values at those indices.
The approach involves:
- Sorting: Transform the unsorted input into a sorted sequence to enable rank-based access.
- Rank Calculation: For each required percentile (50, 95, 99), apply the ceiling formula to determine the 1-based rank.
- Index Conversion: Convert the 1-based rank to a 0-based index suitable for Python list access.
- Value Retrieval: Extract the values at the calculated indices.
- Formatting: Round the results to two decimal places and package them into the required dictionary structure.
3. Step-by-Step Strategy
- Handle Edge Cases: Check if the input list samples is empty. If so, return an appropriate default or handle as per specific requirements (though the problem implies non-empty lists for valid percentiles).
- Sort the Data: Create a sorted copy of the input list. Do not modify the original list if immutability is preferred, but for this problem, a new sorted list is sufficient. Let n be the length of this sorted list.
- Define a Helper Function: Create a helper function or logic block that takes a percentile p and the sorted list, then returns the value at the nearest-rank position.
- Calculate rank: R=⌈100p×n⌉.
- Clamp R to be within [1,n].
- Convert to 0-based index: idx=R−1.
- Return sorted_samples[idx].
- Compute Specific Percentiles:
- Calculate p50 using p=50.
- Calculate p95 using p=95.
- Calculate p99 using p=99.
- Determine Max: The maximum value is simply the last element of the sorted list (sorted_samples[-1]).
- Round Values: Round p50, p95, p99, and max to 2 decimal places using Python's round() function. Note that count should remain an integer.
- Construct Output: Create a dictionary with keys "count", "p50", "p95", "p99", "max" in that specific order. Return this dictionary.
4. Common Pitfalls
- Off-by-One Errors: The nearest-rank formula produces a 1-based rank. Python lists are 0-indexed. Forgetting to subtract 1 from the rank will lead to accessing the wrong element or an IndexError if the rank equals n and you try to access index n (which is out of bounds for a list of size n).
- Ceiling vs. Floor: Using floor instead of ceil will change the percentile definition. The problem explicitly requires ceil. In Python, use math.ceil().
- Floating Point Precision: When calculating 100p×n, floating-point arithmetic might result in values like 4.9999999 instead of 5. math.ceil() handles this correctly, but be aware that direct integer casting might truncate incorrectly.
- Clamping Logic: If n=1, the rank for any percentile should be 1. Ensure your clamping logic handles cases where the calculated rank is less than 1 (though with p>0 and n≥1, this is rare) or greater than n.
- Rounding Behavior: Python's round() function uses "banker's rounding" (round half to even). Ensure this matches the expected output. For example, round(2.5) becomes 2, not 3. The problem asks for 2 decimal places, so round(value, 2).
- Dictionary Order: While modern Python dictionaries preserve insertion order, explicitly creating the dict with keys in the specified order ("count", "p50", etc.) is good practice to ensure consistency with the problem statement.
5. Time & Space Complexity
- Time Complexity: The dominant operation is sorting the list of n samples. Using Python's Timsort, this takes O(nlogn) time. Calculating the ranks and retrieving values is O(1) for each percentile, so the total time complexity is O(nlogn).
- Space Complexity: If you create a new sorted list, the space complexity is O(n) to store the sorted copy. If you sort in-place, it would be O(1) auxiliary space (excluding the input storage), but creating a new list is safer and often preferred for immutability. The output dictionary uses O(1) space.