Boundary value analysis is a test design technique that targets the edges of an input range instead of its middle, because off-by-one defects live at the boundary and nowhere else. The two-value variant tests only the boundary itself and its nearest invalid neighbor, one pair per boundary. The three-value variant adds a second value moving inward, six inputs total across two boundaries instead of four, catching the exact class of < versus <= mistake a mid-range input never touches.
Somewhere in an interview, or a study guide, or a Slack thread where an auditor asked which test design techniques your team actually applies, you got asked to name boundary value analysis and back it with more than "test the edges." Two-value or three-value, someone follows up, and which one did you use, and on what input. This is that answer, worked all the way through on one range with a real bug hiding in it, not a QA lead deciding how much of a suite should target edges versus the middle, and not a team wondering whether their generated tests assert anything real about those edges. Just the vocabulary, backed by arithmetic and a test you can run.
The technique, with the values enumerated
A booking window has to accept exactly 1 to 90 days. Below 1 and above 90 are invalid. That single sentence contains two boundaries: a minimum boundary at 1 and a maximum boundary at 90, and every value in the technique below is chosen relative to one of those two lines, never from anywhere else in the range.
Two-value boundary value analysis (BVA) tests only the boundary itself paired with its nearest invalid neighbor: 0 and 1 for the minimum, 90 and 91 for the maximum. Four inputs, one pair per boundary, no interior value included at all. Three-value boundary value analysis adds one more value moving inward from each boundary, valid side: 2 next to the minimum, 89 next to the maximum. Six inputs total, and every one of them still traces back to a boundary rather than to the middle of the range.
| Input (days) | Position | Two-value pick | Three-value pick | Expected result |
|---|---|---|---|---|
| 0 | Below minimum | Yes | Yes | Invalid |
| 1 | Minimum boundary | Yes | Yes | Valid |
| 2 | Just inside minimum | No | Yes | Valid |
| 89 | Just inside maximum | No | Yes | Valid |
| 90 | Maximum boundary | Yes | Yes | Valid, off-by-one bug here |
| 91 | Above maximum | Yes | Yes | Invalid |
Equivalence partitioning would describe this same range differently: every day count from 1 to 90 belongs to one valid class, and everything outside it belongs to two invalid classes, one below and one above. Boundary value analysis does not pick a representative from that class at random, it picks specifically at the seam between classes, because that is where a class-membership check is most likely to be implemented wrong. The equivalence partitioning piece covers how those classes get derived from a validation function's actual branches rather than guessed from a requirements document.
A boundary bug you can run
Here's the implementation this range gets checked against, one function, written for this booking window rather than lifted from a tutorial:
The spec says 1 to 90 inclusive. The function's minimum check reads that correctly. Its maximum check does not: it compares with < where the spec requires <=, so a 90-day booking, which the spec explicitly allows, gets rejected. That is an invalid boundary silently swallowing a valid one, and it is invisible from anywhere except day 90 itself. Spotting it required reading the comparison operator directly, the same source-level access white box testing is defined by; nothing about this bug shows up from the interface alone.
Here's the parameterised test that walks every input from the table above, plus one interior value at day 45 as a control, seven cases in total, framework-native, not seven copy-pasted assertions. The interior value is exactly the case a boundary-blind test would have picked, and it passes against both the buggy and the fixed implementation, which is the point the closing section later makes about day 45:
Running that parameterised test against the buggy implementation above gives six passes and one failure. The failure is exactly the 90-day case: the function's upper check evaluates 90 < 90, gets False, and rejects a booking the spec requires it to accept. The 0, 1, 2, 45, 89, and 91 cases all pass, because none of them touch the line where the mistake lives. Change that one comparison from < to <= and rerun the same seven cases: all seven pass. Nothing else in the test changed, only the implementation did, which is the point of enumerating boundary inputs explicitly rather than trusting a function to "handle date ranges" in the abstract.
This is the same off-by-one shape as the $500 discount boundary worked through in assertion coverage vs line coverage, just on a range instead of a single threshold: a comparison operator one character away from correct, invisible to any input that isn't the boundary itself. And each case in the table above asserts the specific boolean expected for that input, not merely that the function returned without an exception, which is the distinction how to write good test assertions covers at length.
Robust BVA and when the invalid side matters
The six three-value inputs above, one boundary value, one step inward, and one step outward at both the minimum and the maximum, are what most references call robust or worst-case boundary value analysis once you're deliberately including both directions around every boundary rather than just the line itself. That much most pages that mention BVA at all will tell you.
What they skip is the harder question underneath the invalid boundary: what should actually happen at 0 or 91. "Invalid" is not self-defining. A function can reject with a validation error naming the field, return a bare boolean false, throw an exception, or silently clamp the input to the nearest valid day, and those are four observably different behaviors behind one word. A test that only asserts the function "handled" an invalid boundary without specifying which of those four it expects has not tested the invalid boundary at all, it has tested that the function didn't crash, which is a much weaker claim. Robust BVA earns its name by insisting the invalid side gets the same explicit treatment as the valid side: not just which inputs to include, but which output each one should produce.
This is not a question of how much of a suite's budget should go to boundary cases versus everything else; that's a strategy allocation decision made elsewhere. It's narrower than that: given that you're testing this boundary at all, what exactly does "invalid" mean here, stated plainly enough that whoever writes the test doesn't have to guess.
How Autonoma finds boundaries in your code
That distinction, a boundary the spec states versus a boundary a person has to notice, is the actual difficulty in applying boundary value analysis at scale. The booking window above is one function with two boundaries and a two-character bug. A real codebase has hundreds of comparison operators buried in validation branches across dozens of files, and nothing marks which of them sit at a boundary someone actually cares about versus which are incidental. A person doing this by hand has to read a requirements document, guess which numeric limits it implies, then go find the corresponding comparisons in the code, and the guessing step is where boundaries quietly go untested.
We built our agents to read the codebase before they plan a single test, the same way the worked example above starts from the function rather than from a requirements paragraph. A validation branch with a comparison operator inside it, days < 90 sitting next to a docstring that says "1 to 90 inclusive," is a boundary Autonoma can see directly in the source rather than one a person has to infer from a spec that may never mention the number 90 at all. That is an architectural fact about where the boundary gets found, not a claim about how many of them get caught or how fast. Once a boundary is identified this way, the test cases that exercise it get planned the same way the table above was built by hand: the value at the line, and the values immediately on either side of it.
Why the technique changed jobs
Boundary value analysis was invented for a world where running a test case cost a person's time, so you had to pick a handful of inputs out of an entire range and make every one of them count. That constraint is gone. Running all ninety valid inputs, from day 1 to day 90, costs the same machine-seconds as running one. What survives from the technique is not the selection rule, which stops mattering once selection is nearly free, it's the part of it that states what the correct answer is at each edge, because that statement never became free just because execution did.
That reframing changes what a boundary value analysis exercise is actually for. It stops being a rationing decision, four tests instead of ninety because that's what a sprint allowed, and becomes a documentation exercise: writing down, for each boundary a system exposes, exactly what the correct output is on either side of the line. Once that's written down, whether it gets checked by two inputs, six, or all ninety costs nothing extra to decide.
Here's the sharp version of that gap. Suppose a test generator, human or automated, picks one convenient value from the middle of the range, say day 45, to represent "a valid booking window." Both the buggy implementation above and the fixed one return the same answer for day 45: valid. A test built around that single value passes against either implementation, catches nothing, and reports full confidence. The boundary at day 90 is exactly the place a generator, or a person moving fast, has nothing to go on unless the expected behavior at that specific edge was written down somewhere it could find it. This is the same failure mode AI-generated tests that pass but don't assert anything documents from a different angle: a test that runs and passes is not evidence the thing that matters was ever checked.
None of this changes what the three-word technique means when someone asks you to name it in a review or an interview. It changes what's worth remembering about it: not "pick two or three values," but "state, explicitly, what the answer should be at the line and one step past it," because that statement is the only part a cheap test run can't supply on its own. Autonoma reads the code to find where those lines actually sit and runs the cases against them on every pull request, which turns the finding-the-boundary problem into a solved step rather than a manual guess repeated every time the range changes. It is the E2E layer next to whatever unit tests already cover the function in isolation, not a replacement for either the test design techniques that decide what gets tested or the unit tooling that runs closest to the code.
Frequently Asked Questions
Boundary value analysis is a test design technique that selects test inputs at the edges of a valid range rather than from the middle of it. It exists because defects that involve comparison operators, like using less-than where less-than-or-equal-to was required, only show up when a test input sits exactly on or next to the boundary the operator checks. A test built from values in the interior of the range will pass regardless of whether the boundary check is correct.
Equivalence partitioning groups inputs into classes that are supposed to behave identically and picks one representative value from each class. Boundary value analysis is a refinement of that idea: instead of picking any representative from a class, it picks values specifically at the seams between classes, because that is where a class-membership check is most likely to be implemented incorrectly. The two techniques are usually applied together, with equivalence partitioning defining the classes and boundary value analysis choosing where inside and around them to test.
Three-value boundary value analysis tests three inputs around each boundary: the boundary value itself, one value immediately outside the valid range, and one value immediately inside it. For a range of 1 to 90, that means testing 0, 1, and 2 at the minimum boundary and 89, 90, and 91 at the maximum boundary, six inputs total. This is one more input per boundary than two-value boundary value analysis, which tests only the boundary and its nearest invalid neighbor.
It is important because off-by-one and comparison-operator defects cluster specifically at range boundaries and are effectively invisible to any test built from an interior value. A validation function can pass every test built around convenient, round, or randomly chosen inputs and still reject or accept the wrong value at the exact edge the specification cares about most. Boundary value analysis is the technique that deliberately targets that failure mode instead of leaving it to chance.
The mechanical part, generating the boundary and near-boundary values for a given range, is straightforward to automate once the range itself is known. The harder part is finding the range in the first place: knowing that a particular comparison operator in the code corresponds to a boundary a specification actually cares about. Autonoma's agents read the codebase before planning tests, so a comparison operator inside a validation branch is a boundary the system can see directly in the source rather than one that has to be guessed from a requirements document. That is an architectural difference in where the boundary gets identified, not a claim about test-generation speed or coverage percentages.




