Recommended resources
The freely available resources this lesson will direct you to.
- Boundary Value Analysis and Equivalence Partitioning: Software Testing Tutorialwww.youtube.com
- Test Case Design Techniques Fully Explained | Software Testing | SoftwaretestingbyMKTwww.youtube.com
- ISTQB Certified Tester - Foundation Level Syllabus v4.0www.gasq.org
- A Repeatable Workflow for Solving Coding Interview Problemspaths.grasp.study
Hello! Last lesson, you turned an ambiguous two-sum prompt into an agreed contract: return two distinct, zero-based indices in increasing order, or (-1, -1) if no pair exists. The input may contain duplicates and negative numbers, and your function must not modify it.
Now you can use that contract to design tests before choosing an algorithm. In this lesson, you’ll build minimal, representative, and boundary cases, with a clear expected result for each. The aim is a compact set of tests that exposes likely mistakes—not a long list of arbitrary arrays.
Test behavior, not just input values
A useful test case has three parts: an input, an expected result, and a reason it belongs in the set. For example, [4, 4] with target 8 should return (0, 1). Its purpose is to check that equal values at different positions may form a pair.
One way to choose cases systematically is equivalence partitioning: group inputs that you expect a program to handle in the same way, then choose a representative from each group. Watch Guru99’s short explanation of that idea and its companion technique, boundary value analysis.
Boundary Value Analysis and Equivalence Partitioning: Software Testing Tutorial
Guru99’s Boundary Value Analysis and Equivalence Partitioning: Software Testing Tutorial introduces two ways to select informative tests without trying every input.
Watch partitions for the idea of choosing a representative from each behavior group. Continue through boundary cases to see why values at the edges of those groups deserve separate tests. Focus on the selection principles; the ticket-booking rules are only an illustration.
For two-sum, “a pair exists” and “no pair exists” are two important behavior groups. But that division alone is too coarse. Within “a pair exists,” equal values at distinct indices, a pair involving negative numbers, and a pair at the end of the array can expose different mistakes. These are useful representatives of distinct risks, even though the successful output follows the same rule.
A representative test does not prove every input in its group works. It gives you a deliberate sample. When you can name the mistake a case might catch, you have a stronger reason to include it.
Start with the smallest valid inputs
The agreed contract permits a vector with zero elements. That makes [] the smallest valid input, not an invalid-input test. With any target, it must return (-1, -1): there are no indices to return. A one-element vector also cannot contain two distinct positions. At two elements, the result can first change from “no pair” to “pair found.”
This gives you a particularly revealing cluster:
nums |
target |
Expected result | What it checks |
|---|---|---|---|
[] |
8 |
(-1, -1) |
Handles an empty vector. |
[4] |
8 |
(-1, -1) |
Does not use index 0 twice. |
[4, 4] |
8 |
(0, 1) |
Accepts equal values at distinct indices. |
[4, 5] |
8 |
(-1, -1) |
Does not claim every two-element input has a solution. |
Notice the distinction between minimal input and minimal successful input. Here, the former has zero elements; the latter has two. In another problem, the specification might require at least one or two elements, so you would begin at that stated minimum instead. Never assume that an empty input needs a particular response unless the contract defines one.
The A Repeatable Workflow for Solving Coding Interview Problems reading gives a second small set of two-sum examples. Read its edge-case table for how each example targets an assumption, rather than copying its cases without checking your own contract.
A Repeatable Workflow for Solving Coding Interview Problems
This Grasp reading shows how small two-sum examples can test distinct risks before coding.
In “2. Model: use examples, constraints, and a baseline,” go to “Layer 2: edge examples.” Read the edge examples, from the sentence introducing the table through the explanation of equal values at different indices. Focus on the “What it checks” column. The reading assumes exactly one valid pair, whereas our agreed contract also defines a no-solution result and permits multiple valid pairs; keep those differences in mind.
Put tests on both sides of a boundary
A boundary is where the expected treatment of an input changes. For a rule accepting integer values from 1 through 255, the lower edge is tested with 0 and 1, and the upper edge with 255 and 256. An interior value can establish ordinary behavior, but it is less likely to catch a rule implemented with the wrong endpoint.

