Bug “off-by-one” ao iterar em loops


Introdução

O bug off-by-one acontece quando um loop itera uma posição a mais ou a menos do que deveria, geralmente por causa de condições incorretas de início, fim ou ajuste do índice.

No exemplo a seguir, ele aparece ao combinar incremento controlado e manual no índice.

Código com bug

for (i = 0; i < s.length() - 1; i++) {
  if (currentValue < nextValue) {
    result += nextValue - currentValue;
    i++; // incremento manual
  } else {
    result += currentValue;
  }
}
if (i == s.length() - 1) {
  result += mapping.get(s.charAt(s.length() - 1));
}

Motivo do erro

O for já faz i++ ao final de cada iteração. Ao adicionar i++ dentro do if, o índice avança duas posições quando há subtração. Isso causa:

  • símbolos não processados por estarem pulados
  • último caractere ignorado, pois i não atinge o valor esperado para adicioná-lo

Esse comportamento é conhecido como loop-and-a-half, causado pela tentativa de “escapar” do loop manualmente, quebrando o controle natural do for.

Exemplo

Caso s = "IV"

  1. i = 0, detecta subtração, soma 5 - 1, faz i++ -> i = 1
  2. final do loop, for faz mais i++ -> i = 2, loop encerra
  3. 'V' foi processado pela subtração; bloco que adiciona o último símbolo é ignorado

Resultado esperado.

Caso s = III

  1. i = 0, detecta soma, adicionando “1” ao acumulador, i++ -> i = 1
  2. final do loop, for faz mais i++ -> i = 2, loop encerra
  3. 'I' foi interpretado como soma; porém o bloco que adiciona o último símbolo, caso não tenha sido processado, foi ignorado

Resultado errado.

Solução: loop simples

Substitua por:

int result = 0;
for (int i = 0; i < s.length(); i++) {
  int curr = map.get(s.charAt(i));
  if (i + 1 < s.length() && curr < map.get(s.charAt(i + 1))) {
    result -= curr;
  } else {
    result += curr;
  }
}
return result;
  • i é incrementado apenas pelo loop
  • soma ou subtrai conforme a comparação
  • processa todos os símbolos corretamente.

Alternativa: iteração reversa

Outra abordagem:

int total = 0, prev = 0;
for (int i = s.length() - 1; i >= 0; i--) {
  int curr = map.get(s.charAt(i));
  if (curr < prev) total -= curr;
  else total += curr;
  prev = curr;
}
return total;
  • percorre de trás para frente
  • elimina necessidade de pular índices
  • evita o erro off-by-one

Aprendizados

  • Não modificar manualmente o índice dentro do loop
  • Usar loops com incremento controlado pelo próprio for
  • Testar edge cases simples (tamanho 1, 2, vazio)
  • Usar intervalos semiabertos 0 ≤ i < n
  • Revisar loops no papel para conferir limites (Baeldung)

Referências