MemoWise: Ruby memoization you can reset, preset and measure
The wise choice for Ruby memoization
At a glance
- What is it?
- MemoWise is a Ruby gem that adds memoization to a class or module by prepending it, with methods for resetting and presetting cached values, support for class methods, frozen objects and inheritance, and a documented thread-safety contract. It suits a Ruby team that wants memoization it can invalidate, and it is a behavioural library rather than a pure speed trick.
- Who is it for?
- MemoWise is worth adopting in a Ruby codebase where a method is expensive and its result is stable for a while, because it gives you reset_memo_wise and preset_memo_wise, which is the part most memoization libraries leave you to build yourself, and because the thread-safety contract is written down rather than implied.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 25 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Memoization you can invalidate, which is the actual feature
Ruby memoization gems are easy to write and easy to get subtly wrong. The usual shape is a hash in an instance variable, keyed by the arguments, checked before the method body runs. That part is not the hard part. The hard part is what happens when the cached value should no longer be trusted, and the usual answer in a hand-rolled implementation is that there is no answer, you reach into the instance variable and clear it by hand.
MemoWise is built around that gap. When you prepend MemoWise within a class or module, it exposes three methods: memo_wise, preset_memo_wise and reset_memo_wise. The first is the declaration that marks a method as memoizable. The other two are the difference between this library and a hash in an instance variable.
preset_memo_wise lets you store a value ahead of the first call, which is what you want when you already know the answer, for example a value loaded from a database during initialization that should not trigger the slow path on first access. reset_memo_wise lets you clear cached results, either for one method, for one argument of a method, or for every memoized method on the object.
The documentation is explicit that memoization can also be reset for all methods, or for just one argument, so the invalidation granularity is finer than a single boolean.
That is the feature to judge the library on. If your expensive method never needs invalidating, a plain hash is enough and this gem is overhead. If it does need invalidating, then this gem removes the part of memoization that is actually hard to get right.
Prepending a module is the whole installation
There is no configuration, no plugin registration and no initializer. You add the gem and you prepend it.
The installation is a Gemfile line:
gem 'memo_wise'and then bundle install, or gem install memo_wise if you are not using Bundler.
Using it looks like this:
class Example
prepend MemoWise
def slow_value(x)
sleep x
x
end
memo_wise :slow_value
private
# maintains privacy of the memoized method
def private_slow_method(x)
sleep x
x
end
memo_wise :private_slow_method
endTwo details in that example are worth pausing on.
The first is that memo_wise :slow_value is a call in the class body, not a modifier on the method definition. The method is declared normally and then registered afterwards. That is a different style from a def-level keyword, and it has a practical consequence: the registration line is ordinary Ruby that runs at class definition time, so it can be arranged, generated or applied conditionally in ways an attribute-style marker cannot.
The second is the private method. The example registers a private method with memo_wise and the comment says it maintains privacy of the memoized method. Because memo_wise is a call rather than an attribute, the method's visibility stays as declared, and the memoization wrapper does not make a private method public.
The instance and class method forms are the same API in different directions. For a class method, the registration takes self: :class_slow_value, and the reset and preset calls are made on the class itself, Example.reset_memo_wise(:class_slow_value) and Example.preset_memo_wise(:class_slow_value, 3) { 4 }.
The thread-safety contract, written out in two paragraphs
Most memoization libraries say nothing about what happens when two threads call the same uncached method at the same time. MemoWise states two guarantees on all supported Ruby versions, and they are stated differently before and after a value exists.
Before a value has been memoized, contended calls from multiple threads may each call the original method, may return different valid results when the method is nondeterministic like rand, and will memoize exactly one valid return value.
After a value has been memoized, contended calls from multiple threads always return the same memoized value.
Read those together and the contract is precise. There is no lock around the first call, so N threads racing into an uncached method can all do the expensive work. Each may compute a different answer if the method is not deterministic. What is guaranteed is that exactly one of those answers ends up stored, and from that point on every caller sees that one value.
That is a deliberate design choice rather than an oversight, and it has a cost worth naming. A method with side effects, a method that increments a counter, a method that writes to a store, will run more than once when two threads arrive together. If your method must run exactly once, memoization is the wrong tool, and the documentation tells you enough here to work that out yourself before you rely on it.
The second guarantee is the one people usually want: once the value is there, every thread sees it, so a memoized method is safe to call from many threads as long as the method itself is idempotent.
The benchmark table is the clearest argument for the library
The readme publishes a benchmark table against four other gems, run in GitHub Actions and regenerated on every code change. The framing matters: values above 1.00x represent how much slower each gem's memoized value retrieval is than the latest commit of MemoWise, measured with benchmark-ips 2.14.0.
The rows are argument shapes rather than generic cases: no arguments, one positional argument, two positional, one keyword, two keywords, a positional with a keyword, a splat, a double splat, and a fully mixed signature. Results are given for Ruby 3.4.3.
Across those rows the picture is consistent for the argument-free and simple-positional cases, where the competing gems are several times slower. The margin narrows sharply as the signature gets complex: with a splat and a double splat, and with the fully mixed signature, the competing gems come down to roughly 1.5x to 3x, and in the fully mixed row the closest competitor is at 1.52x.
That shape tells you something real. A memoization key has to be built from the arguments, and building a key for a complex signature is where the cost goes. The gap between 18x and 1.5x across the table is the cost of key construction, and it is the reason a benchmark that only measures the no-argument case tells you very little.
One footnote matters for anyone reading the dry-core column: it may cause incorrect behavior caused by hash collisions, with a link to the dry-rb issue. That is a caveat about the comparison, not about MemoWise, but it is the kind of thing that should stop you from reading the dry-core numbers as a fair fight.
You can reproduce the table yourself, which is what a project publishing numbers should let you do:
$ cd benchmarks
$ bundle install
$ bundle exec ruby benchmarks.rbThe readme asks for a pull request or an issue if your results differ or if another gem should be benchmarked.
Class methods, frozen objects and inheritance, and what still does not work
The feature list is short and every entry corresponds to a case where a naive memoization implementation falls over.
Class and module methods are supported, with the self: form of registration shown earlier. Support for memoization on frozen objects matters because a frozen instance cannot have an instance variable assigned to it lazily, so an implementation that stores the cache in an instance variable cannot work on a frozen object at all. Support for inheritance of memoized class and instance methods means a subclass inherits the memoization rather than silently losing it.
The one hard limitation is stated plainly: methods which take implicit or explicit block arguments cannot be memoized. There is no workaround offered, and the reason is structural rather than an oversight in the implementation. A memoization key has to capture the method's arguments, and a block is not an argument in the sense a key can be built from. A method that takes a block is out of scope for this library, full stop.
That limitation is worth checking against your own code before anything else, because it is the one that will make the library inapplicable. A method that yields is a common shape, particularly for collection-returning methods where the block does the filtering.
Documentation is generated with YARD and published at rubydoc.info, and the readme also shows how to generate it locally. The doc dependencies are installed separately from the main bundle:
BUNDLE_WITH=docs bundle installand a hot-reloading server is available:
BUNDLE_WITH=docs bundle exec yard server --reloadwhich serves the docs at http://127.0.0.1:8808/ and refreshes them as you edit. The static generation is bundle exec yard, producing docs/index.html.
The repository also treats its own documentation as tested. yard-doctest runs the examples in the YARD docs with bundle exec yard doctest, and dokaz tests the examples in the README and other non-code documentation. That is worth knowing because it means the usage examples in the readme are verified rather than aspirational, and the .dokaz and .yardopts files in the tree are how that verification is configured.
Where MemoWise is the wrong choice
The first limit is the block-argument rule, and it is disqualifying for a whole class of methods. If the expensive call you want to cache takes a block, MemoWise cannot help and no configuration will change that.
The second limit is the caching lifetime. MemoWise caches on the instance for as long as the instance lives, unless you reset it. A method whose result depends on external state that changes, a database row that another process updates, a configuration value that is reloaded, will serve a stale value until someone calls reset_memo_wise. That is the correct behaviour for a memoization library, and it is the reason reset_memo_wise exists, but it means the correctness of your code depends on you calling reset at the right time. A library that reset itself on every change would be a different and much more invasive thing.
The third limit is the first-call race described earlier. Under contention on an uncached value, the underlying method may run several times. That is fine for a pure calculation and wrong for anything with a side effect.
The fourth is that a dependency is a dependency. For a memoization need that is one hash in one class, pulling in a gem with its own version line, its own thread-safety semantics and its own support history is a heavier answer than the problem. The gem is worth it when you want the invalidation API, the class method support and the frozen object handling without writing and testing them yourself.
The fifth is the state of the project. The repository is not archived and the last push was on 2026-09-28, so work is landing. But there are no GitHub releases, the licence is MIT with a LICENSE.txt file in the tree, and the release cadence that a gem version number implies is not visible in this repository. A team pinning memo_wise in a Gemfile.lock is depending on the RubyGems release, not on this repository's commits.
Editorial conclusion
MemoWise is worth adopting in a Ruby codebase where a method is expensive and its result is stable for a while, because it gives you reset_memo_wise and preset_memo_wise, which is the part most memoization libraries leave you to build yourself, and because the thread-safety contract is written down rather than implied. It is the wrong choice for a method whose result changes within one request, and it is the wrong choice if you want the cheapest possible lookup with no dependency. Before you add it, check the three things the documentation states: that methods taking implicit or explicit block arguments cannot be memoized, so a method with a block is out of scope; that the value memoized before a race is exactly one valid return value while the losing threads may each call the original, so a method with side effects behaves differently under contention than you might assume; and the benchmark table, where the comparison gems are given at fixed versions, since the dry-core row carries a note that it may cause incorrect behavior caused by hash collisions.
Frequently asked questions
How do I install MemoWise in a Ruby project?
Add gem 'memo_wise' to your application's Gemfile and run bundle install, or install it directly with gem install memo_wise. The gem is published on RubyGems as memo_wise and is MIT licensed.
How do I use MemoWise to memoize a method?
Prepend MemoWise inside your class or module and call memo_wise with the method name in the class body. The same three methods, memo_wise, preset_memo_wise and reset_memo_wise, are then exposed, and class methods are registered with the self: form such as memo_wise self: :class_slow_value.
Can MemoWise memoize a method that takes a block?
No. The documentation states that methods which take implicit or explicit block arguments cannot be memoized. The reset and preset calls also work at a finer granularity than whole methods, since memoization can be reset for all methods, for just one method, or for just one argument.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/panorama-ed-memo-wise)Was this useful?