Skip to content

fix(Repeat): lastIndexOf returned size instead of size - 1#2227

Merged
jdeniau merged 1 commit into
immutable-js:mainfrom
chatman-media:fix/repeat-last-index-of-off-by-one
Jun 22, 2026
Merged

fix(Repeat): lastIndexOf returned size instead of size - 1#2227
jdeniau merged 1 commit into
immutable-js:mainfrom
chatman-media:fix/repeat-last-index-of-off-by-one

Conversation

@chatman-media

Copy link
Copy Markdown
Contributor

Bug

Repeat#lastIndexOf returns this.size when the searched value matches, but the last valid index of a Repeat of n elements is n - 1.

const { Repeat, List, Seq } = require('immutable');

Repeat('x', 3).lastIndexOf('x');        // => 3  (should be 2)
List(['x','x','x']).lastIndexOf('x');   // => 2  ✅
Seq.Indexed(['x','x','x']).lastIndexOf('x'); // => 2  ✅
['x','x','x'].lastIndexOf('x');         // => 2  ✅ (native)

Every other indexed collection (List, Range, generic Seq) and the native Array return size - 1; only Repeat was off by one.

While fixing this I noticed a related issue on empty repeats — both indexOf and lastIndexOf reported the value as present even though the collection has no elements:

Repeat('x', 0).indexOf('x');      // => 0  (should be -1)
Repeat('x', 0).lastIndexOf('x');  // => 0  (should be -1)

lastIndexOf('missing') is documented to return -1 when the value isn't found, and an empty collection contains nothing, so both should return -1.

Fix

In src/Repeat.js:

  • lastIndexOf returns this.size - 1 (instead of this.size) on a match.
  • both indexOf and lastIndexOf guard on this.size !== 0 so an empty Repeat returns -1.

The infinite case is unchanged: Repeat(value) has size === Infinity, so size - 1 is still Infinity.

Tests

Added regression tests to __tests__/Repeat.ts covering the first/last index of a non-empty repeat and the empty-repeat case. They fail on main (lastIndexOf returns 3 instead of 2; empty repeat returns 0 instead of -1) and pass with the fix. Full suite (798 tests) green, eslint + prettier clean.

Repeat#lastIndexOf returned this.size when the searched value matched,
but the last valid index of a Repeat of n elements is n - 1. For
example Repeat('x', 3).lastIndexOf('x') returned 3 instead of 2.

An empty Repeat (Repeat(value, 0)) also incorrectly reported the value
as present: both indexOf and lastIndexOf returned 0 even though the
collection has no elements. Both now return -1 when size is 0, matching
the documented contract and the behaviour of List/Range/Seq.

@jdeniau jdeniau left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

@jdeniau jdeniau merged commit 6a1860f into immutable-js:main Jun 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants