DEV Community

Augusts Bautra
Augusts Bautra

Posted on • Edited on

4 1

Memoization in Ruby

Memoization is a simple and powerful performance optimisation technique that all Ruby developers should have full command of.
Here I'll go over three memoisation variants, in order of increasing difficulty:

  1. Naive ||=-based memozation
  2. Intermediate defined?-based memoization
  3. Advanced memoization for methods with parameters

1. ||=

A popular Ruby idiom, pronounced "or-equals", where assignment only happens if the lefthand side is falsey. Use this where you know that any potential assigned values will never be falsey.

def response
  # it is a common practice to store the memoized value in
  # an instance variable (ivar) of the same name as the method.
  @response ||= expensive_operation
end
Enter fullscreen mode Exit fullscreen mode

2. defined?-based memoization

A less known pattern that is capable of memoizing falsey values. Also useful if expensive_operation is a multiline statement.

def response
  return @response if defined?(@response)

  # NB, this is a regular assignment because
  # line above already did the cache check.
  @response = expensive_operation
end
Enter fullscreen mode Exit fullscreen mode

3. Memoization for methods with parameters

Sometimes it's useful to memoize several values, depending on the arguments given to a method. This approach can only be used if the values the method returns (and that are about to be memoized) are idempotent i.e. remain the same on repeat calls.

It's a good practice to name the ivar in plural of the method name.

Notice that memo_key must be something unique based on the arguments given. Using Object#hash is a general approach, but there can be others, for example, args can be of a known type and have a unique identifier returned by .id...

def response(arg1, arg2)  
  @responses ||= {}
  memo_key = "#{arg1.hash}_#{arg2.hash}"

  return @responses[memo_key] if @responses.key?(memo_key)

  @responses[memo_key] = begin
    puts "Doing expensive operation"
    expensive_operation
  end
end

> response(:a, :b)
Doing expensive operation
=> something
> response(:a, :b)
=> something

> response(false, false)
Doing expensive operation
=> something_else
> response(false, false)
=> something_else
Enter fullscreen mode Exit fullscreen mode

Sentry image

Hands-on debugging session: instrument, monitor, and fix

Join Lazar for a hands-on session where you’ll build it, break it, debug it, and fix it. You’ll set up Sentry, track errors, use Session Replay and Tracing, and leverage some good ol’ AI to find and fix issues fast.

RSVP here →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay