CodeDifficulty: Intermediate3 min

The Opposite-Ends Template in Python, Java and TypeScript

Two indices, one comparison, and a move chosen by whether the sum is too small or too large.

The opposite-ends template finds a pair in a sorted array with two indices that start at the ends and move towards each other. The loop runs while left < right, so a value is never paired with itself, and it stops when the pointers meet with no pair found. Two parts change between problems, and the highlighted lines mark them: the test on the current pair, and which pointer each outcome moves. Each listing runs on 2 5 7 8 12 15 for the target 19, on a pair of equal values, on an array with no pair and on an empty array.

The opposite-ends template in three languages

pair_in_sorted.pypy
def pair_in_sorted(values: list[int], target: int) -> tuple[int, int] | None:
    left, right = 0, len(values) - 1
    while left < right:
        total = values[left] + values[right]
        if total == target:
            return left, right
        if total < target:
            left += 1
        else:
            right -= 1
    return None


for values, target in [([2, 5, 7, 8, 12, 15], 19), ([3, 3], 6), ([1, 2, 4], 10), ([], 4)]:
    pair = pair_in_sorted(values, target)
    answer = "none" if pair is None else f"indices {pair[0]} {pair[1]}"
    print(f"{values} target {target}: {answer}")
OutputRan with python:3.14.8-slim-trixie (Python 3.14.8)
[2, 5, 7, 8, 12, 15] target 19: indices 2 4
[3, 3] target 6: indices 0 1
[1, 2, 4] target 10: none
[] target 4: none
PairInSorted.javajava
import java.util.Arrays;

public class PairInSorted {
    static int[] pairInSorted(int[] values, int target) {
        int left = 0;
        int right = values.length - 1;
        while (left < right) {
            int total = values[left] + values[right];
            if (total == target) {
                return new int[] {left, right};
            }
            if (total < target) {
                left++;
            } else {
                right--;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        int[][] inputs = {{2, 5, 7, 8, 12, 15}, {3, 3}, {1, 2, 4}, {}};
        int[] targets = {19, 6, 10, 4};
        for (int k = 0; k < inputs.length; k++) {
            int[] pair = pairInSorted(inputs[k], targets[k]);
            String answer = pair == null ? "none" : "indices " + pair[0] + " " + pair[1];
            System.out.println(Arrays.toString(inputs[k]) + " target " + targets[k] + ": " + answer);
        }
    }
}
OutputRan with eclipse-temurin:25.0.4.1_1-jdk-noble (Java 25.0.4.1)
[2, 5, 7, 8, 12, 15] target 19: indices 2 4
[3, 3] target 6: indices 0 1
[1, 2, 4] target 10: none
[] target 4: none
pair_in_sorted.tsts
function pairInSorted(values: number[], target: number): [number, number] | null {
  let left = 0;
  let right = values.length - 1;
  while (left < right) {
    const total = values[left] + values[right];
    if (total === target) {
      return [left, right];
    }
    if (total < target) {
      left++;
    } else {
      right--;
    }
  }
  return null;
}

const inputs: [number[], number][] = [
  [[2, 5, 7, 8, 12, 15], 19],
  [[3, 3], 6],
  [[1, 2, 4], 10],
  [[], 4],
];
for (const [values, target] of inputs) {
  const pair = pairInSorted(values, target);
  const answer = pair === null ? "none" : `indices ${pair[0]} ${pair[1]}`;
  console.log(`[${values.join(", ")}] target ${target}: ${answer}`);
}
OutputRan with node:24.22.0-trixie-slim (Node 24.22.0), TypeScript 5.9.3
[2, 5, 7, 8, 12, 15] target 19: indices 2 4
[3, 3] target 6: indices 0 1
[1, 2, 4] target 10: none
[] target 4: none

The three listings print the same answers. An empty array sets right to −1, so the loop never starts and the answer is none without a special case. The pass is O(n) time and O(1) extra space for n values, and it assumes the array is already sorted: on unsorted input it can walk past a pair that exists. A problem that asks for values rather than indices may sort first, at O(n log n), and TypeScript's sort then needs the comparator (a, b) => a - b, because its default compares numbers as strings.