skip to content

Where does a cross-field rule such as confirm-password or a start-before-end date range attach, and when must it re-run?

level: middleimportance: should knowfreq 52%

answer

  1. the subject is a pair, not a field
  2. declare the inputs you read
  3. a change re-runs every rule that reads it
  4. wait for both sides before comparing

basics

~20 s

It attaches to the group that holds both values, or to one field that declares a dependency on the other, and it must re-run when either participant changes - not only the field the user edited, or its error goes stale.

solid answer

~50 s

A cross-field rule has a pair, not a field, as its subject, so hanging it off one field alone is what produces the classic stale error: the user fixes the first password and the confirm field still says the values do not match. Two workable shapes. **Attach to the group**: the rule lives on the object holding both values, runs when any participant changes, and nominates which field displays the message. **Attach to one field with a declared dependency**: the rule lives on the confirm field but registers the other field as an input, so the form re-runs it when that field changes. Either way the form needs a dependency map from a changed field to the rules that must re-run. Hold the verdict until both participants have usable values, so an empty end date is reported as required rather than as an invalid range, and let one field own the message.

go deeper

for a junior

Recall that a rule comparing two fields belongs to the pair, and that editing either field has to re-check it - otherwise the mismatch message stays after the fix.

for a middle

Explain the two attachment shapes, group-level and declared-dependency, and the dependency map that makes a change re-run every rule reading that field.

for a senior

Bring the sequencing judgment: per-field rules before comparisons, holding the verdict until both sides are usable, one deterministic message owner, and clearing through the dependency map.

for a principal

Set the convention across the product: where cross-field rules are declared, how dependencies are expressed so they cannot be forgotten, and which field owns a pair's message, so error behaviour is uniform.

Most rules are a function of one value. A cross-field rule is a function of two or more - passwords that must match, a range whose end must not precede its start, a total that must equal the sum of its parts, a field that becomes required because of another field's answer. Everything awkward about them follows from that arity. ## Where the rule attaches - **On the enclosing group.** The rule is declared where both values are visible - the form object, or the sub-object holding the pair. It runs whenever any value inside that group changes, and it names the field that should display its message. This is the shape most declarative schemas use, because the rule's scope and its inputs coincide. - **On one field, with declared dependencies.** The rule sits on the field that will show the message, and lists the other fields it reads. The form builds a dependency map and re-runs the rule when any listed field changes. This keeps errors co-located with the field that displays them, at the cost of a registration step that is easy to forget. What does not work is a rule attached to one field that silently reaches for another value at run time. It produces a correct verdict at the wrong moments: it runs when its own field changes, and not when the value it secretly depends on does. ## The dependency-map problem The requirement is one sentence long: **a change to a field re-runs every rule that reads that field**, not merely the rules declared on it. Getting it wrong looks like this: 1. The user types a password, then a mismatching confirmation. The pair rule runs, and the confirm field shows a mismatch. 2. The user corrects the *first* field. Its own rules re-run and pass. 3. Nothing re-ran the pair rule, so the confirm field still shows the mismatch over two values that now agree. The user's only escape is to touch the confirm field again, which they have no reason to do, since the message is pointing at a field they consider finished. | Rule shape | Re-runs when | Message shown on | |---|---|---| | Single-field rule | That field changes | That field | | Pair rule on the group | Either participant changes | A nominated field, or the group | | Pair rule on one field with declared inputs | That field or any declared input changes | The declaring field | | Pair rule reaching for another value implicitly | Only its own field changes (the bug) | The declaring field, staler than it looks | ## Timing at the edges - **Wait for both participants.** A range rule over an empty end date has nothing to compare. Report the emptiness with the field's own required rule and hold the range verdict until both sides have values, otherwise the user gets a confusing complaint about the range while the real problem is a missing value. - **Order the rules.** Run cheap per-field rules first and let the cross-field rule run only when its inputs individually pass. A date-range comparison against an unparseable date produces nonsense. - **Pick one owner for the message.** Printing the same complaint on both fields doubles the noise; the convention is to show it on the second field of the pair, the one the user most recently completed, and to leave the first clean. - **Clear on both paths.** When the rule passes, the message must disappear even if the field that shows it was not the field that changed. This is the same eager-clearing discipline single-field rules need, applied through the dependency map. - **Respect the display gate.** A cross-field error should still wait for the participants to be touched, or for a submit attempt. A rule that fires the instant the first of two fields is filled accuses the user of a mismatch they were never given a chance to avoid. ## Two-or-more, and conditional requirements The same machinery covers rules with more than two inputs (a set of percentages that must sum to a total) and rules where one field's answer changes another field's rule entirely - a shipping method that makes an address required. In the second case the dependency is on a *rule*, not just a verdict: changing the controlling field changes which rules apply to the dependent one, so the dependent field must be re-judged, and a message raised under the previous configuration must be dropped rather than left standing. ## What the framework does and does not give you A runtime with fine-grained tracking makes this almost free: express the verdict as a derived value that reads both fields, and the framework re-computes it exactly when either changes, because the dependency is observed rather than declared. A runtime that re-runs the component function recomputes everything anyway, so the derivation is also automatic - the risk there is a rule wired to a change handler rather than to the value. Where rules are registered imperatively with a form library, the dependency list is manual, and a missing entry is invisible until a user fixes the wrong half of a pair.

  • Why is a mismatch error left standing after the user fixes the other field the single most common cross-field bug?
    Because the rule was re-run only for the field it was declared on. Fixing the first password changes a value the rule reads but is not attached to, so nothing recomputes the pair. The form needs a map from changed field to dependent rules, and the display must clear when the recomputed verdict passes, even on a field the user did not touch.
  • Which of a pair of fields should display the cross-field message?
    One of them, consistently - conventionally the later field, since it is the one the user has just completed and the one whose value they are most likely to adjust. Showing the message on both prints the same complaint twice and leaves two messages to clear. The choice matters less than making it deterministic across the form.
  • An end-date field is empty. Should the range rule report an invalid range?
    No. Let the field's own required rule speak for the missing value and hold the range verdict until both participants have usable values. Comparing against an absent or unparseable value produces a message about the wrong problem, and the user cannot act on a range complaint when the real issue is that one side has not been filled in.

saying these in an interview costs you the question

  • Attaches a pair rule to one field and reads the other implicitly
  • Re-runs only the edited field's rules after a change
  • Reports an invalid range while one side is still empty
  • Prints the same cross-field message on both fields
  • Leaves a mismatch message standing after the partner field is fixed
  • Runs a comparison before each side's own rules have passed