skip to content

Column Types and What They Cost

Every column carries one representation, chosen by a guess or by you and paid for once per row. Most production surprises here are a type that moved and nobody checked.

on this pageshow

explore

questions

20

A column holds 10 million values each stored in the same declared 8 bytes — how much does it occupy, and what is left out?

level: juniorimportance: must knowfreq 64%

answer

  1. start from bytes per row
  2. declared width, not value magnitude
  3. width times row count
  4. the packed value buffer only
  5. text breaks the multiplication

basics

~20 s

Eighty million bytes: the declared width times the row count. That figure is the packed value buffer only — anything stored beside it, such as an absence mask, the held-once values behind codes, or the table's row labels, is extra.

solid answer

~50 s

This is a fixed-width numeric column: every row occupies the same declared number of bytes, and a value outside that range cannot be represented at all. So the per-column figure is one multiplication — 8 bytes times 10,000,000 rows, or 80,000,000 bytes, about 76 MiB. Nothing about the individual values enters it: a row holding `3` and a row holding three billion each cost 8 bytes, because the width belongs to the column's representation and not to the value. The two levers are therefore the declared width and the row count. What the multiplication gives you is the packed value buffer. It does not include a separate absence mask if the design keeps one, the distinct values held once behind a column of codes, or the table's row labels — and it is the wrong tool entirely for a column whose rows hold references to values allocated elsewhere.

code

pseudocode · 14 lines
pseudocode
# per-column figure for a fixed-width numeric column
width_bytes = 8            # declared once, for the whole column
row_count   = 10000000

value_buffer_bytes = width_bytes * row_count          # 80,000,000

# same arithmetic, narrower declared width
narrow_buffer_bytes = 2 * row_count                   # 20,000,000

# what the two lines above do NOT contain:
#   an absence mask beside the values   (~row_count / 8 bytes)
#   the distinct values behind a coded column
#   the table's row labels
#   anything a stored reference points at

go deeper

for a junior

Recall the one multiplication: bytes per row times number of rows. Know that the width is declared for the column, so how big the numbers are does not change the answer.

for a middle

Explain why the multiplication is exact for a packed buffer — no per-value header, no reference to follow — and name the things it does not count: an absence mask, the values held once behind codes, the row labels.

for a senior

Be able to size a real table from its column list in your head, and to say which of its columns have a figure you trust and which have only a floor. The honesty about the floor is the part interviewers listen for.

for a principal

The lever you own is the declared width itself. Narrowing a column halves its bytes and halves the range it can represent; that trade, made once at the table's boundary, is worth more than any later tuning.

