skip to content

A relational engine lets you declare one logical table as a set of physical child tables using RANGE, LIST or HASH. Explain what table partitioning is and what each of the three strategies is for.

level: juniorimportance: must knowfreq 60%

answer

  1. One logical table, many child tables, one server
  2. RANGE = intervals, usually time
  3. LIST = explicit value sets
  4. HASH = equal pieces, no meaning
  5. Key must be in the primary key

basics

~20 s

Partitioning splits one logical table into physical pieces inside the same database, chosen by a partition key. RANGE assigns contiguous intervals (usually dates), LIST assigns explicit value sets (region, status), HASH spreads rows evenly by a hash of the key.

solid answer

~60 s

Partitioning declares a parent table with a **partition key** and splits its rows into child tables, all in the same database instance. Applications still read and write the parent; the engine routes each row to the right child. - **RANGE** — each partition owns a contiguous interval of the key: `2026-01-01` to `2026-02-01`, ids 1–1,000,000. Overwhelmingly the most common in practice because the key is usually a date, and time-based data arrives, is queried and is deleted in time order. - **LIST** — each partition owns an explicit set of values: `('EU')`, `('US','CA')`. Used when the key is a small, stable, meaningful set — region, country, tenant class. - **HASH** — rows are assigned by hashing the key modulo the partition count. Used when there is no natural interval or grouping and you only want to break a huge table into equal, more manageable pieces. RANGE and LIST partitions carry meaning, so whole partitions can be attached and detached as a unit; HASH partitions do not, so they are chosen purely for even size. Partitioning is *within one server* — it does not add write capacity the way splitting across servers does.

code

sql · 20 lines
sql
CREATE TABLE events (
  id          bigint       NOT NULL,
  occurred_at timestamptz  NOT NULL,
  region      text         NOT NULL,
  PRIMARY KEY (id, occurred_at)
) PARTITION BY RANGE (occurred_at);

CREATE TABLE events_2026_01 PARTITION OF events
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');

-- LIST
CREATE TABLE accounts (id bigint, region text NOT NULL, PRIMARY KEY (id, region))
  PARTITION BY LIST (region);
CREATE TABLE accounts_eu PARTITION OF accounts FOR VALUES IN ('DE','FR','ES');

-- HASH
CREATE TABLE sessions (user_id bigint NOT NULL, PRIMARY KEY (user_id))
  PARTITION BY HASH (user_id);
CREATE TABLE sessions_p0 PARTITION OF sessions
  FOR VALUES WITH (MODULUS 4, REMAINDER 0);

go deeper

for a junior

Define the parent/child model, name RANGE, LIST and HASH with one example each, and state that it happens inside one server.

for a middle

Derive the strategy from delete and query patterns, and explain the primary-key rule and why a query without the key gains nothing.

for a senior

Discuss partition count, planning overhead, and the fact that partitioning is chosen for manageability first and query speed second.

for a principal

Frame the decision by lifecycle: how data arrives, is queried and is retired over years, and what the choice locks in — a partition key, like a shard key, is expensive to change.

