Home DSA Top K Frequent Elements: 5 Smart Tricks That Save the Day
Intermediate 3 min · September 07, 2026

Top K Frequent Elements: 5 Smart Tricks That Save the Day

Top K Frequent Elements in O(n) with bucket sort.

N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Production
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
Before you start⏱ 18 min
  • Hash-map frequency counting
  • Heap basics (or willingness to learn buckets)
  • Big-O: linear vs log-linear time
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Top K Frequent Elements asks for the k most common values (e.g. [1,1,1,2,2,3], k=2 gives [1,2])
  • Optimal approach: count frequencies, bucket numbers by count (n+1 buckets), walk down collecting k — O(n) time, O(n) space
  • Key trick: frequencies range over 1..n, so counts themselves are bucket indices — no comparison sort needed
  • Asked at Amazon, Meta, and Google in most hashing rounds — the standard linear-selection filter
✦ Definition~90s read
What is Top K Frequent Elements?

Top K Frequent Elements is LeetCode 347, a Medium hashing problem and the canonical linear-selection interview question. Given up to 10^5 integers and k, you return the k most common values in any order. It appears at Amazon, Meta, Google, and Microsoft because it tests whether candidates see past sort-all to bounded-range selection.

Imagine an election with thousands of ballots.

The solution counts frequencies, drops numbers into n+1 buckets indexed by count, and walks down collecting k — O(n) time, O(n) space, zero comparisons. Heaps give the O(n log k) streaming alternative; QuickSelect gives the average-linear threshold alternative; hash-partitioning scales counting to clusters. One problem teaches four selection regimes, which is why interviewers never stop asking it.

Plain-English First

Imagine an election with thousands of ballots. First, tally votes per candidate — one pass through the pile. Now, instead of sorting candidates (slow), prepare numbered boxes 1 through N and drop each candidate's name card into the box matching their vote total. Then open boxes from the highest number downward, collecting name cards until you hold K winners. Every ballot is touched twice — once to tally, once to box — so the whole count finishes in linear time. No pairwise comparisons anywhere.

⚙ Browser compatibility
Latest versions — ✓ supported
ChromeFirefoxSafariEdge

Top K Frequent looks like a sorting problem. It's a counting problem in disguise.

The reflex is count-then-sort-all. That runs O(u log u) on distinct elements — fine, but it's not linear. You'll pass and still miss the point. The follow-up 'now do it in O(n)' ends rounds.

The answer is bucket sort on frequencies. Counts range from 1 to n, so index buckets by frequency and walk down. That's O(n) time, O(n) space, and the pattern behind a dozen frequency problems. Learn the buckets, not just the heap.

Return the K Most Frequent Values, in Any Order

Given integer array nums and integer k, return the k most frequent elements in any order. Example: [1,1,1,2,2,3], k=2 → [1,2] (frequencies 3,2,1). Guaranteed: k is valid (1 <= k <= distinct count). Ties beyond k accept any choice.

Constraints: n to 10^5, values ±10^4, k ≤ distinct count. O(n log n) full sorts pass but invite the linear follow-up. Output order is free — graders compare sets.

Walk the example: counts {1:3, 2:2, 3:1}. Buckets size 7 (n+1=7): bucket[3]=[1], bucket[2]=[2], bucket[1]=[3]. Walk down from 6: empty... 3 → take 1; 2 → take 2. Collected 2 = k. Done: [1,2]. Each number placed once, read once.

📊 Production Insight
Write the frequency table {1:3, 2:2, 3:1} on the board before coding. Candidates who tabulate first never sort by value later.
🎯 Key Takeaway
Count, bucket by frequency, walk down to k — trace [1,1,1,2,2,3] to [1,2] first.

Sorting by Frequency Is O(n log n) — Slower Than Required

Count-then-full-sort: Counter in O(n), then sort u distinct keys by frequency in O(u log u). Total O(n + u log u), space O(n). At u = 10^5 that's ~1.7M comparisons — passes in PyPy usually, but it is not linear and dies on strict follow-ups.

Min-heap-of-k improves selection to O(n log k): push (freq, num), cap size at k. Good when k is tiny; converges to full-sort cost as k → u. Neither is the linear answer.

Interview play: 'Sort-all is O(u log u) — passes but not linear. Frequencies are bounded by n, so buckets give true O(n).' Claim nothing you can't prove; the bound is the interview.

📊 Production Insight
Write the full cost expression O(n + u log u) on the board. Interviewers trust candidates who quantify the part they are about to eliminate.
🎯 Key Takeaway
Count-then-sort costs O(n + u log u) — the sort dominates; name it before optimizing.

Bucket Sort: Index by Frequency Instead of by Value

Frequencies lie in 1..n, a bounded integer range — bucket sort applies. Steps: counts = Counter(nums) O(n); buckets = [n+1 empty lists]; for num, c in counts.items(): buckets[c].append(num) — each distinct placed once; walk freq from n down to 1, extending result, stopping at exactly k.

