Common patterns: iteration, hashing, two-pointer, basic transformations. Try each yourself first – ask for solutions/hints per question when ready.
- Reverse a string without using
.reversereverse_string("hello") => "olleh" - Palindrome check
palindrome?("racecar") => truepalindrome?("hello") => false - Count vowels
count_vowels("programming") => 3 - Find max in array without
.maxfind_max([3, 7, 2, 9, 4]) => 9 - Remove duplicates from array, preserve order
remove_duplicates([1,2,2,3,1,4]) => [1,2,3,4] - FizzBuzz (1 to n)
fizzbuzz(15) => ["1","2","Fizz","4","Buzz",...,"FizzBuzz"] - Anagram check
anagram?("listen", "silent") => true - Sum of array, no
.sumarray_sum([1,2,3,4]) => 10 - Capitalize each word (title case), no
.capitalizeon whole stringtitle_case("the ruby language") => "The Ruby Language" - Find second largest number
second_largest([4, 1, 9, 7, 9]) => 7 - Find Alice and Bob spending amounts
details = [{ amount: 2500, requestor: 'Alice', id: 23 }... - Pattern Check
pattern_check("{()}") # => true
1. Reverse a string without using .reverse
Concept
1. finding the last string character index to find the last string character first
2. then decreasing the index to find the upto the first character
def reverse_string(str)
last_str_index = str.length - 1
result = ""
while last_str_index >= 0
result << str[last_str_index]
last_str_index -= 1
end
result
end
puts reverse_string("hello")
puts reverse_string("programming")
Solution 2: Another way without using Index variables
def reverse_string(str)
str.each_char.reduce("") { |result, char| char + result }
end
Concept
Prepend each character to an accumulator instead of appending – that flips the order without touching any index. each_char + reduce replaces the while-loop/counter entirely.
2. Palindrome check
def palindrome?(str)
first_char_index = 0
last_char_index = str.length - 1
while first_char_index < last_char_index
if str[first_char_index] != str[last_char_index]
return false
end
first_char_index += 1
last_char_index -= 1
end
return true
end
p palindrome?("ala")
p palindrome?("alla")
p palindrome?("racecar")
p palindrome?("car")
Concept
- Two pointer approach
Two indices start at opposite ends of the string and move toward each other, comparing elements pairwise:
first_char_indexstarts at0,last_char_indexstarts atlength - 1- Each iteration compares
str[first]vsstr[last]– if they ever mismatch, it can’t be a palindrome, so return immediately - Otherwise, both pointers move inward (
first += 1,last -= 1) until they meet or cross (first < lastbecomes false) - If the loop finishes without a mismatch, all mirrored pairs matched → palindrome
Why this pattern in general: it’s the go-to when you need to compare elements from both ends of a sequence without extra space – palindromes, reversing in-place, “sorted array pair sum” problems, container/water-trapping problems all reuse this exact skeleton (two indices, converge or diverge, one comparison per step).
One edge case worth saying out loud in an interview: this correctly handles even-length (abba) and odd-length (aba, middle char never gets compared to itself) without any special-casing – that’s often a follow-up question.
3. Count vowels
1. Without Using Array#each or any other Enumerable methods
def count_vowels(str)
vowels = ['a', 'e', 'i', 'o', 'u']
vowel_count = 0
first = 0
while first <= str.length - 1
if vowels.include?(str[first])
vowel_count += 1
end
first += 1
end
vowel_count
end
p count_vowels("programming")
p count_vowels("ala")
p count_vowels("Grow your audience by promoting your content")
Concept
- Using a pointer
2. Using Enumerable#count
# using `Enumerable#count`
def count_vowels(str)
vowels = ['a', 'e', 'i', 'o', 'u']
str.each_char.count { |char| vowels.include?(char.downcase) }
end
p count_vowels("programming")
p count_vowels("ala")
p count_vowels("Grow your audience by promoting your content A")
Enumerable#count
The Enumerable#count method in Ruby returns the number of elements in a collection, optionally filtering them based on an item or a truthy block criterion.
[10, 20, 30].count
# => 3
{ a: 1, b: 2 }.count
# => 2
[1, 2, 4, 2, 1, 2].count(2)
# => 3
["apple", "banana", "apple"].count("apple")
# => 2
# Count numbers greater than 10
[5, 12, 8, 18, 3].count { |num| num > 10 }
# => 2
# Count odd numbers using symbol-to-proc syntax
[1, 2, 3, 4, 5].count(&:odd?)
# => 3
# With a Hash, it yields both the key and the value
{ candy: 5, apples: 2, cookies: 10 }.count { |key, value| value > 4 }
# => 2
3. Using Enumerable#select
# using `String#each_char` and `Enumerable#select`
def count_vowels(str)
vowels = ['a', 'e', 'i', 'o', 'u']
str.each_char.select { |char| vowels.include?(char.downcase) }.size
end
p count_vowels("programming")
p count_vowels("ala")
p count_vowels("Grow your audience by promoting your content A")
In Ruby, Enumerable#select (Aka filter, find_all) is an inbuilt method used to filter a collection by evaluating each element against a given block and returning only the items for which the block evaluates to true.
collection.select { |element| condition }
With a block: Returns a new collection containing all elements that match the condition.
Without a block: Returns an Enumerator object.
Aliases: filter and find_all are exact aliases and perform identically
reject –> The opposite of select; returns all elements that evaluate to false.
find / detect –> Returns only the first element that matches the condition, then stops iterating.
## Array
numbers = [1, 2, 3, 4, 5, 6]
# Using block syntax to get even numbers
even_numbers = numbers.select { |num| num.even? }
# => [2, 4, 6]
# Short-hand symbol-to-proc syntax
even_numbers = numbers.select(&:even?)
# => [2, 4, 6]
## Hash
scores = { alice: 95, bob: 65, charlie: 82 }
# Filter for scores greater than 70
passing = scores.select { |name, score| score > 70 }
# => {:alice=>95, :charlie=>82}
users = [
{ name: "Alice", active: true },
{ name: "Bob", active: false },
{ name: "Charlie", active: true }
]
active_users = users.select { |user| user[:active] }
# => [{:name=>"Alice", :active=>true}, {:name=>"Charlie", :active=>true}]
4. Using Array#each
In Ruby, each is not actually a method defined by the Enumerable module itself; instead, it is a method requirement that your class must implement.
The Enumerable module acts as a mixin that provides collection-handling capabilities (like .map, .select and .reduce). However, for those methods to function, your custom collection class must define its own #each method to yield items sequentially.
# using `String#split` and `Array#each`
def count_vowels(str)
vowels = ['a', 'e', 'i', 'o', 'u']
vowel_count = 0
str.split('').each do |char|
vowel_count += 1 if vowels.include?(char)
end
vowel_count
end
p count_vowels("programming")
p count_vowels("ala")
p count_vowels("Grow your audience by promoting your content")
Best: Solution 2 (each_char.count)
def count_vowels(str) vowels = ['a', 'e', 'i', 'o', 'u'] str.each_char.count { |char| vowels.include?(char.downcase) }end
count with a block is built exactly for “how many elements satisfy this predicate” – it says what you want, not how to accumulate it. One line of actual logic, no throwaway accumulator variable. This is what a senior Ruby dev would write.
Why the others rank lower:
- Solution 1 (your index/while version): correct, but same critique as Q1 – manual pointer + counter for something
Enumerabledoes in one call. Fine as a “let me show I understand the mechanics” opener, but don’t lead with it if asked for idiomatic Ruby. - Solution 3 (
select.size): works, but wasteful –selectbuilds an intermediate array just to throw it away and count its size.countdoes the same job without the allocation. Small thing, but an interviewer watching for efficiency awareness will notice. - Solution 4 (
split('').each):split('')allocates a full array up front;each_chariterates lazily without materializing one. Also reintroduces the manual counter thatcounteliminates. Weakest of the four.
4. Find max in array without .max
# using `Array#each`
def find_max(array)
max = nil
array.each do |num|
max = num if max.nil? || num > max
end
max
end
p find_max([])
p find_max([3, 7, 2, 9, 4])
p find_max([32, 7, 29, 79, 41])
This is actually the idiomatic version – no index needed since each gives you the values directly, and seeding max with nil (instead of array[0] or 0) correctly handles edge cases: empty array returns nil instead of crashing or silently returning wrong data, and it works for negative-only arrays where seeding with 0 would be a bug.
Concept:
- linear scan with running accumulator
Track the best-seen-so-far value in a variable, compare each new element against it, update when you find something better. This is the base pattern behind max/min, and generalizes directly to “find the element matching some condition” problems (max by custom criteria, longest string, etc.).
One thing worth saying in an interview: this is O(n) time, O(1) space and it’s actually not worse than Array#max – that’s what .max does internally too.
Only nitpick: num > max – if you want strict correctness on the first iteration, walk through it: max is nil, max.nil? short-circuits true, so num > max never evaluates against nil (which would raise). Good – that’s intentional short-circuit ordering, not luck. Just make sure you can explain why the order of the || matters if asked.
5. Remove duplicates from array, preserve order
Using Array#uniq
def remove_duplicates(array)
array.uniq
end
remove_duplicates([1,2,2,3,1,4]) => [1,2,3,4]
Without using uniq, select etc.
At First I tried to iterate over array using array index and deleting the duplicated value, then pass the mutated array recursively into the Method. This cause issue like: mutating array (via .delete) while iterating over it with each_with_index – the index no longer matches the shrinking array, so later lookups go out of bounds and return nil.
X - WRONGarray.each_with_index do |num, index| other_nums = array[(index + 1)..-1] other_nums.each do |next_num| if num == next_num # duplicate num uniq_nums << array.delete(num) # store duplicated # find duplicate without duplicated num remove_duplicates(array, uniq_nums) end end end uniq_nums + array
Nested loops (which is what pushed me to O(n²) and the mutation trap in the first place)
General rule: never mutate a collection you’re actively iterating over. This is a classic bug.
Fix – hash-based “seen” tracker, single pass, no uniq/select:
def remove_duplicates(array)
seen = {}
array.each do |num|
seen[num] = true unless seen[num]
end
seen.keys
end
p remove_duplicates([1, 2, 2, 3, 1, 4, 3])
p remove_duplicates([7, 4, 2, 7, 2, 8, 4])
p remove_duplicates([8, 1, 0, 8, 0, 0, 1, 5, 6])
Concept:
- seen-set / membership tracking
Use a hash as an O(1) lookup table for “have I encountered this before?” instead of nested loops. Single pass:
- New value → mark it seen, keep it
- Already seen → skip it, original order preserved naturally since you only append once per unique value
This pattern is the backbone of dedup, “first unique element” – same seen/counts hash idea reused everywhere. No recursion needed;
6. FizzBuzz (1 to n)
The Fizz Buzz problem requires writing a program that prints or returns numbers from 1 to a given integer n, replacing multiples of 3 with “Fizz”, multiples of 5 with “Buzz”, and multiples of both 3 and 5 with “FizzBuzz”.
https://leetcode.com/problems/fizz-buzz/description
The Rules
For every integer i from 1 to n:
- Print “FizzBuzz” if i is divisible by both 3 and 5 (i.e., a multiple of 15).
- Print “Fizz” if i is divisible only by 3.
- Print “Buzz” if i is divisible only by 5.
- Print the number itself as a string if none of the above conditions match.
Example (n = 15)
If n = 15, the output sequence looks like this:1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz"
def fizzbuzz(limit)
result = []
(1..limit).each do |num|
if num % 15 == 0
result << "FizzBuzz"
elsif num % 3 == 0
result << "Fizz"
elsif num % 5 == 0
result << "Buzz"
else
result << num.to_s
end
end
result
end
p fizzbuzz(15)
p fizzbuzz(30)
Concept:
- range iteration + conditional branching, no state carried between iterations (unlike Q5’s
seenhash) – each number is judged independently, so a simpleeachwithif/elsifis the correct tool, nothing fancier needed.
One idiomatic variant worth knowing for interviews, using map instead of manual array-building:
def fizzbuzz(limit)
(1..limit).map do |num|
if num % 15 == 0
"FizzBuzz"
elsif num % 3 == 0
"Fizz"
elsif num % 5 == 0
"Buzz"
else
num.to_s
end
end
end
7. Anagram check
def anagram?(first, second)
return false unless first.length == second.length
first_count_hash = Hash.new(0)
second_count_hash = Hash.new(0)
first.each_char {|char| first_count_hash[char] += 1 }
second.each_char {|char| second_count_hash[char] += 1 }
first_count_hash == second_count_hash
end
p anagram?("listen", "silent")
p anagram?("note", "tone")
p anagram?("act", "cat")
p anagram?("earth", "heart")
p anagram?("earth", "hears")
Fix – frequency count comparison (no sort, no mutation, no uniq/tally even):
CONcept
- frequency-count comparison
Two strings are anagrams iff they have identical character-frequency distributions. Build a count hash for each string (Hash.new(0) gives a default of 0 so += 1 works without checking key? first), then compare the two hashes directly – Ruby’s Hash#== checks all key-value pairs match, regardless of insertion order.
This is the same “seen/counts” hash idea from Q5, reused again – frequency-count hashes are one of the most repeated tools across string/array interview problems (anagrams, first-unique-char, “group anagrams,” character-frequency questions). Worth internalizing as your default first move whenever a problem involves counting occurrences of something.
8. Sum of array, no .sum
Using Array#inject
def array_sum(array)
array.inject(:+)
end
Without using Array#inject
# without using .sum, .inject
def array_sum(array)
return "provide non-empty array" if array.empty?
sum = 0
array.each do |num|
sum += num
end
sum
end
p array_sum([1,2,3,4])
p array_sum([2,72,1,0])
Correct Solution. each with an accumulator is the right idiom here – this is one of the few cases where a keeping-total variable isn’t a code smell, because you genuinely need to carry state (the sum) across iterations, unlike Q6’s FizzBuzz where each element was independent.
Concept:
- accumulator pattern
Since I am avoiding .sum/.inject here specifically, Good to say explicitly: “In production I’d use .sum; writing it manually to show the mechanics.”
9. Capitalize each word (title case), no .capitalize on whole string
def title_case(sentence)
sentence.split.map { |word| word[0].upcase + word[1..] }.join(' ')
end
p title_case("the ruby language")
Concept:
- split → transform each element → rejoin.
split(whitespace-aware, collapses multiple spaces automatically) →mapfor a 1-to-1 word transform (same reasoning as Q6’s FizzBuzz – independent per-element transform, no accumulator needed) →jointo reassemble.
10. Find second largest number
# this has one BUG - check below
def second_largest(array)
return nil if array.length < 2
second_largest = array.first
largest = array.first
array[1..].each do |num|
if num > largest
second_largest = largest
largest = num
elsif num < largest && num > second_largest
second_largest = num
end
end
second_largest
end
p second_largest([4, 1, 9, 7, 9])
p second_largest([4, 3, 2, 3, 4])
p second_largest([50, 100, 150, 200])
Concept:
- single-pass dual-tracking – same accumulator idea as Q4/Q8, but tracking two running values instead of one, with an ordering dependency between them (you can only correctly update
second_largestrelative to wherelargestcurrently stands, which is why theelsifmust re-check against both bounds, not just one).
BUG FOUND:
second_largest([4, 3, 2, 3, 4]) => 4 X WRONG - Why?
Good catch – real bug. Walk through it:
largest = second_largest = 4 (both seeded to array.first)
The real problem: initializing both trackers to the same value creates a chicken-and-egg lockout – second_largest can only be beaten by something bigger, but it started at the max, so nothing (except a new largest) can ever unseat it.
Fix – seed with -Infinity, don’t assume array.first is a valid second-place candidate:
# FIXED
def second_largest(array)
return nil if array.length < 2
largest = second_largest = -Float::INFINITY
array.each do |num|
if num > largest
second_largest = largest
largest = num
elsif num > second_largest && num != largest
second_largest = num
end
end
second_largest
end
Concept refinement:
- dual-tracker pattern (Q10 original) +
-Float::INFINITY, not real array values – this is the standard idiom for “find min/max/second-max” problems specifically because it guarantees the first comparison always succeeds and updates correctly – good to be able to answer each one individually if an interviewer pushes on “why did you add that condition?”
NOTE: This is a good one to remember: never seed a running-max/min tracker with an actual data element unless you’re certain it can’t create a lockout — -Infinity/nil-then-check (like your Q4 find_max) are the safe patterns.
11. Find Alice and Bob spending amount from orders
Qn) Find the total amount spend by Alice and Bob
details = [
{ amount: 2500, requestor: 'Alice', id: 23 },
{ amount: 2500, requestor: 'John', id: 22 },
{ amount: 1500, requestor: 'Bob', id: 21 },
{ amount: 1500, requestor: 'Alice', id: 23 },
{ amount: 1000, requestor: 'Bob', id: 21 },
{ amount: 500, requestor: 'Sera', id: 20 }
]
Answer:
# Answer 1 (filter + each + accumulator)
result = Hash.new(0)
filters = ["Alice", "Bob"]
details.filter { |order| filters.include?(order[:requestor]) }
.each {|order| result[order[:requestor]] += order[:amount] }
puts result
# Answer 2 (filter + group_by + each_pair + reduce + accumulator)
filters = ["Alice", "Bob"]
result = []
details.filter {|order| filters.include?(order[:requestor]) }
.group_by {|order| order[:requestor]}
.each_pair { |user, orders| result << { "#{user}": orders.reduce(0) {|sum, order| sum += order[:amount] } } }
puts result
12. Pattern check
Qn) Find the expression pattern match correctly with words included in it
# my solution
def pattern_check(pattern = "")
return true if pattern.empty?
pairs_hash = {
"{" => "}",
"(" => ")",
" " => " "
}
symbols = pattern.split("")
last_index = symbols.size - 1
first_half = last_index / 2
# p symbols
idx = 0
while (idx <= first_half)
symbol = symbols[idx]
pair = symbols[last_index - idx]
# p "symbol: #{symbol}"
# p "pair index: #{last_index - idx}"
# p "static pair symbol: #{pairs_hash[symbol]}"
# p "pair symbol received: #{pair}"
if symbol.match(/\w+/) && pair.match(/\w+/)
idx += 1
next
end
unless pairs_hash[symbol] == pair
return false
end
idx += 1
end
return true
end
p pattern_check("{()}") # => true
p pattern_check("{(})") # => false
p pattern_check("{( text )}") # => true
The above solution is based on Two Pointer approach and is not correct.
Check for the correct solution (Stack Approach) here: https://railsdrop.com/what-the-question-is-actually-asking/