rubyguides

String#next

next -> new_string

#next (also known as #succ) returns the next string in ASCII sequence. It increments the last character of the string and wraps around — similar to how a counter works. It’s useful for generating sequential identifiers, ranges of labels, or stepping through alphabetical sequences.

How successor generation works

The algorithm increments the rightmost character that isn’t at its maximum. If a character wraps past its maximum, it resets to the minimum and carries to the next character leftwards.

"a".next        # => "b"
"z".next        # => "aa"
"az".next       # => "ba"
"ZZ".next       # => "AAA"
"199".next      # => "200"

The key rule: characters increment within their own range. Lowercase letters go a→z→aa, uppercase go A→Z→AA, digits go 0→9→00.

Each character class wraps independently, and when the rightmost character reaches its maximum, it resets to the minimum and triggers a carry to the next character to the left. If every character is already at its maximum — for instance, "zz" or "99" — the string grows by one position and the new leftmost character starts at the class minimum. This carry-and-grow behavior is what makes next suitable for generating sequential identifiers without pre-allocating a fixed width.

Basic usage

"hello".next    # => "hellp"
"world".next    # => "worle"

"abc".next      # => "abd"
"abz".next      # => "aca"

"file1".next    # => "file2"
"file9".next    # => "filf"

Each call returns a new string — the original is unchanged.

The examples above demonstrate single invocations of next, each producing the immediate successor of the input. Because the method always returns a new string rather than mutating the receiver, you can safely call it inside loops, map blocks, or enumerator chains without worrying about side effects on the original collection. This purity property is what makes next suitable for functional-style pipelines where intermediate strings should not be altered.

Multiple successor calls

Calling next repeatedly steps through a sequence:

s = "a"
5.times { puts s = s.next }
# a, b, c, d, e

For alphabetic sequences with a fixed width (padded with leading characters):

"a".next.next   # => "c"
"a9".next       # => "b0"
"z9".next       # => "aa0"

Chaining .next.next or calling it inside a times loop steps through the sequence one position at a time. When alphanumeric characters mix — like "a9" — the digit advances first, and when it wraps to 0, the letter increments as a carry. This interleaved behavior follows the same rules as a positional numeral system where each character position has its own base determined by its character class.

Interaction with digits and letters

The successor of "9" is "10" — a new character is added:

"9".next        # => "10"
"99".next       # => "100"

Mixed alphanumeric strings follow the carry rules described above: the rightmost character that can increment does so, and characters to its left change only when a carry propagates. The sequence "a9""b0" shows a single carry (9 wraps to 0, letter a advances to b), while "z9""aa0" shows a double carry (z wraps to a and a new a is prepended, then 9 wraps to 0).

"a1".next       # => "a2"
"a9".next       # => "b0"
"z1".next       # => "z2"
"z9".next       # => "aa0"

The alphanumeric carry behaviour shown above follows the same rules as incrementing a base-36 counter, where digits 0-9 roll over before letters a-z. Understanding this carry logic is key to predicting next output for any mixed string, including edge cases like all-z inputs where the string grows by one character.

With padding

To maintain a fixed width (useful for sorted identifiers):

def padded_successor(str, width)
  str.next.rjust(width, str[0])
end

padded_successor("a", 3)    # => "b"
padded_successor("z", 3)    # => "aa"  (not padded)

The padded_successor helper above attempts to maintain a fixed width by right-justifying the successor with the original string’s first character. However, this approach breaks when the successor string grows longer than the original — for example, "z".next is "aa", which already has width 2 and does not need padding. The next_padded variant below handles this case more reliably by always applying rjust after the successor is computed.

For consistent-width sequences, use rjust after the successor:

def next_padded(str, width)
  str.next.rjust(width, '0')
end

next_padded("9", 2)    # => "10"
next_padded("99", 3)   # => "100"

The next_padded function ensures that the result always meets the minimum width, which is important when the consumer of the identifier expects a fixed-width format (e.g., invoice numbers like "001", "002", …). The padding character should match the expected leading character — '0' for numeric sequences, 'a' for alphabetic ones — to keep the identifiers sortable as strings.

Generating ranges

Generate a range of sequential strings with #next:

first = "alpha"
last  = "alphd"
current = first
while current <= last
  puts current
  current = current.next
end
# alpha, alphb, alphc, alphd

The while loop above uses Ruby’s string comparison to walk from a start label to an end label. Because next produces strings that sort correctly in the same character class, the <= comparison works as expected — "alphd" is indeed greater than "alphc". This pattern is useful for generating sequential labels where you know the endpoint in advance but don’t want to hardcode every intermediate value.

For cases where character ranges matter:

# Generate sequential codes
def generate_codes(start, count)
  code = start
  count.times.each_with_object([]) do |_, codes|
    codes << code
    code = code.next
  end
end

generate_codes("A01", 5)
# => ["A01", "A02", "A03", "A04", "A05"]

The generate_codes helper collects successive identifiers into an array using each_with_object. Because next creates a new string each time, the array ends up with independent string objects rather than references to the same mutable buffer. This is important if you plan to modify the generated codes later — each entry is a separate allocation.

Performance

Successor generation is fast — O(n) in the string length. For very long strings, each call touches every character from the rightmost non-maximum position to the end. Most practical uses involve short strings (3–10 characters), so the linear scan is rarely a bottleneck. Generating millions of sequential codes may benefit from caching or pre-computation, but for typical identifier generation workloads the performance is more than adequate.

succ — the alias

#succ is an exact alias for #next. Both methods do the same thing:

"abc".next == "abc".succ  # => true

Use whichever reads more naturally in context.

Comparison with other methods

  • #next vs #chr: #chr converts an integer to a single character; #next steps to the next string in sequence.
  • #next vs #succ vs #advance (from Date): #next on strings is for character sequences; Date#advance moves a calendar date forward.

While String#next and Date#next share the same method name, they operate in completely different domains. String#next advances through character sequences using ASCII/Unicode rules, while Date#next moves to the next calendar day. Don’t confuse them — a string that looks like a date (e.g., "2026-01-31".next) will not produce the next calendar date but rather "2026-01-32", which is not a valid date at all.

Edge cases

Empty string:

"".next  # => "aa"

An empty string has no characters to increment, so next treats it as if every position is at its minimum and appends two 'a' characters. This is a special case worth handling explicitly in code that may receive empty input — the result "aa" is rarely the desired behavior for an empty identifier field.

Only maximum characters:

"Z".next  # => "AA"
"9".next  # => "10"

When every character in the string is at its class maximum — "Z" for uppercase, "z" for lowercase, "9" for digits — the string grows by one position and the new character starts at the class minimum. For "Z", the result is "AA" (uppercase carry); for "9", the result is "10" (digit carry with a new leading 1). This growth behavior is consistent across all character classes.

Binary-like strings with consistent width:

"000".next  # => "001"
"999".next  # => "0000"

See Also