Why linear: counting touches n elements; placement touches u distinct; collection scans n+1 buckets plus k picks. Total O(n). Space O(n): counts plus buckets hold u references. Correctness: bucket[c] holds exactly the numbers with frequency c, so descending collection yields non-increasing frequencies and the first k are top-k (ties arbitrary but valid).

Beats heaps asymptotically when k grows: heap costs O(n log k) → O(n log n) at k ~ u, buckets stay O(n). Heaps win only on streaming memory (O(k) selection state) — name that trade-off live.

📊 Production Insight
Say 'frequencies are bounded by n, so they index the buckets' — that one sentence is the entire linear-time proof.
🎯 Key Takeaway
Bounded frequencies index buckets directly — O(n) time with no comparisons anywhere.

The Bucket-Sort Solution in Full Python

The code above is the complete LeetCode submission. Trace [1,1,1,2,2,3], k=2: counts {1:3,2:2,3:1}; buckets[3]=[1], buckets[2]=[2], buckets[1]=[3]; walk 6..4 empty, 3 → [1], 2 → [1,2], len == k → return [1,2].

Trace [7,7,7], k=1: buckets size 4, buckets[3]=[7]; walk 3 → [7], return. No crash — slot n exists. Complexity O(n) time, O(n) space.

solution.pyPYTHON
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import Counter

class Solution:
    def topKFrequent(self, nums: list[int], k: int) -> list[int]:
        counts = Counter(nums)
        buckets: list[list[int]] = [[] for _ in range(len(nums) + 1)]
        for num, c in counts.items():
            buckets[c].append(num)
        res: list[int] = []
        for freq in range(len(nums), 0, -1):
            for num in buckets[freq]:
                res.append(num)
                if len(res) == k:
                    return res
        return res
⚠ The Two Sizing Rules
Buckets size n+1 (not n, not k) — max frequency n needs slot n. Collect exactly k — ties don't excuse extras.
📊 Production Insight
Run [7,7,7] k=1 live after writing. It validates the n+1 sizing — the crash site of half of all bucket implementations.
🎯 Key Takeaway
Counter, n+1 buckets, descending collect with early stop — verify on ties and all-same.

Ties at the K Boundary, k = n and Single-Element Arrays

All-same [7,7,7] k=1 → [7] (needs slot n). Single [1] k=1 → [1]. Full-tie [1,1,2,2,3,3] k=2 → any 2 of the 3 (grader accepts sets). k = distinct count returns everything. Negative and large values behave identically — hashing is value-agnostic.

Empty-bucket walk cost: scanning n+1 buckets is O(n) regardless — no action needed. Early-stop placement (return inside loops) avoids flag variables; confirm both loops exit via the return, not just the inner one.

📊 Production Insight
The full-tie case is the highest-value probe: it fails every collect-a-whole-bucket implementation while passing all 'normal' tests.
🎯 Key Takeaway
Test all-same, single, full-tie, k-equals-distinct — four inputs cover sizing and stop rules.

Why Buckets Beat a Heap Once k Approaches n

Time O(n): count (n) + place (u) + scan buckets (n+1) + collect (k) — all linear terms. Space O(n): counts (u) + buckets (u references + n+1 lists). Optimal in the comparison-free sense: each element must be counted (Ω(n) floor), and counts must be stored (Ω(u) floor).

Whiteboard closer: 'Bounded frequencies index buckets — linear, no comparisons.' Then bridge: 'Streaming data swaps buckets for a k-capped heap O(n log k); clusters shard by hash and merge counts.' One problem, three deployment stories.

📊 Production Insight
Close by naming the bounded-range principle. Interviewers file it as pattern knowledge that transfers to sorts, counts, and histograms.
🎯 Key Takeaway
Count + place + scan are all linear; bounded range is what defeats the comparison-sort floor.
● Production incidentPOST-MORTEMseverity: high

The 23-Minute Full Sort That Wasn't O(n)

Symptom
The sort version passed small tests but timed out on 105-length stress input; the bucket rewrite then IndexErrored on [7,7,7] k=1 with 12 minutes left.
Assumption
The candidate assumed sorting distinct elements by frequency was O(n) 'because Counter is linear' — never noticing the sort dominates at O(u log u). They believed passing tests equaled optimal complexity.
Root cause
sorted(count.items(), key=freq) on 100k distinct keys cost ~1.7M comparisons (TLE-adjacent in Python with overhead), and the first bucket attempt sized n instead of n+1 — crashing on all-same input before the rewrite.
Fix
With 10 minutes left the interviewer asked for the complexity at 100k distinct keys. The candidate admitted O(u log u), rebuilt as frequency buckets in 6 minutes, and passed — graded hire-leaning for honest analysis under pressure.
Key lesson
  • Counting is linear; sorting the counts is not — state the full O(n + u log u) honestly before optimizing.
  • Frequencies live in 1..n, so buckets index them directly — bounded ranges always hint at non-comparison sorts.