## The arithmetic A column's **representation** is the one physical form every value in that column is stored in. When that representation is a **fixed-width numeric column** — every row occupies the same declared number of bytes, and a value outside that range simply cannot be represented — the cost model is a single multiplication: ``` declared_width_in_bytes * row_count = bytes in the value buffer 8 * 10000000 = 80000000 bytes (about 76 MiB) ``` Eighty million bytes. This is not an estimate with error bars around it: for this representation it is the actual size of the packed block of values, because the representation guarantees every row costs the same. ## Why the width does not depend on the values The most common wrong instinct is that a column holding large numbers costs more than one holding small numbers. Under this representation it does not: - The width is fixed **when the column's representation is chosen**, not per value. A row holding 3 and a row holding three billion each occupy the declared width. - There is **no per-value header** — no length, no kind tag, no reference to follow. The buffer is values back to back. - Changing the declared width is therefore the only lever on this figure. Halving it halves the column, and it also halves the range the column can represent. That is the trade. - The row count is the other lever, and it is usually not yours to set. This is also why the figure needs no measurement. You need two numbers — declared width and row count — and both are known before a single value has been read. ## What the figure includes, and what it does not The multiplication describes **the packed value buffer**. A column in practice may carry more beside it, and a table certainly does. | Thing | In the width-times-rows figure? | |---|---| | The packed values themselves | Yes — this is exactly what it counts | | A separate absence mask beside the values | No — a second structure, roughly one bit per row | | The distinct values held once behind a column of codes | No — the figure counts the per-row codes only | | The table's row labels | No — they belong to the table, not to any column | | Anything a stored value refers to elsewhere | No — the buffer holds only the reference | That last row is the one that undoes people, and it is why the arithmetic is reliable for numbers and unreliable for anything separately allocated. ## Where the same multiplication stops being right Two cases, and at this level only two matter: 1. **Text.** Depending on the design, a column of text is either one reference per row pointing at values allocated elsewhere, or one contiguous block of bytes with a position recorded per row. Under the first, width times rows counts the references and misses everything they point at. 2. **Any value reached through a reference.** The same applies to the **holder-for-anything representation** — a column that stores one reference per row and lets each row be a different kind of thing. Its buffer is a slot per row; the cost is elsewhere. In both cases the multiplication still returns a real number. It is simply not the number you wanted: it is the size of the column's own buffer, and the values live somewhere else. ## A worked column list Take a ten-million-row table with four columns: | Column | Representation | Per-row bytes | Value buffer | |---|---|---|---| | identifier | fixed-width numeric | 8 | 80,000,000 | | quantity | fixed-width numeric, narrower | 2 | 20,000,000 | | ratio | fixed-width numeric | 8 | 80,000,000 | | description | text held as references | 8 (the reference) | 80,000,000, plus everything referred to | Three of those four figures are the whole story for their column. The fourth is a floor. Quoting 260 MB for this table would be arithmetic done correctly and an answer that is wrong — the fourth column's real cost is not in it, and neither are the row labels or any absence masks. ## Doing the sizing 1. List the columns and the representation each one carries. 2. For every fixed-width numeric column, multiply declared width by row count. 3. For a column of text or of references, say out loud which storage form it uses; the multiplication is the floor, not the answer. 4. Add the per-column figures, then name what the sum still leaves out. A candidate who does steps 1 and 2 in their head and is honest about step 3 is doing the whole job this question asks for. The trap is stopping after step 2 and calling the result "the table".

  • Why does the same arithmetic not survive contact with a column of text?
    Because under one of the two common storage forms the column's buffer holds a reference per row rather than the value. The multiplication then measures the slots, and every value sits in a separate allocation elsewhere with its own bookkeeping. Under the other form — one contiguous block of bytes plus a position per row — the arithmetic is honest again, but it is characters plus an offset, not a single declared width.
  • Does the estimate change if half the values in the column are identical?
    No. Every row pays the declared width whatever value it holds, and the arithmetic never looks at the values. A representation that stores repeated values as short codes instead is a different representation with a different per-row width, and you would redo the multiplication for that width rather than adjust this one.
  • Ten million rows at 8 bytes — is that 80 MB or 76 MB?
    Both, depending on the unit. It is exactly 80,000,000 bytes, which is 80 MB counting in decimal megabytes and about 76.3 MiB counting in binary ones. The gap is about 5% and it is worth naming when you quote a figure, because the person you hand it to may be reading a tool that uses the other unit.

saying these in an interview costs you the question

  • Says a column of large numbers costs more than one of small numbers.
  • Cannot give a figure without measuring the stored values first.
  • Reports the value buffer as the whole table's size.
  • Applies width times rows to a column of text without qualifying it.
  • Thinks every row carries its own length or kind tag.
open as a page

A table is built from in-memory records whose account identifier is all digits — why state that column's type?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Stating the column's type at construction fixes it as text before any value is stored, so leading zeros survive and long identifiers stay exact. Left undecided, digits are treated as a quantity and both are lost for good.

open as a page

An average over a column of whole-number counts comes back fractional. Why is the result's representation a property of the operation?

level: juniorimportance: must knowfreq 72%

basics

~20 s

Every operation has a result rule that fixes what its output column is made of; the inputs only feed it. An average is a division, defined over fractional values, so whole numbers in still gives fractional out.

open as a page

A column of a million quantities is built and one entry is a word. Why does that one entry decide how all million are stored?

level: juniorimportance: must knowfreq 70%

basics

~20 s

A column carries one representation for all its rows, so no row can be special. The misfit forces a whole-column choice: fall back to a form that holds anything, refuse the value, or tag every row with its kind.

open as a page

A column of 5 million text values is measured two ways and the figures differ tenfold — what storage form makes that possible?

level: middleimportance: must knowfreq 66%

basics

~20 s

One reference per row, with the values allocated separately elsewhere. The column's own buffer is a fixed slot per row, so a shallow count stops there; following the references adds every value's bytes plus its per-allocation bookkeeping.

open as a page

You state a column's type when a table is built — when does that refuse a bad value, and when is it only a hint?

level: middleimportance: must knowfreq 58%

basics

~20 s

Two designs exist and they look identical in code. One validates the stated type at construction and refuses a non-conforming value there; the other honours it at construction and lets the next operation re-decide. Know which before relying on it.

open as a page

A 2-million-row column of near-unique order identifiers is held as codes over a value set — why does that cost more, not less?

level: middleimportance: must knowfreq 64%

basics

~10 s

