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
sandt, returntrueiftis an anagram ofs, otherwise returnfalse.
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:
- Count characters from the first string
- Remove counts using the second string
- 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.