Nathan Clayton

Back to blog

LeetCode Valid Anagram: From Hashmaps to Peak Ruby

A simple LeetCode problem becomes a great example of Ruby’s philosophy: expressing what you want instead of how to do it. In this post, we compare a traditional hashmap implementation of Valid Anagram against Ruby’s incredibly concise chars.tally approach and explore why Ruby solutions can feel almost suspiciously elegant.

Some LeetCode problems are really language problems in disguise.

Valid Anagram is a perfect example.

The core algorithm is simple enough:

Given two strings s and t, return true if t is an anagram of s, otherwise return false.

Examples:

is_anagram("anagram", "nagaram")
# => true

is_anagram("rat", "car")
# => false

The interesting part is how differently Ruby lets you express the solution compared to most languages.


The Classic LeetCode Solution

If you’ve done enough interview prep, this is probably the version your brain immediately reaches for.

def is_anagram(s, t)

  # If lengths differ,
  # they cannot be anagrams
  return false unless s.length == t.length

  # Character frequency map
  counts = Hash.new(0)

  # Count characters from s
  s.each_char do |char|
    counts[char] += 1
  end

  # Subtract characters using t
  t.each_char do |char|

    counts[char] -= 1

    # Too many occurrences in t
    return false if counts[char] < 0
  end

  # Ensure all counts returned to zero
  counts.each_value do |count|
    return false if count != 0
  end

  true
end

This is the classic hashmap frequency-counting approach.

The logic is:

  1. Count characters from the first string
  2. Remove counts using the second string
  3. Ensure everything balances back to zero

This exact algorithm works in basically every major language.

Java.
C++.
Python.
Go.
JavaScript.

Just different syntax wrapped around the same idea.


Why Hash.new(0) Is So Nice

One tiny Ruby detail makes this solution much cleaner:

counts = Hash.new(0)

That default 0 matters a lot.

Without it:

counts["a"] += 1

would fail because missing hash entries are nil.

Instead:

counts["a"]
# => 0

So incrementing becomes effortless.

Ruby makes frequency maps feel extremely natural.


The Peak Ruby Solution

Then Ruby hits you with this:

def is_anagram(s, t)
  s.chars.tally == t.chars.tally
end

That’s the entire solution.

No loops.
No counters.
No bookkeeping.
No manual hashmap logic.

Just:

chars

to split into characters and:

tally

to count frequencies.

Example:

"anagram".chars.tally

# =>
# {
#   "a" => 3,
#   "n" => 1,
#   "g" => 1,
#   "r" => 1,
#   "m" => 1
# }

So the problem effectively becomes:

frequency_map_a == frequency_map_b

Which honestly feels almost unfairly concise.


chars.tally Feels Like Cheating

One thing that stands out when learning Ruby is how often the language already contains the abstraction you were about to write manually.

In many languages, you build the frequency map yourself.

In Ruby:

tally

already exists because counting occurrences is such a common operation.

The code reads almost like plain English:

s.chars.tally == t.chars.tally

“Compare the character tallies.”

That’s very Ruby.


chars vs each_char

There is one subtle implementation difference between these approaches.

This:

s.chars

creates an array.

Whereas:

s.each_char

iterates characters directly without allocating a separate array first.

So technically:

chars.tally

creates a little more intermediate data.

The manual hashmap version is slightly more memory-conscious.

But realistically?

For normal application code and LeetCode-sized inputs, the difference is negligible.

The readability gain is usually worth it.


Time Complexity

Both solutions are effectively:

  • Time Complexity: O(n)
  • Space Complexity: O(n)

because both approaches store character frequencies.

The difference is not performance.

It’s expression.


Ruby Encourages Higher-Level Thinking

One thing I keep noticing while learning Ruby is that the language constantly nudges you toward describing intent instead of implementation details.

The traditional solution says:

“Create a hashmap, loop over characters, increment counts, decrement counts, validate totals.”

The Ruby solution says:

“Compare the character frequencies.”

Same algorithm.
Completely different level of abstraction.

That’s the kind of thing that makes Ruby feel refreshing after spending years in more verbose ecosystems.


Which Solution Should You Use?

For interviews?

The explicit hashmap solution is probably still the safer answer because it demonstrates the underlying algorithm clearly.

For actual Ruby code?

s.chars.tally == t.chars.tally

is hard to beat.

Tiny.
Readable.
Idiomatic.

Peak Ruby.

Share this post

Spread the word on your favorite platform.

Are you sure?