## What partitioning is A partitioned table is one logical table whose rows are physically stored in several separate child tables, called partitions. You declare the split when you create the table by naming a **partition key** — one or more columns — and a **strategy** that says how key values map to partitions. From then on, an insert into the parent is automatically routed to the partition whose bounds accept its key, and a query against the parent transparently reads whichever partitions can contain matching rows. Crucially this all happens inside **one database server**. The partitions share the same process, the same buffer pool, the same write-ahead log, the same CPU. Partitioning reorganises storage and metadata; it does not add hardware. That distinction — partitioning is within a server, sharding is across servers — is the single most common confusion on this topic. ## RANGE partitioning Each partition owns a contiguous, non-overlapping interval of the key, checked as lower bound inclusive, upper bound exclusive. ```sql CREATE TABLE events ( id bigint, occurred_at timestamptz NOT NULL, payload jsonb ) PARTITION BY RANGE (occurred_at); CREATE TABLE events_2026_01 PARTITION OF events FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); ``` This is the dominant real-world case, because most enormous tables are event-shaped: rows are inserted with a timestamp, queried by a time window, and eventually discarded by age. Range partitioning by time aligns all three. Inserts concentrate in the newest partition, whose indexes stay small and hot in cache; window queries touch a handful of partitions; and expiring old data becomes a metadata operation on a whole partition rather than a mass delete. Range also works on numeric keys (id blocks) and on multi-column keys, where the comparison is lexicographic across the listed columns. ## LIST partitioning Each partition enumerates the key values it accepts. ```sql CREATE TABLE accounts (id bigint, region text NOT NULL) PARTITION BY LIST (region); CREATE TABLE accounts_eu PARTITION OF accounts FOR VALUES IN ('DE','FR','ES'); CREATE TABLE accounts_us PARTITION OF accounts FOR VALUES IN ('US','CA'); ``` Use LIST when the key is categorical, the set of values is small and stable, and the categories genuinely matter to how you query or manage the data — for example keeping one region's rows on separate storage, or isolating a category you archive independently. Its weakness is exactly its explicitness: a new value that no partition lists is rejected (or lands in a default partition if you declared one), so LIST needs governance whenever the domain can grow. ## HASH partitioning ```sql CREATE TABLE sessions (user_id bigint NOT NULL, ...) PARTITION BY HASH (user_id); CREATE TABLE sessions_p0 PARTITION OF sessions FOR VALUES WITH (MODULUS 8, REMAINDER 0); -- ... p1..p7 ``` Hash gives you N roughly equal pieces without any semantic meaning. Choose it when the table is too big to manage comfortably but has no natural time or category axis — a large user-keyed table, for instance. The benefits are narrower than range: index maintenance and vacuum/statistics work per partition, and equality lookups on the key prune to one partition. What you do *not* get is the ability to drop a partition to expire data (no partition corresponds to "old"), or cheap range pruning, and changing the partition count means rewriting the table. ## Choosing the strategy Derive it from three things, in this order: 1. **How you delete.** If data expires by age, range on time, full stop — this benefit usually dwarfs the others. 2. **How you query.** The key must appear in the predicates of your important queries; otherwise the engine reads every partition and you have added overhead without benefit. 3. **How data is shaped.** Continuous ordered value → RANGE. Small fixed category set → LIST. Neither, just too big → HASH. The key must be part of the primary key and of every unique constraint on the table, because the engine can only enforce uniqueness within a partition's local index. ## What partitioning is *not* It is not a performance button. A well-indexed 50-million-row table on decent hardware does not need it and will typically get slower with it, since the planner now has more relations to consider and every query that does not carry the key touches every partition. It also does not increase write throughput — same server, same WAL. And it does not replace indexes: each partition still needs its own indexes for lookups within it. ## How to answer Define the parent/child model and the partition key, give the three strategies with a one-line canonical use for each (range = time, list = category, hash = even split of a big table), and volunteer the boundary: this is one server, unlike sharding, and the key must be in your queries for it to pay off.

  • What is the difference between partitioning a table and sharding it?
    Partitioning splits one table into physical pieces inside a single database server; all partitions share that server's CPU, memory, storage and write-ahead log. Sharding splits rows across independent servers, each with its own resources, so it adds write and storage capacity while giving up cross-shard joins, global uniqueness and single-node transactions. Partitioning improves manageability and can enable pruning; it does not add throughput headroom.
  • Why must the partition key be included in the table's primary key and unique constraints?
    Because each partition maintains its own local indexes, and a unique index over one partition cannot see rows in another. If the constraint did not include the partition key, two rows with the same value could land in different partitions and neither index would detect the duplicate. Including the key guarantees all candidate duplicates fall in the same partition, so the local index enforces it.

One filing cabinet with labelled drawers. Range = one drawer per month, list = one drawer per department, hash = drawers numbered 0-7 to keep each a manageable weight.

saying these in an interview costs you the question

  • Describing partitioning as a way to scale writes across machines
  • Assuming partitioning speeds up every query, including ones that never filter on the key
  • Believing partitions do not need their own indexes
  • Choosing HASH for time-series data and then wondering why old data cannot be dropped by partition
  • Declaring a unique constraint that omits the partition key and expecting it to hold table-wide

context