Near-unique values defeat the arrangement: every distinct value is still stored once, and now every row stores a code too. As the distinct count approaches the row count, the codes are pure addition.

open as a page

Two columns with different representations are combined in one arithmetic expression. What decides the representation of the column that comes back?

level: middleimportance: must knowfreq 62%

basics

~20 s

The operation resolves both operands to one representation before it runs, and the result carries that. Across designs the resolution takes three shapes: widen to the more general common type, refuse, or fall back to a representation holding anything.

open as a page

Why is a column that stores one reference per row, each of any kind, slower to compute over and not merely wider?

level: middleimportance: must knowfreq 60%

basics

~20 s

Because the single typed pass is gone. Over a packed buffer the same machine operation repeats for every row; once each row may be anything, each row costs a dereference, a kind check, an operation lookup and a freshly allocated result.

open as a page

A 10-million-row column holds one of six department names. What is actually stored when it is kept as codes, and what does that buy?

level: juniorimportance: should knowfreq 55%

basics

~20 s

Each distinct name is kept once in a value set, and every row stores only a short whole number pointing into it. Per-row cost drops to that code, and equality can become a comparison of codes.

open as a page

A whole-number column gains 200,000 absent values — under which designs does its per-row width change, and under which does it not?

level: middleimportance: should knowfreq 51%

basics

~20 s

It depends where absence is recorded. Borrowing a marker from inside the number range forces the column to a representation wide enough to hold it; a validity bit beside the value leaves the declared width alone and costs roughly one bit per row.

open as a page

A conversion of a text column to numbers meets 40 unparseable values in 100,000 — how does coercing rather than refusing change where they surface?

level: middleimportance: should knowfreq 64%

basics

~20 s

Coercing turns the 40 unparseable values into absence and lets the pipeline continue, so the loss surfaces much later as a figure nobody can explain. Refusing stops at the conversion line, with the offending values still in front of you.

open as a page

Sorting a column of size labels held as codes can give three different orders — what decides which one you get?

level: middleimportance: should knowfreq 49%

basics

~20 s

Three sources compete: the values' own comparison order, an order declared on the column's value set, and the stored codes as assigned. Which one the design consults decides whether small, medium, large sorts in that order.

open as a page

One row of a narrow fixed-width whole-number column is assigned an out-of-range value. What happens to the whole column?

level: middleimportance: should knowfreq 54%

basics

~20 s

The change cannot be local: a column is one buffer in one representation, with no per-row type. Four outcomes are real - refusal, the column widens, it falls back to holding anything, or the value is coerced to fit.

open as a page

What does a column declared to hold one of a small named set of kinds give you that a hold-anything column does not?

level: middleimportance: should knowfreq 42%

basics

~20 s

A closed, named set of kinds and a per-row tag. Consumers can branch over every case that exists, and an unexpected fourth kind is refused rather than absorbed, where a hold-anything column accepts everything and records nothing about what arrived.

open as a page

You sum the per-column byte figures for a 40-column table to size it — what does that sum still leave out?

level: seniorimportance: should knowfreq 46%

basics

~20 s

The sum covers column value buffers only. It leaves out the row labels, each column's absence mask, the value set held once behind a coded column, and the real cost of any column measured shallowly — and it double-counts buffers shared with another live table.

open as a page

Column types are stated only at the first step of a five-step pipeline and a bad value entered at step three — where else should they be stated?

level: seniorimportance: should knowfreq 50%

basics

~20 s

At every point a table is built, not only the first. A step that builds a new table from a previous step's output is a fresh construction with no memory of your declaration, so its columns get their representation from whatever values arrive.

open as a page

Two coded columns of country labels, built from different files, are compared row by row and identical labels come back unequal. Why?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A stored code is only an offset into its own column's value set, so the same number stands for different values in two separately built columns. Comparing codes rather than decoded values compares two unrelated numbering schemes.

open as a page

A total over a fixed-width whole-number column of positive values returns a negative number, and nothing was raised. Why?

level: seniorimportance: should knowfreq 47%

basics

~20 s

The accumulated value passed the top of the representation's range and wrapped into its negative part. Wrapping is a defined result for a fixed-width form, not an error, so nothing is reported and the answer looks like data.

open as a page

Two million rows carry a quantity column where a few dozen entries are not quantities; which disposition do you take, and what does each cost downstream?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Decide at the boundary, not downstream. Refusing stops the batch at a known line and keeps the column typed; falling back keeps the batch running and taxes every later pass; a tagged union keeps the evidence and makes every consumer branch.

open as a page