Rails pluralize Just Got 4x Faster

· 4 min read

Rails’ pluralize helper just received a significant performance boost that makes it up to 4 times faster for uncountable words. The optimization, merged in PR #55485, tackles inefficiencies in the ActiveSupport Inflector through regex caching and structural improvements.

If you’re not familiar with pluralize, it’s the helper that converts singular words to their plural forms:

1
2
3
4
5
6
7
8
"post".pluralize        # => "posts"
"person".pluralize      # => "people"
"sheep".pluralize       # => "sheep" (uncountable)

# With counts
pluralize(1, "post")    # => "1 post"
pluralize(2, "post")    # => "2 posts"
pluralize(5, "person")  # => "5 people"

The Performance Problem

Rails’ pluralization system was performing redundant operations for every pluralization check. While the regex patterns were compiled once, the system still had to iterate through and check each pattern individually. The Uncountables class also inherited from Array, creating unnecessary overhead.

Before the optimization, checking if a word was uncountable meant:

  • Iterating through each uncountable word pattern
  • Performing multiple individual regex matches
  • Maintaining complex array inheritance structure

Rails Optimization Approach

The optimization introduces three key improvements:

1. Regex Caching with Regexp.union()

Before:

1
2
3
4
5
6
7
8
class Uncountables < Array
  def uncountable?(str)
    each do |word|
      return true if /#{Regexp.escape(word)}/i.match?(str)
    end
    false
  end
end

After:

1
2
3
4
5
6
class Uncountables
  def uncountable?(str)
    @pattern ||= Regexp.union(@members.map { |w| /#{Regexp.escape(w)}/i })
    @pattern.match?(str)
  end
end

The new approach creates a single, cached regex pattern using Regexp.union() instead of compiling individual patterns for each check.

2. Composition Over Inheritance

The Uncountables class no longer inherits from Array. Instead, it uses composition with an internal @members array and delegates necessary methods:

1
2
3
4
5
6
7
8
class Uncountables
  extend Forwardable
  def_delegators :@members, :<<, :concat, :each, :clear, :to_a

  def initialize
    @members = []
  end
end

3. English Inflection Fast Path

A dedicated cache for English inflections reduces instance lookups:

1
2
3
4
5
6
7
8
9
def self.instance(locale = :en)
  @__instances__ ||= {}
  @__instances__[locale] ||=
    if locale == :en
      @__en_instance__ ||= new
    else
      new
    end
end

Performance Results

The optimization delivers substantial improvements across all pluralization types:

1
2
3
4
5
# Benchmark results (iterations/second)
                  Before      After      Improvement
regular         252.175k   298.382k      18% faster
irregular       851.502k   1.943M        128% faster
uncountable     1.487M     6.008M        304% faster (4x)

Uncountable words see the most dramatic improvement, going from 1.487M to 6.008M iterations per second. That’s a 4x performance increase.

When This Optimization Matters

This optimization benefits applications that:

  • Frequently pluralize words in views and helpers
  • Process large datasets with text inflection
  • Use Rails’ built-in pluralization in tight loops
  • Handle user-generated content requiring pluralization

Common scenarios include:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Dashboard counters
pluralize(@users.count, 'user')
pluralize(@orders.count, 'order')

# Data processing
products.each do |product|
  puts "#{product.quantity} #{product.name.pluralize(product.quantity)}"
end

# API responses
render json: {
  message: "Found #{results.count} #{model_name.pluralize(results.count)}"
}

Technical Deep Dive

The optimization works by combining multiple regex checks into one. Previously, each uncountable? check would iterate through all patterns:

1
2
3
# Before: Check each regex individually
uncountable_patterns.any? { |pattern| pattern.match?('equipment') }
# This means potentially checking many patterns one by one

Now, a single unified regex handles all uncountable words:

1
2
3
# After: One regex for all uncountable words
pattern = Regexp.union(uncountable_patterns)
pattern.match?('equipment') # Single check against combined pattern

The Regexp.union() method creates an optimized alternation pattern that the Ruby regex engine can process efficiently.

Memory and CPU Benefits

Beyond raw speed improvements, the changes reduce:

  • CPU overhead: Single regex match vs. multiple iterations
  • Performance overhead: No need to loop through patterns
  • Structural complexity: Removed unnecessary Array inheritance

Rails applications with heavy text processing will see the most benefit from these efficiency gains.

Conclusion

Rails’ pluralize optimization demonstrates how targeted performance improvements can deliver significant gains. By caching regex patterns and simplifying class structure, the change achieves 4x faster performance for uncountable words while improving overall inflection efficiency.

You might wonder why I’m covering an internal Rails optimization that developers don’t need to act on. Truth is, these kinds of improvements fascinate me. A 4x performance boost from clever regex caching and removing unnecessary inheritance? It’s elegant problem-solving at its finest. While we won’t change our code because of this, understanding how Rails evolves under the hood makes us better developers. Plus, seeing the framework we use daily getting faster without any work on our part? That’s just satisfying.

Prateek Choudhary
Prateek Choudhary
Technology Leader