In an Iceberg table partitioned by bucket(16, user_id), why does a range filter on user_id scan every bucket?
answer
- the hash throws the ordering away
- neighbouring ids land in unrelated partitions
- only equality survives the projection
- order-preserving transforms are the contrast
basics
~20 sBucketing hashes the value, so ordering is destroyed and neighbouring ids land in unrelated buckets. Iceberg can project only equality and IN predicates through the bucket transform; a range predicate matches every bucket, so nothing is pruned.
solid answer
~50 sThe `bucket[N]` transform computes a hash of the value and takes it modulo N, so it is deliberately not order preserving: `user_id = 101` and `user_id = 102` almost certainly sit in different buckets. Iceberg prunes by projecting a query predicate through the transform onto each file's stored partition value, and that projection only exists for predicates the transform preserves — equality and `IN` for buckets. A `BETWEEN` or `>` on the bucketed column cannot exclude any bucket, so every partition is planned. Order-preserving transforms like `day`, `truncate` and `identity` do project range predicates, which is the contrast to draw. If range access on that column matters, bucketing is the wrong partition field; if point lookups and even write distribution matter, bucketing is right and you make ranges tolerable with a sort order so per-file bounds do the filtering.
code
sql · 10 linesCREATE TABLE prod.db.events (
user_id bigint, event_ts timestamp, amount decimal(10,2))
USING iceberg
PARTITIONED BY (bucket(16, user_id));
-- one bucket planned
SELECT * FROM prod.db.events WHERE user_id = 4711;
-- all 16 buckets planned: a hash gives no ordering to exploit
SELECT * FROM prod.db.events WHERE user_id BETWEEN 100 AND 200;go deeper
Know that bucketing spreads a key by hashing it, and that hashing loses ordering, so filtering a bucketed column by a range cannot skip partitions the way an equality filter can.
Explain predicate projection: the planner converts a filter on the source column into a filter on the stored partition value, and a hash only supports equality and IN, unlike order-preserving transforms such as day or truncate.
Diagnose it from the plan, then choose a remedy with its cost: sort within buckets so file bounds prune, evolve the spec to an order-preserving field, or keep bucketing because even distribution and key-based joins matter more.
Frame it as choosing which access pattern the physical layout serves. Bucketing buys even file sizes and point-lookup locality at the price of range scans, and that tradeoff should be a documented decision for a shared table, not an accident.
## What the bucket transform does The Iceberg format defines `bucket[N]` as a hash of the source value — a 32-bit Murmur3 hash of its canonical serialized form — reduced to a non-negative integer modulo N. The result is a bucket number stored as that file's partition value. The point of the transform is uniform spread: a high-cardinality key such as a user or order id is scattered evenly across N partitions regardless of how skewed the raw values are, which keeps partition sizes even and gives writers a predictable number of output files. Uniform spread and order preservation are opposites. A good hash destroys locality on purpose, so consecutive ids land in unrelated buckets. ## Why that kills range pruning Iceberg prunes by projecting the user's predicate through the partition transform. The projection has to be sound: it may keep files that turn out to hold no matching rows, but it must never drop a file that holds one. For an order-preserving transform, a range on the source column implies a range on the partition value — if `event_ts >= 2026-05-01` then `day(event_ts) >= 2026-05-01`, so earlier days can be dropped. For a hash, no such implication exists. `user_id BETWEEN 100 AND 200` says nothing about the bucket numbers involved; the 101 values could occupy all 16 buckets. The only sound projection is "every bucket", so nothing is excluded. Equality is different. If `user_id = 4711`, then the file containing it must have bucket value `bucket(4711)`, a value the planner can compute directly. So equality prunes to exactly one bucket out of N, and an `IN` list prunes to at most one bucket per listed value. That is the access pattern bucketing is designed for. ```sql -- prunes to a single bucket SELECT * FROM prod.db.events WHERE user_id = 4711; -- prunes to at most three buckets SELECT * FROM prod.db.events WHERE user_id IN (4711, 4712, 9001); -- touches all 16 buckets: no sound projection through a hash SELECT * FROM prod.db.events WHERE user_id BETWEEN 100 AND 200; ``` ## Diagnosing it in the wild The symptom is a query with a selective-looking predicate whose scan plans every partition. The give-away in the plan is that the partition filter is absent or trivially true while the row-level filter is present. Check the table's partition spec first: if the column in the predicate appears under `bucket`, and the predicate is a range, this is the explanation and no amount of statistics tuning changes it. The same reasoning explains a related surprise: a `truncate` transform *does* preserve order, so ranges and prefix predicates project through it. Candidates who lump the two transforms together as "the non-time ones" usually get this wrong. ## What to do about it There are three honest options, and choosing between them is the senior part of the answer. 1. **Accept it and let file bounds work.** Declare a sort order on the bucketed column so that within each bucket, files hold contiguous id ranges. The range predicate still visits all N buckets' manifests, but per-file lower and upper bounds on `user_id` drop nearly every file, so the bytes actually read collapse even though partition pruning did nothing. This is usually the cheapest fix. 2. **Change the partition field.** If ranges on that column are the dominant access pattern, bucketing is optimising for the wrong query. Partition on a transform that preserves order — a time transform, or `truncate` on the id — and, because spec evolution is metadata-only, you can make the change without rewriting the table, accepting that only new data gets the new layout. 3. **Keep bucketing for its other benefits.** Bucketing on a key gives even file sizes on skewed data, bounds the file count per commit, and lets engines that understand the layout co-locate work on that key for joins and merges. If those matter more than range scans, keep the spec and fix the query patterns instead. ## The interview framing State the mechanism first (hash, modulo N, not order preserving), then the rule (only equality and IN project through it), then the contrast with an order-preserving transform, and finish with the practical remedy. The weak answer says "bucketing is bad for ranges" without explaining projection; the strong one explains why the projection must be sound, which is also why Iceberg never returns wrong results — it just plans more files than you hoped.
- Would an IN list of 500 user ids prune usefully on a bucket(16, user_id) table?It projects, but not usefully: 500 distinct ids hash across essentially all 16 buckets, so the planner keeps them all. Projection through a hash prunes only when the number of distinct values is small relative to the bucket count. With a large IN list you are back to relying on per-file bounds and on whatever other partition fields the spec has.
- Why does truncate behave differently from bucket for range predicates?Truncate is monotonic: truncating preserves relative order for numbers and prefixes for strings, so a range on the source column implies a range on the truncated value and the planner can drop partitions soundly. A hash has no such property, which is exactly what makes it good at spreading skewed keys and bad at range access.
- How would you pick the bucket count N?Aim for partitions that produce well-sized files at your commit cadence: total data per write window divided by N should land near your target file size. Too large an N multiplies small files on every commit; too small leaves partitions too big to prune usefully. Changing N later means a new partition field, since bucket[16] and bucket[32] are different transforms.
saying these in an interview costs you the question
- Says more statistics or a bloom filter would fix the range scan
- Claims bucketing preserves ordering within each bucket
- Treats bucket and truncate as behaving the same way for ranges
- Suggests raising the bucket count to improve range pruning
- Assumes Iceberg returns wrong rows rather than just planning more files