---
title: Ruby Memoization and Alternatives
teaser: As Ruby developers, we tend to memoize too often.
tags: ruby,web
author: Joël Quenneville
published_on: 2018-12-05
---

![memoization](https://images.thoughtbot.com/master/feEEXaqR0SzpxLt6OwBi_memoization.png)

In Ruby, it's common to use **memoization** to make sure that instance variables
in a method only get set once regardless of how many times the method is called.
For example:

```ruby
class Dashboard
  def users
    @users ||= Users.all
  end
end
```

Sometimes there are better solutions. Let's look at the problem we're solving
and the trade-offs involved.

## No caching

Memoization is often used when deriving values from other state on our objects.
Saving derived state to an instance variable **is a form of caching** and comes
with all the associated gotchas (cache invalidation!).

Most of the time, this caching is a form of **premature optimization**. It's
often best to approach caching problems with a cost/benefit analysis. Caching
has some upfront costs you always pay: extra complexity and cache invalidation.
The desired benefit is to be able to skip some work.

Let's look at some common scenarios:

1. For operations that are done only once you pay the cost but don't get any
   benefits.
2. For cheap operations that are used multiple times, you pay the cost but often
   the benefit of skipping the work trends towards zero unless you're working at
   an extremely high volume.
3. For expensive operations that get called multiple times, the benefit of only
   doing the work once (or not doing it at all in a lazy method) may be worth
  the cost.

Adding a caching layer makes the code harder to reason about and more bug-prone.
Make sure you benchmark your code to make sure the benefits are worth the cost.
Usually it's OK to do the same cheap operation more than once!

```ruby
class User
  # ...

  def age
    # No need to complicate the code by caching this
    # @age ||= Time.now.year - @date_of_birth

    Time.now.year - @date_of_birth
  end
end
```

## Constructors

Memoization is useful because it allows us to do some work only once and then
have it available via an instance variable. There's another construct in Ruby
that has this feature - constructors! You can almost always convert a memoized
method into a simple reader by moving the logic to the constructor.

In general, you want to be setting most of your instance variables in the object
constructor at initialization time.

```ruby
class Dashboard
  attr_reader :users

  def initialize
    @users = Users.all
  end
end
```

Note that this approach is *eager*. All the calculations are done immediately
upon object instantiation. This is fine most of the time. Occasionally you
prefer to defer an expensive calculation until the result is actually needed.
That's when you're going to need another technique.

## Laziness

If you're doing some expensive work that may not necessarily get used, you may
prefer a *lazy* approach. This is where memoization shines.

Instead of doing the work in the constructor, you do it in a method. The
expensive calculations don't happen at object instantiation but instead only
happens when the method is called. Memoization ensures the result is cached so
we don't calculate every time the method is called.

```ruby
class Dashboard
  def stats
    @stats ||= begin
      # do expensive work
    end
  end
end
```

## Separate caching and calculation

When caching, it's best to separate caching logic and calculation logic into
their own methods. This improves readability and also makes it easy to re-run
the calculations if necessary. As a bonus, it means you don't need to deal with
those awkward `begin ... end` blocks!

```ruby
class Dashboard
  def stats
    @stats ||= do_expensive_work
  end

  private

  def do_expensive_work
    # do expensive work
  end
end
```

This works with the constructor approach too:

```ruby
class Dashboard
  attr_reader :stats

  def initialize
    @stats = do_expensive_work
  end

  private

  def do_expensive_work
    # do expensive work
  end
end
```

The idea of separating _branching code_ (yes `||=` is a form of branching!) from
_doing code_ applies in a lot of other contexts too. Along with two other
similar ideas, it forms part of a trio of principles I call the [triangle of
separation](https://thoughtbot.com/blog/triangle-of-separation).

## Conclusion

Memoization has two big benefits:

1. Cache expensive work
2. Delay expensive work via laziness

As a form of caching it comes with all the advantages and downsides of such. It
also complicates a codebase.

Most common uses of memoization in Ruby are premature optimization. For
operations that:

* _are always used by your object_ then set the instance variable in the
  constructor and have a normal reader
* _are only used once_ then a regular method is fine
* _are cheap_ then a regular method is fine
* _are expensive and not always used_ then you may want to use a memoized method
  to do the work lazily

In all cases, move the calculation to its own method!