In our coding problem, the important boundaries are not only numeric ranges:
- Array length: sizes
0,1, and2straddle the point where a distinct-index pair becomes possible. - Index position: a solution using the first or last position checks that an implementation does not accidentally skip an endpoint.
- Allowed value magnitude: values near the stated limit test numeric handling, even when the array is short.
- Maximum array length: an input of 100,000 elements is valid under the agreed contract. An input of 100,001 is outside it; unless invalid-input handling is specified, do not invent an expected result for that case.
For instance, [8, 1, 4, 6] with target 10 has the pair (2, 3), which includes the last index. A loop that stops one position early could miss it. [2, 7, 11, 15] with target 9 expects (0, 1), exercising the beginning.
The value limit presents a different risk. For [2000000000, 2000000000] with target -294967296, the correct result is (-1, -1): the two values sum mathematically to four billion, not to the negative target. Adding those values in a 32-bit C++ int causes signed overflow, which is undefined behavior; a test may expose the problem, but you must not assume overflow will wrap in a particular way. The lesson is to choose extreme values that target a specific numeric mistake, and to reason about the expected answer using arithmetic that can represent the sum.
Assemble a compact, purposeful suite
The minimal cases establish whether the function handles the smallest vectors. Add representatives for distinct behaviors the contract permits:
nums |
target |
Expected result | Why include it? |
|---|---|---|---|
[2, 7, 11, 15] |
9 |
(0, 1) |
Ordinary successful case; pair at the start. |
[8, 1, 4, 6] |
10 |
(2, 3) |
Pair includes the last index. |
[-4, 10, 6] |
2 |
(0, 2) |
A negative value participates in the pair. |
[3, 9, 5] |
6 |
(-1, -1) |
A lone 3 cannot be paired with itself. |
[0, 0, 5] |
0 |
(0, 1) |
Two separate zeros make a valid pair. |
[1, 8, 2, 7] |
9 |
(0, 1) or (2, 3) |
Either valid pair is acceptable. |
The last row illustrates an important testing rule: do not make the test stricter than the contract. An implementation returning (2, 3) passes that case, even if you initially noticed (0, 1) first. Conversely, (1, 0) would not satisfy our agreed output order, and (0, 2) would not sum to the target.
For a large-input boundary, you could use a vector containing 99,998 zeros followed by 4 and 5, with target 9. It has 100,000 elements and one valid pair, (99998, 99999). The many zeros do not create another pair for that target. This case checks both the stated maximum length and a solution at the final two positions. You need not write out all 100,000 elements in interview notes; describe how the input is constructed and state its expected result.
A good suite is not necessarily one test per conceivable feature. The same case can cover more than one concern, such as [4, 4] covering both the smallest successful length and duplicate values. Keep a case when it tests a distinct contract rule, boundary, or likely failure; remove it if it merely repeats another case with different numbers.
Know what counts as passing
For a successful result (i, j), check all parts of the output contract: , the values at those positions sum to the target, and the input vector is unchanged. Evaluate the sum in a sufficiently wide type when checking extreme values. For (-1, -1), verify that no pair of distinct positions satisfies the target; finding that one attempted pair fails is not enough.
This is also why expected results should be worked out before running an implementation. If the function returns a plausible-looking pair, you still need an independent reason to accept it. For small cases, inspect the possible pairs directly. For a deliberately constructed large case, use the construction itself to justify the expected answer.
The categories now work together: minimal cases check whether a valid solution is even possible, representative cases sample meaningfully different behavior, and boundary cases probe the points where rules or indexing change. All three come from the agreed contract, not from guesses about an algorithm’s internals.
Takeaways
Construct each test with an expected result and a purpose. Begin at the smallest allowed input, test where behavior first changes, sample duplicates, negatives, zeros, missing and multiple solutions, and include endpoint and numeric-limit cases when the contract calls for them. Accept every output the contract allows—no more and no less.
Next, you’ll derive a straightforward brute-force solution and identify its performance bottleneck. The cases you built here will give you a way to check that baseline before considering a faster approach.