skip to content

What is a junk dimension, and when do you collapse fact-table flags into one?

level: middleimportance: should knowfreq 55%

answer

  1. what happens to the leftover flags?
  2. one row per combination, not per flag
  3. six two-row dimensions is the wrong answer
  4. cross-product or observed combinations
  5. codes become readable labels here

basics

~20 s

A junk dimension collapses several low-cardinality flags and codes — order type, payment type, gift flag, ship mode — into one small dimension whose rows are the distinct combinations, replacing many narrow columns in the fact row with a single key.

solid answer

~40 s

After the four-step design, a fact table is often left with a scatter of low-cardinality indicators that belong to no real dimension: `is_gift`, `payment_type`, `ship_mode`, `order_channel`. Leaving them as columns widens every fact row and forces analysts to filter on cryptic codes; giving each its own two-row dimension litters the model with trivial tables. A junk dimension holds one row per distinct **combination** of those flags, with a surrogate key that replaces all of them in the fact row and readable labels alongside the codes. Build it either as the full cross-product when the combinations are few and bounded, or by adding rows as unseen combinations arrive during the load. What belongs in it is bounded, low-cardinality, descriptive and attribute-free; anything high-cardinality, continuous or carrying its own attributes does not.

code

text · 11 lines
text
BEFORE: fact_orders
 date_key | customer_key | channel | pay_type | ship_mode | gift | amount
 20260801 |         4471 | Web     | Card     | Express   | Y    | 129.00

AFTER: fact_orders
 date_key | customer_key | order_indicator_key | amount
 20260801 |         4471 |                  17 | 129.00

dim_order_indicator
 key | order_channel | payment_type | ship_mode | gift_wrap_flag
  17 | Web           | Card         | Express   | Yes

go deeper

for a junior

Know the term and the shape: several small yes/no or few-value columns gathered into one dimension whose rows are the combinations, replaced in the fact table by one key.

for a middle

Explain the mechanics and the alternatives you rejected — flags on the fact row versus one tiny dimension each — and be able to compute the cross-product size and say which population strategy you would pick.

for a senior

Show the operational judgment: which columns you refuse to admit, how you keep key assignment deterministic across loads, and what you do when a new flag arrives after the dimension is published.

for a principal

Own the convention across many fact tables: how these dimensions are named and governed, when flags should instead become a real dimension, and how you stop the pattern from turning into an unbounded catch-all.

## What is left over after dimension design When you design a fact table you pull the meaningful context out into dimensions — customer, product, date, store, promotion. What is frequently left behind is a residue of small indicators that came off the source transaction and describe the transaction itself: an order channel of web/phone/store, a payment type of card/cash/invoice, a ship mode of standard/express, a gift-wrap flag, a rush flag, a return-eligible flag. Each has a handful of values, none has descriptive attributes of its own, and none belongs to an existing dimension. Two bad answers present themselves. Leave them as columns on the fact table, and the fact row grows a tail of narrow codes that analysts must memorize (`payment_type = 'C'` — card or cash?). Or give each one its own dimension table, and you end up with six two-row tables and six joins in every query, which nobody enjoys reading. ## The junk dimension The third answer is to put them all in one dimension whose rows are the distinct **combinations** of the flag values, with a single surrogate key. The fact table then carries one extra key instead of six columns. ```sql CREATE TABLE dim_order_indicator ( order_indicator_key INTEGER PRIMARY KEY, order_channel VARCHAR(20) NOT NULL, -- Web / Phone / Store payment_type VARCHAR(20) NOT NULL, -- Card / Cash / Invoice ship_mode VARCHAR(20) NOT NULL, -- Standard / Express gift_wrap_flag VARCHAR(3) NOT NULL -- Yes / No ); ``` Two things happen that are worth more than the saved bytes. First, the codes become labels: the dimension is where you decode `C` into `Card`, so every report reads the same words and nobody re-invents a CASE expression. Second, the combinations become browsable — an analyst can look at the dimension and see which combinations actually occur, and correlated filters ("express, gift-wrapped, paid by card") land on one join. ## Two ways to populate it **Full cross-product.** If the columns are bounded and small, enumerate every combination up front. Three columns with 3, 3 and 2 values plus a yes/no flag give 3 x 3 x 2 x 2 = 36 rows; loading facts is then a straight lookup that can never miss. The cost is rows that never occur, which is irrelevant at this size. **Observed combinations.** If the cross-product is large but the real data is sparse — say 4,000 theoretical combinations of which 200 ever appear — build it incrementally: during each load, find combinations not yet present, insert them with new surrogate keys, then look up. This keeps the dimension honest about the business but adds a step that must be deterministic, so that the same combination always resolves to the same key. A sensible instinct: keep the row count in the hundreds or low thousands. A junk dimension is meant to be small enough that reading the whole thing is trivial. ## What belongs, and what does not Belongs: low cardinality (a few values each), bounded and slowly changing value sets, descriptive with no attributes of their own, and useful as filters or groupings. Does not belong: high-cardinality identifiers (those are degenerate dimensions and stay in the fact row), continuous values such as amounts, weights or timestamps (either a measure or a banded attribute), anything with its own descriptive attributes (it deserves a real dimension), and anything that already exists in a proper dimension (do not duplicate the customer's segment here). It is also fine to have more than one junk dimension on a fact when the flags cluster into unrelated groups — for example one for order-handling indicators and one for audit/quality indicators — if merging them would multiply the combinations without helping anyone. ## Trade-offs to name in an interview - **Filtering.** A filter on one flag now requires the join. That is one join instead of none, but the dimension is tiny. - **Adding a flag later.** A new column changes the combination set, so keys must be reissued or the dimension rebuilt and the facts re-keyed. Adding flags is not free, which argues for enumerating a bounded cross-product up front where you can. - **Cardinality discipline.** The pattern only works while the combination count stays small; one careless high-cardinality column destroys it. - **Naming.** "Junk" is the textbook term but a poor name to ship; teams usually name the table for its subject, such as `dim_order_indicator`. ## The confusion to avoid A junk dimension is not a degenerate dimension. Degenerate is one high-cardinality identifier with no table at all; junk is several low-cardinality flags gathered into one table. They frequently appear on the same fact row, and interviewers use the pair to check that the vocabulary is genuinely understood rather than memorized.

  • Would you enumerate the full cross-product or only the combinations that actually occur?
    Enumerate the cross-product when it is small and bounded — a few dozen or few hundred rows — because the fact load becomes a lookup that can never miss. Build only observed combinations when the theoretical product is large and the data sparse, accepting an insert-unseen-combinations step that must assign keys deterministically.
  • What happens when the business adds a seventh flag to a junk dimension you already built?
    The combination set changes, so either you rebuild the dimension with new keys and re-key the affected facts, or you extend it with the new column defaulted for existing rows and add the new combinations going forward. Neither is free, which is why bounded cross-products are enumerated up front where possible.
  • Why not just leave five flag columns on the fact table?
    They widen every fact row, they force each report to decode cryptic values independently, and they give correlated filters no single place to live. A junk dimension centralizes the labels and replaces the columns with one key, at the cost of one small join.

It is the kitchen drawer of the model: not junk in the sense of worthless, but the drawer where all the small useful odds and ends live together instead of each getting its own cupboard.

saying these in an interview costs you the question

  • Puts high-cardinality identifiers into the junk dimension
  • Says one row per flag instead of per combination
  • Creates a separate two-row dimension for every flag
  • Thinks 'junk' means the data is unimportant
  • Adds attributes that already exist in a proper dimension

context