Coding problem · Medium · Data Processing
Top Endpoints from Access Logs
Medium Practical tasks Seen at Datadog, Cloudflare, Shopify, Amazon
You are given lines from an HTTP access log in the common format:
203.0.113.9 - - [11/Oct/2026:10:00:01 +0000] "GET /api/users?page=2 HTTP/1.1" 200 512
Return the k most requested paths:
- the path is the request target without its query string (
/api/users?page=2→/api/users); - count every well-formed request regardless of method or status;
- skip malformed lines (no quoted
"METHOD path PROTOCOL"part); - order by count descending, then by path ascending; return fewer than
kpaths if there aren't enough.
Example 1
Input: lines = (7 sample lines), k = 2 Output: ["/api/users","/api/orders"] Explanation: /api/users appears 3 times, /api/orders twice.
Hints
Hint 1
A regex like "[A-Z]+ (\S+) [^"]*" extracts the request target.
Hint 2
Strip the query string, count with a hash map, then sort by (−count, path).
Write and run your solution
Loading the editor… (JavaScript is required to run code in your browser.)
Solution and explanation
Show the solution
Time O(n + p log p) for p distinct paths · Space O(p)
One pass: match each line against a regex that captures the request target, drop malformed lines, cut the query string at ?, and count in a hash map. Sorting distinct paths by (-count, path) gives a deterministic order. For huge logs you'd stream the file and keep a size-k heap instead of sorting every path.
Reference solution
function topEndpoints(lines, k) {
const counts = new Map();
for (const line of lines) {
const m = line.match(/"[A-Z]+ (\S+) [^"]*"/);
if (!m) continue;
const path = m[1].split("?")[0];
counts.set(path, (counts.get(path) ?? 0) + 1);
}
return [...counts.entries()]
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1))
.slice(0, k)
.map(([path]) => path);
}
function topEndpoints(lines: string[], k: number): string[] {
const counts = new Map<string, number>();
for (const line of lines) {
const m = /"[A-Z]+ (\S+) [^"]*"/.exec(line);
if (!m) continue;
const path = m[1].split("?")[0];
counts.set(path, (counts.get(path) ?? 0) + 1);
}
return [...counts]
.sort(([pa, ca], [pb, cb]) => cb - ca || pa.localeCompare(pb))
.slice(0, k)
.map(([path]) => path);
}
import re
from collections import Counter
REQUEST = re.compile(r'"[A-Z]+ (\S+) [^"]*"')
def top_endpoints(lines: list[str], k: int) -> list[str]:
counts = Counter()
for line in lines:
m = REQUEST.search(line)
if m:
counts[m.group(1).split("?", 1)[0]] += 1
return [path for path, _ in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))[:k]]
<?php
function topEndpoints(array $lines, int $k): array
{
$counts = [];
foreach ($lines as $line) {
if (!preg_match('/"[A-Z]+ (\S+) [^"]*"/', $line, $m)) {
continue;
}
$path = strtok($m[1], '?');
$counts[$path] = ($counts[$path] ?? 0) + 1;
}
uksort($counts, fn($a, $b) => [$counts[$b], $a] <=> [$counts[$a], $b]);
return array_slice(array_map('strval', array_keys($counts)), 0, $k);
}
class Solution {
private static final java.util.regex.Pattern REQUEST = java.util.regex.Pattern.compile("\"[A-Z]+ (\\S+) [^\"]*\"");
public String[] topEndpoints(String[] lines, int k) {
Map<String, Integer> counts = new HashMap<>();
for (String line : lines) {
var m = REQUEST.matcher(line);
if (!m.find()) continue;
counts.merge(m.group(1).split("\\?", 2)[0], 1, Integer::sum);
}
return counts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey()))
.limit(k)
.map(Map.Entry::getKey)
.toArray(String[]::new);
}
}
Common mistakes
- Counting
/api/usersand/api/users?page=2as different endpoints. - Unstable ordering for ties — always define a tie-breaker.
- Crashing on a malformed line instead of skipping it.
Follow-up questions
- Group by route template (
/users/123→/users/:id). - Compute p95 latency per endpoint.
- Process a multi-GB file with constant memory.
More Data Processing problems
- Group Events into Sessions · Easy
- Flatten Nested JSON · Medium
- Revenue Report from CSV · Easy