Lập trình · 23/10/2026

JavaScript substring vs slice: Negative and Reversed Indices Differ

Replacing slice with substring can change which part of a string is extracted. They often agree for ordered positive indices. Negative indices and reversed boundaries reveal their different contracts.

JavaScript substring và slice: chỉ mục âm và đảo thứ tự cho kết quả khác nhau

Replacing slice with substring can change which part of a string is extracted. They often agree for ordered positive indices. Negative indices and reversed boundaries reveal their different contracts.

1. Negative indices have different meanings

const text = 'ABCDE';
console.log(text.slice(-2)); // DE
console.log(text.substring(-2)); // ABCDE
console.log(text.slice(1, -1)); // BCD
console.log(text.substring(1, -1)); // A

slice can interpret negative indices relative to the end. substring treats negatives as zero before applying its boundary rules. See MDN String.slice and MDN substring.

The last call does not mean “up to the penultimate character”. A method-name replacement in a parser can change the extracted value without throwing. Review the range of both indices rather than only the input text.

2. Reversed boundaries may be silently reordered

const text = 'ABCDE';
console.log(JSON.stringify(text.slice(4, 1))); // ""
console.log(JSON.stringify(text.substring(4, 1))); // "BCD"
console.log(text.substring(1, 4)); // BCD

substring swaps the boundaries when necessary, while slice returns an empty string here. Incorrect start and end calculations can therefore produce plausible-looking text instead of an obvious failure.

A nonempty result does not validate the range. Decide explicitly whether reversed selection boundaries should be accepted or reported as an error.

3. Define a half-open range contract

function cutRange(text, start, end) {
  if (typeof text !== 'string') throw new TypeError('text must be a string');
  if (!Number.isInteger(start) || !Number.isInteger(end)) {
    throw new TypeError('indices must be integers');
  }
  if (start < 0 || end < start || end > text.length) {
    throw new RangeError('invalid range');
  }
  return text.slice(start, end);
}
console.log(cutRange('ABCDE', 1, 4)); // BCD
console.log(JSON.stringify(cutRange('ABCDE', 2, 2))); // ""

This helper requires 0 ≤ start ≤ end ≤ text.length, including start and excluding end. Equal boundaries form a valid empty range. Negative, fractional, string and reversed indices are rejected rather than interpreted implicitly.

This is an application policy, not a requirement for every string utility. If reverse selections are supported, normalize them under that policy before invoking the helper.

4. Establish what an index counts

The ASCII examples use one code unit per letter. General JavaScript string indices count UTF-16 code units rather than user-perceived characters. This helper validates numeric ranges but cannot guarantee that a cut preserves emoji or combining sequences.

Use appropriate segmentation when a feature asks for N displayed characters. For positions supplied by an API, establish whether it counts bytes, code points or UTF-16 units; matching numbers do not establish matching text boundaries.

5. Test boundaries before changing methods

  • Normal positive ranges.
  • Negative and reversed indices.
  • Equal bounds, empty text and the full string.
  • Numeric strings, fractions, NaN and out-of-range indices.
  • Non-ASCII text when display-character boundaries matter.

All six bilingual blocks ran with Node.js, alongside helper valid-range and invalid-input checks. Real parsers and grapheme segmentation were not integrated.

Discussion

Comments 0

Sign in to comment

You need an account to join the discussion and reply to other readers.

Sign inRegister

No comments yet. Be the first to share your thoughts.