Production debug guideFour defect shapes and the exact check that exposes each one.4 entries
Symptom · 01
IndexError on all-same or single-element input
Fix
Change allocation to [[] for _ in range(len(nums) + 1)] and rerun [1] k=1 and [7,7,7] k=1. Frequency n must have a valid slot — test max-frequency inputs explicitly.
Symptom · 02
[1,1,1,2,2,3] k=2 returns 3 instead of 2
Fix
Isolate the ranking key: entries must be (freq, num) or bucket-by-count. Rerun [1,1,1,2,2,3] k=2 expecting {1,2} — if 3 appears, the sort key is the value.
Symptom · 03
Too many elements returned on frequency ties
Fix
Restructure collection with a double-break: stop the moment len(result) == k. Rerun a tied case like [1,1,2,2,3,3] k=2 and assert exactly 2 elements returned.
Symptom · 04
TLE on 100k+ distinct elements
Fix
Confirm no full sort of distinct keys remains: the only sort-like step should be linear bucket placement (or a k-bounded heap). If sorted(count, key=...) spans all keys, replace with buckets for the linear claim.
Top K Frequent Approaches Compared
ApproachTimeSpaceVerdict
Count + sort all by frequencyO(n + u log u)O(n)Passes, but never linear
Count + min-heap of size kO(n log k)O(n)Good, optimal when k is small
Count + heapq.nlargest(k)O(n + u log k)O(n)Good, shortest code
Count + bucket sort by frequencyO(n)O(n)Best: true linear, interview favorite

Key takeaways

1
Top K Frequent reduces to counting then selecting by frequency
never by value.
2
Frequency range 1..n enables bucket sort
index buckets by count, walk down, collect k.
3
Buckets give true O(n) time; heaps give O(n log k)
know which to claim and when.
4
Size buckets at n+1 and stop at exactly k elements to dodge the two classic crashes.
5
The count-then-select skeleton scales to streams (bounded heap) and clusters (hash-partition + merge).

Common mistakes to avoid

4 patterns
×

Sizing buckets at k or n instead of n+1

Symptom
IndexError when one element fills the whole array (frequency n lands outside a size-n table). All-same input [7,7,7] with k=1 crashes.
Fix
Size buckets at n+1 (indices 0..n) since max frequency is n. Index 0 stays empty by construction. Iterate high-to-low collecting until k gathered.
×

Sorting by value instead of by frequency

Symptom
[1,1,1,2,2,3] k=2 returns [2,3] or [1,3] instead of [1,2]. Value order and frequency order are unrelated.
Fix
Compare frequencies, never values: heap entries (freq, num) with nlargest, or bucket by count. Test [1,1,1,2,2,3] k=2 → [1,2]: value 3 is largest yet least frequent — the perfect trap case.
×

Returning a whole bucket instead of exactly k elements

Symptom
k=2 with three items tied at frequency 2 returns 3 elements. LeetCode's length check fails despite 'correct' frequencies.
Fix
Collect across buckets until len(result) == k, breaking out of both loops. Any order within the answer is accepted — but exactly k elements are required.
×

Sorting all distinct elements by frequency (O(u log u))

Symptom
Passes, but claiming O(n) with a full sort is false — and follow-up 'do it in linear time' stalls. Buckets are the linear answer.
Fix
Count with dict/Counter in one pass (O(n)), then select in O(n) via buckets or O(n log k) via heap. Sorting all distinct keys costs O(u log u) — fine but never optimal-claimed.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Solve it in O(n) and prove the bound.
Q02SENIOR
How do you handle a stream too large for buckets?
Q03SENIOR
Scale this to a distributed log too big for one machine?
Q01 of 03SENIOR

Solve it in O(n) and prove the bound.

ANSWER
Count frequencies O(n), allocate n+1 buckets indexed by count, drop each number into its frequency bucket, then walk down from n collecting until k gathered. Counting is linear, each number is placed and read once — O(n) time, O(n) space for counts plus buckets.
FAQ · 5 QUESTIONS

Frequently Asked Questions

01
Does O(n) violate the sorting lower bound?
02
Can I use heapq.nlargest instead of buckets?
03
Is collections.Counter allowed?
04
Does the output order matter?
05
What about QuickSelect on frequencies?
N
Naren Founder & Principal Engineer

20+ years shipping performance-critical code where algorithms decide the bill. Written from production experience, not tutorials.

Follow
Verified
production tested
September 22, 2026
last updated
1,799
articles · all by Naren
🔥

That's Hashing. Mark it forged?

3 min read · try the examples if you haven't

Previous
Trapping Rain Water Problem
12 / 13 · Hashing
Next
House Robber Dynamic Programming