---
name: boundary-value-enumeration
source: https://app.decimal.ai/s/boundary-value-enumeration@1/SKILL.md
source_sha256: 14030b999643
---

# Enumerate the Six Boundary Test Values, Not Just the Two Limits

Handed a field that accepts an integer from a minimum to a maximum and asked which values to test at the edges, the base model names **the two limits themselves** — `min` and `max` — and stops. Sometimes it adds one step inside each. What it reliably omits are the two values **just outside the valid range** (`min-1` and `max+1`), the invalid partitions that expose the most common defect of all: a `>` written where `>=` was meant, an array sized `n` instead of `n+1`, a loop that runs one iteration too few or too many. Testing only `min` and `max` cannot see those bugs — both endpoints are *valid*, so a wrong comparison that lets `min-1` through or rejects `max` slips past.

The knowledge this skill supplies is the full three-points-per-boundary set and the rule for collapsing it when the range is small.

## When to activate

Activate when the input is an **integer field bounded by a fixed lowest and highest allowed value** — an age `18..120`, a quantity `1..99`, a port `1024..65535`, a string *length* `3..30`, a temperature in whole degrees — and the ask is *which specific numbers to test at the edges*. The answer is a concrete list of integers.

Do **not** activate to write the test code or choose a framework, to explain or compare testing techniques in the abstract, to partition a non-numeric field (a status enum, an email, a color name — those have no numeric edge; return an empty set), or to reason about a continuous / floating-point quantity where "just outside" needs an epsilon rather than `±1`.

## The rule: three integers per boundary

For a valid integer range `[min, max]`, the boundary test set is the six values, **three per limit**:

- Lower boundary → `min-1`, `min`, `min+1`
- Upper boundary → `max-1`, `max`, `max+1`

`min` and `max` are the last **valid** values; `min-1` and `max+1` are the first **invalid** values on each side; `min+1` and `max-1` are one step inside. Then **deduplicate and sort ascending** — because when the range is narrow the two boundaries' triples overlap:

- `max - min >= 3` → all six are distinct: six values.
- `max - min == 2` → `min+1` equals `max-1`; they collapse to **five**.
- `max - min == 1` (adjacent limits) → the triples overlap by two values; **four** remain (`min-1, min, max, max+1`).
- `max - min == 0` (a single allowed value, `min == max`) → both triples are the same; **three** remain (`v-1, v, v+1`).

The set never depends on the *sign* or *magnitude* of the limits: if `min` is `0`, the just-outside value is `-1`; if `min` is negative, `min-1` is further negative. Emit every value the rule produces, including negatives and zero.

(Standard boundary-value analysis, three-value variant — paraphrased from the ISTQB Foundation Level syllabus and BS 7925-2, as of 2026-07. https://www.istqb.org — the technique itself is uncontested; the base's gap is omitting the just-outside invalid points.)

## Worked examples

**Rule — include the just-outside invalid values.** Field: a brightness setting accepting `10..90`.

- BEFORE (base): `[10, 90]` — only the two limits. Off-by-one bugs at either edge are invisible.
- AFTER: `[9, 10, 11, 89, 90, 91]` — the limit, one inside, and one outside at each end.

**Rule — a range starting at zero produces a negative just-outside value.** Field: a retry count `0..3`.

- BEFORE (base): `[0, 1, 2, 3]` — it lists the valid values and never tests below the floor.
- AFTER: `[-1, 0, 1, 2, 3]` — `min-1` is `-1`; `min+1 (1)` and `max-1 (2)` already sit among the valid values, so the deduplicated set is five.

**Rule — a single-value range collapses to three.** Field: a lock that accepts exactly `50`.

- BEFORE (base): `[50]` — one value, no edges probed.
- AFTER: `[49, 50, 51]` — `min == max`, so both triples coincide.

**Rule — negative limits shift, they do not disappear.** Field: a thermostat `-2..4`.

- BEFORE (base): `[-2, 4]`.
- AFTER: `[-3, -2, -1, 0, 3, 4, 5]` — `min-1` is `-3`, `max+1` is `5`; nothing about the negative sign changes the recipe.

## Edge cases & exceptions

- **Adjacent limits (`max = min+1`).** The two triples share `min+1 (= max)` and `max-1 (= min)`, so four distinct values remain — do not list six with duplicates.
- **`min-1` crosses zero.** For `1..n` the lower just-outside value is `0`; for `0..n` it is `-1`. Always compute `min-1` literally.
- **String length or size fields.** Test the *length* range as integers (`3..30` → `2,3,4,29,30,31`); the field being text does not make it non-numeric.
- **Non-numeric or unordered fields.** A status label set, an email address, a named-color choice, a checkbox — no ordered integer range exists, so there is nothing to enumerate: return an empty set rather than inventing numbers.
- **Floating-point / continuous ranges.** `±1` is the wrong step size; this skill covers integer ranges only. Do not emit `min-1`/`max+1` as if the field were integral.

## Do / Don't

- **Do** include `min-1` and `max+1` every time — they are the whole point.
- **Don't** stop at `min` and `max`; two valid endpoints cannot catch a wrong comparison operator.
- **Do** deduplicate and sort ascending before answering.
- **Don't** list a value twice when the boundaries overlap on a narrow range.
- **Do** carry the sign through: `0`'s lower neighbor is `-1`, a negative `min`'s neighbor is more negative.
- **Don't** apply the `±1` recipe to a non-integer or unordered field — return empty instead.

## Common mistakes the base makes

1. **Only the two limits.** `[min, max]` — the single most common and most damaging omission.
2. **Forgetting the outside values.** Listing `min, min+1, max-1, max` (four) but dropping `min-1` and `max+1` — the invalid partitions never get tested.
3. **No dedup on narrow ranges.** Emitting six entries for `[0,1]` when only four distinct integers exist.
4. **Refusing to go below zero.** Clamping `min-1` to `0` for a `0..n` range instead of testing `-1`.
5. **Applying it to unordered fields.** Producing integers for a status enum or a color picker that has no numeric edge.

## Quick checklist

1. Is the field an integer with a fixed lowest and highest allowed value? If not (a label set, free text, a name), the set is empty — stop.
2. Write the three lower values: `min-1`, `min`, `min+1`.
3. Write the three upper values: `max-1`, `max`, `max+1`.
4. Compute each literally — carry the sign, let `min-1` go to `0` or negative.
5. Deduplicate (narrow ranges overlap) and sort ascending.
6. Confirm both invalid neighbours (`min-1` and `max+1`) survived — they are the values the base drops.

## Output shape

Return the values as a JSON object: `{"boundary_values": [ ... ]}` — one array, deduplicated, sorted from lowest to highest, integers only. When the input has no ordered integer range (a category, free text, a name, a boolean), the array is empty.

## Deterministic grading

`scripts/grading_spec.json` holds the correct integer set for each benchmark field, and `scripts/grade.py` compares a submitted `boundary_values` array against it as a set — order-independent, with duplicates collapsed on both sides. See the eval suite.
