skip to content

Why does subtracting a 3-value single-row operand from a 4-value single-column operand return 12 numbers instead of an error?

level: middleimportance: must knowfreq 60%

answer

  1. count the axes, not the values
  2. lengths lined up from the last axis backwards
  3. an axis of length one is eligible to stretch
  4. four by one against one by three
  5. the count is the tell, not the values

basics

~20 s

Under a rule that lines the two operands' axis lengths up from the last axis backwards and stretches any axis of length one, both operands qualify: one is four by one, the other one by three. Both stretch, and the result is a four-by-three rectangle of every pairing.

solid answer

~50 s

A trailing-axis matching rule works like this: line the two operands' axis lengths up from their last axis backwards, stretch any axis whose length is one, and refuse anything else. A 4-value operand laid out as a single column has lengths four and one; a 3-value operand laid out as a single row has lengths one and three. At each position one side is one and the other is longer, so *both* are stretched and the result has lengths four by three - twelve values, one for every pairing. Nothing went wrong by the rule; the expression asked for a cross table and got one. The tell is never the values, which are all arithmetically correct subtractions of some pair, but the count: twelve results from operands of four and three. The usual cause is an operand that kept an axis of length one from whatever produced it, so what you thought was a flat list of four is four rows of one.

code

pseudocode · 13 lines
pseudocode
left  = 4 values laid out as one column   -> axis lengths (4, 1)
right = 3 values laid out as one row      -> axis lengths (1, 3)

line the lengths up from the last axis backwards:
    last axis:        1 vs 3   -> left is one  -> stretch left to 3
    the one before:   4 vs 1   -> right is one -> stretch right to 4

left - right  ->  axis lengths (4, 3)  ->  12 results, no error

same two operands, both flat:
left  = 4 values  -> axis lengths (4)
right = 3 values  -> axis lengths (3)
left - right  ->  refused: 4 and 3 disagree and neither is one

go deeper

for a junior

Recall that a length-one axis is the thing that gets stretched, and that two operands of four and three values can legally produce twelve results. Knowing the rectangle is possible is most of the value at this level.

for a middle

Work the rule out loud: line the axis lengths up from the last backwards, stretch anything of length one, and show why both operands qualify here. Then name the tell - the result's count, because every value in it is arithmetically correct.

for a senior

Demonstrate the guard rather than the fix: an assertion on the result's number of positions, placed right after the expression, catches this whichever operand picked up the extra axis. Explain why value-level checks never will.

for a principal

The angle is where shape expectations belong in a shared codebase - at step boundaries, at the transform's edge, or not at all - and what it costs a team when orientation is carried implicitly between steps that different people own.

## The rule, stated precisely A **trailing-axis matching rule** is one specific way of resolving operands of unequal size: line the two operands' axis lengths up from their **last axis backwards**, stretch any axis whose length is **one**, and refuse anything else. Everything in this question follows mechanically from that sentence. For a flat list of values there is one axis, and its length is the number of values. For a list laid out as a single column there are two axes: as many rows as values, and one column. For a list laid out as a single row there are also two: one row, and as many columns as values. ## Working the example through Four values as a single column has lengths **4, 1**. Three values as a single row has lengths **1, 3**. Line them up from the right: | axis, counting from the last | left operand | right operand | what the rule does | |---|---|---|---| | last | 1 | 3 | the left is one, so stretch it to 3 | | the one before | 4 | 1 | the right is one, so stretch it to 4 | Both operands had a length-one axis where the other had a longer one, so **both** were stretched. The result has lengths 4 by 3: twelve values, each the difference of one value from the left with one value from the right. This is the rule working exactly as documented. It is not a bug, and nothing here is undefined. ## Why nothing complains The operation was legal. The rule was satisfied on every axis, so there is no error to raise and no warning to print. And every individual number in the result is a correct subtraction - just of a pair you did not intend. That is what makes this failure expensive. A value-level check passes. A spot check of the first few numbers passes. A check that the result contains no absent values passes. **The only signal is the count**: twelve results out of operands holding four and three. ## Where the extra axis comes from Nobody writes a single-row operand against a single-column operand on purpose. The mismatch is inherited: - an earlier operation **preserved the axis it worked along** instead of collapsing it, so a fold that should have produced a flat list of four produced four rows of one; - one operand was read or constructed with an outer wrapper around it, so a list of three became one row of three; - the two operands came from different steps of the pipeline that happen to disagree about orientation, and orientation is invisible when you print a short list. The two operands look identical in a printed preview. Their axis lengths are what differ, and axis lengths are what the rule reads. ## The generalised trap The accidental rectangle is the extreme case of a milder family: **an element-wise expression whose result is bigger than either input**. The moment a length-one axis exists on one side and a longer one on the other, the result takes the longer length. Chain two such expressions and the growth compounds, which is why a memory figure can be surprising even though each individual operand was small. As a working model: 1. Count the axes on each operand, not the values. 2. Line the lengths up from the last axis backwards. 3. Anywhere the two disagree and neither is one, the pair is refused. 4. Anywhere they disagree and one of them is one, that side is stretched and the result takes the larger length. 5. The result's total number of positions is the product of the resulting lengths, which can be much larger than either input. ## The fix and the guard The fix is to make both operands flat - to remove the length-one axis on whichever side carries it - so that the expression matches position by position and returns four results. The **guard** is separate and matters more: assert the result's number of positions immediately after the expression, against the row count you expect. That assertion costs one line, fires on the run that would otherwise have published a cross table, and works regardless of which side picked up the extra axis. And note the boundary: under a rule that has no concept of a length-one axis - one that repeats the shorter operand, or one that matches operands by their row labels instead - the same written expression returns something else entirely. The rectangle is what a trailing-axis rule does, not what mismatched sizes always do.

  • Where does an unwanted axis of length one usually come from?
    From an earlier operation that preserved the axis it worked along rather than collapsing it, so a fold over rows returns four rows of one instead of a flat list of four. Construction can do it too - wrapping a list of three in an outer container makes it one row of three. A printed preview looks the same either way, which is why the axis lengths are what you inspect.
  • Why is the result's value count a better tell than the values themselves?
    Because every value in the rectangle is a correct subtraction of some pair, so nothing looks wrong at value level. Spot checks, absent-value checks and range checks all pass. The count is the only property that contradicts the intent: twelve results from operands of four and three cannot be what a per-position subtraction should give.
  • Would flipping the order of the two operands avoid the rectangle?
    No. The rule reads axis lengths, and both operands keep their lengths whichever side of the operator they sit on. Reversing the subtraction gives a rectangle with the signs flipped and the same twelve positions. The only fix is to remove the length-one axis so the two operands match position by position.

saying these in an interview costs you the question

  • Says a 12-value result from 4 and 3 inputs must be a tool bug
  • Thinks an element-wise result can never exceed the larger input
  • Treats a single-row and a single-column operand as the same thing
  • Inspects the first few result values instead of the result's count
  • Believes reordering or sorting the operands fixes it