Skip to content

Instantly share code, notes, and snippets.

@monotykamary
Last active April 7, 2026 06:59
Show Gist options
  • Select an option

  • Save monotykamary/c853ae49ccc23d335060bf35c2be4263 to your computer and use it in GitHub Desktop.

Select an option

Save monotykamary/c853ae49ccc23d335060bf35c2be4263 to your computer and use it in GitHub Desktop.
Reward Aggregator
# frozen_string_literal: true

# Reward Aggregator
# Partners return inconsistent key types: some JSON APIs return strings,
# internal services return symbols. Result must unify them.

GRAB_PARTNER = {
  'reward_a' => { 'points' => 5000, 'key' => 'gr-a', 'tier' => 'silver' },
  'reward_b' => { 'points' => 7500, 'key' => 'gr-b' },
  'reward_c' => { 'points' => 9000, 'key' => 'gr-c' },
  'reward_d' => { 'points' => 12000, 'key' => 'gr-d' },
}.freeze

# NOTE: Others use idiomatic Ruby symbol keys
SHOPEE_PARTNER = {
  reward_a: { points: 5500, key: 'sh-a' },
  reward_d: { points: 10000, key: 'sh-d' },
}.freeze

BE_PARTNER = {
  reward_b: { points: 7000, key: 'be-b' },
  reward_c: { points: 9000, key: 'be-c' }, # tie with grab!
  reward_d: { points: 9500, key: 'be-d' },
}.freeze

GO_PARTNER = {
  reward_d: { points: 8500, key: 'go-d' },
  reward_e: { points: 13000, key: 'go-e' },
}.freeze

def fetch_grab
  sleep(0.01)
  GRAB_PARTNER
end

def fetch_shopee
  sleep(0.03)
  SHOPEE_PARTNER
end

def fetch_be
  sleep(0.04)
  BE_PARTNER
end

def fetch_go
  sleep(0.02)
  GO_PARTNER
end

# REQUIREMENTS:
# 1. Accepts Array<Proc|Method> (callable API fetchers)
# 2. Returns Hash mapping reward_id -> best option hash with LOWEST :points cost
# 3. TIE-BREAKING: If point costs are equal, the LATER partner in the input 
#    array wins (preserves business priority ordering)
# 4. MEMORY OPTIMIZATION: If only ONE partner provided, return the original 
#    Hash object directly (not a shallow/deep copy). Use Object#equal? to verify.
# 5. KEY COMPATIBILITY (THE QUIRK): Partner APIs are inconsistent—Grab returns
#    string keys, others return symbols. The aggregated result must support 
#    indifferent access (both result[:reward_a] and result['reward_a'] should 
#    return the same value). Hint: How does Rails handle params?
# 6. ROBUSTNESS: Skip entries without :points/'points' key. Handle empty input.
#
# -----------------------------------------------------------

def aggregate_rewards(fetchers)
  # Your implementation here
  raise NotImplementedError, 'TODO: Implement aggregation logic'
end


# Testing

puts "=== Test Group 1: Four-way aggregation (string vs symbol keys) ==="
result = aggregate_rewards([method(:fetch_grab), method(:fetch_shopee), method(:fetch_be), method(:fetch_go)])

puts "test 1 (cheapest wins): #{result[:reward_a].equal?(GRAB_PARTNER['reward_a'])} # grab cheaper (5k < 5.5k)"

puts "test 11 (indifferent access symbol): #{result[:reward_a][:points] == 5000} # access via symbol key"
puts "test 12 (indifferent access string): #{result['reward_a']['points'] == 5000} # access via string key"
puts "test 13 (identity preserved): #{result[:reward_a].equal?(result['reward_a'])} # same object for both keys"

puts "test 3 (tie break): #{result[:reward_c].equal?(BE_PARTNER[:reward_c])} # tie at 9k, later partner (be) wins"
puts "test 14 (cross access tie): #{result['reward_c'].equal?(BE_PARTNER[:reward_c])} # string access to symbol-defined reward"

puts "\n=== Test Group 2: Single source indifferent access ==="
result3 = aggregate_rewards([method(:fetch_grab)])

puts "test 10a (object identity): #{result3.equal?(GRAB_PARTNER)} # single source returns original frozen hash"
puts "test 10b (indifferent on single): #{result3['reward_a'].equal?(result3[:reward_a])} # supports both access types"

puts "\n=== Interview Discussion Points ==="
puts "1. Threading: Fetch partners concurrently to beat 10+40ms sequential time?"
puts "2. Indifferent Access: Did you use ActiveSupport::HashWithIndifferentAccess or manual key normalization?"
puts "3. Identity vs Convenience: Single-source optimization conflicts with wrapping for indifferent access. How did you resolve?"
puts "4. Ascenda Scale: With 20+ partners, how would you avoid O(n²) key conversion overhead?"

Before you begin: Please open this file in your preferred IDE (VS Code, WebStorm, etc.) and run it using Ruby. Make sure to disable any AI-assisted features such as GitHub Copilot, Tabnine, or any other code completion/suggestion tools before you start. We want to see your own thought process and problem-solving approach.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment