401(k) Required Minimum Distributions (RMDs) now start at age 72

401(k) Required Minimum Distributions (RMDs) now start at age 72

401(k) plans can help you save for retirement in a significantly tax-advantaged way. However, the Internal Revenue Service (IRS) requires that you start taking withdrawals from their qualified retirement accounts when you reach the age 72. These withdrawals are called required minimum distributions (RMDs).

Why do I have to take RMDs?

In exchange for the tax advantages you enjoy by contributing to your 401(k) plan, the IRS requests collection of taxes on these amounts when you turn 72. The IRS taxes RMDs as ordinary income, meaning withdrawals will count towards your total taxable income for the year.

Generally, the IRS collects taxes on the gains in retirement accounts such as 401(k)s. However, if Roth 401(k) account assets are held for at least 5 years, Roth 401(k) funds are not taxed. Because there are taxes being paid to the government, these distributions are NOT eligible for rollover to another account.

When do I have to start taking RMDs?

Before the 2019 SECURE Act, RMDs applied to employees who turned 70 ½. However, this legislation increased the RMD age to 72 starting in 2020.

You may remember that the CARES Act, passed in March 2020 in response to the COVID-19 crisis, temporarily waived RMDs for 2020. RMDs resumed for the 2021 plan year with the newly increased age limit of 72.

How much do I have to withdraw?

RMDs are calculated based on your age and your account balance as of the end of the previous year. To determine the required distribution amount, Betterment divides your previous year’s ending account balance by your life expectancy factor (based on your age) from the uniform lifetime table (shown below). As a note, if you had no balance at the end of the previous year, then your first RMD will not occur until the following year.

Uniform Lifetime TableAge of Plan ParticipantLife Expectancy (in Years)7027.47126.57225.67324.77423.87522.97622.07721.27820.37919.58018.78117.98217.18316.38415.58514.88614.18713.48812.78912.09011.49110.89210.2939.6949.1958.6968.1977.6987.1996.71006.31015.91025.51035.21044.91054.51064.21073.91083.71093.41103.11112.91122.61132.41142.1115 and older1.9

Additionally, if you have taken a cash distribution from your 401(k) account in any given year you are subject to an RMD, and that distribution amount is equal to or greater than the RMD amount, that distribution will qualify as the required amount and no additional distribution is required.

Does everyone who turns 72 need to take an RMD?

Turning 72 in a given year doesn’t mean that you have to take an RMD. Only those who turn 72 in a given year AND meet any of the following criteria must take an RMD:

You have taken an RMD in previous years. If so, then you must take an RMD by December 31 of every year.You own more than 5% of the company sponsoring the 401(k) plan. If so, then you must take an RMD by December 31 every year.You have left the company (terminated or retired) in the year you turned 72. If so, then the first RMD does not need to occur until April 1 (otherwise known as the Required Beginning Date) of the following year but must occur consecutively by December 31 for every year.>Example: John turned 72 on June 1, 2021. John also decided to leave his company on August 1, 2021. He has been continuously contributing to his 401(k) account for the past 5 years. The first RMD must occur by April 1, 2022. The next RMD must occur by December 31, 2022 and every year thereafter.>NOTE: that this criteria means that you do NOT need to take an RMD if you meet the RMD age and are still working.You are a beneficiary or alternate payee of an account holder who meets the above criteria.

What are the consequences of not taking an RMD?

Failure to take an RMD for a given year will result in a penalty of 50% of the amount not taken on time by the IRS.

How do I take an RMD?

Your employer will notify you that you may be subject to an RMD and provide you with an RMD form. You will need to fill and return this form to your employer for approval. Betterment will process the RMD and the distribution will be delivered via the method of your choice (check or ACH).

Want a better 401(k)?
Learn More>

Did you miss our previous article…

Meet $VOTE: Channeling Our Values Through Shareholder Engagement

Meet $VOTE: Channeling Our Values Through Shareholder Engagement


Today, we are excited to announce that we will begin integrating the $VOTE ETF, recently launched by Engine No. 1, into all of Betterment’s Socially Responsible Investing portfolios.

This new ETF invests in 500 of the largest U.S. companies, weighted according to their size, with a management fee of only .05%. You might think that this sounds a lot like a garden variety index fund tracking the S&P 500—a commodity for many years now.

So, why the excitement?

In short, $VOTE represents a highly innovative approach to pushing the economy towards sustainability via index fund investing. It may be “passive” in the traditional sense—buying shares in companies purely based on an index—but it is “active” when it comes to engaging with those companies as a shareholder.

Beyond Divestment: What’s Shareholder Engagement?

Historically, values-aligned investing has often been synonymous with avoiding the purchase of certain stocks—a practice often referred to as “divestment.” The alternative to divestment is “engagement.” By owning a stock, and using your rights to vote on shareholder resolutions, you can attempt to change the company’s activities from the inside.

Vanguard, BlackRock, and State Street—the “Big Three” largest fund managers—are collectively the biggest shareholders in most companies, but have historically been reluctant to rock the boat and aggressively challenge management. As a result, when it comes to investing through index funds, the full potential of shareholder engagement to drive change hasn’t been tapped.

Engine No. 1’s new $VOTE ETF promises to change that. To understand why, it helps to understand the mechanics of how shareholders can push for change.

Proxy Voting

Purchasing stock in a company grants you not just a share of its profits, but also the right to influence its decision-making. This process is called “proxy voting,” which can be a powerful tool with the potential to transform the entire economy, company by company.

Publicly traded companies operate like quasi-democracies, accountable to their shareholders. They hold annual meetings, where shareholders can vote on a number of topics. Shareholders who disagree with some aspect of how a company’s business is conducted can engage with management, and if they feel they aren’t being heard, can present an alternate course of action by making a “shareholder proposal.”

If they can persuade a majority of all shareholders to vote in support of the proposal, they can overrule management. When more drastic change is warranted, such “activist” shareholders can seek to replace management entirely, by nominating their own candidates for the company’s board of directors.

Shareholder Activism: Social Change Through Engagement

Social change via shareholder activism has a storied history. As early as 1951, in a seminal case, civil rights leader James Peck took the fight to the proxy arena, by filing a shareholder proposal with the Greyhound Corporation, recommending that the bus operator abolish segregated seating in the South.

Seventy years later, on May 26, 2021, activist hedge fund Engine No. 1 stunned the corporate world by winning a proxy battle against the current leadership of ExxonMobil, persuading a coalition of shareholders to elect three of its own candidates to the board—the first ever climate-centered case for change.

Engine No. 1 argued that Exxon’s share price was underperforming that of its peers because the company was unprepared for the transition away from fossil fuels. It nominated candidates for the board that would push the oil giant to embrace renewable energy. Against all odds, holding just .02% of Exxon’s stock, Engine No. 1 prevailed.

Corporate boardrooms across the entire S&P 500 are buzzing, asking what the Exxon coup means for them. Where will environmentally and socially conscious investors strike next? These questions are warranted: The Exxon campaign was a first, but it surely won’t be the last.

“Index Activism”: Bringing Power To The People

Individual investors are increasingly aware of proxy voting as a domain by which their portfolios can channel their values. In a recent Morningstar report, 61% of those surveyed said that sustainability should be factored into how votes attributable to their 401(k)s are cast.

However, most Americans, including Betterment customers, don’t buy stock of companies like Greyhound or Exxon directly, but through index funds.

When you buy a share of an index fund, the index fund manager uses your money to buy stocks of companies on your behalf. As a shareholder of the fund, you benefit financially when these underlying stocks rise in value, but the index fund is technically the shareholder of each individual company, and holds the right to participate in each company’s proxy voting process.

As more investors tell the industry that they want their dollars to advance sustainable business practices, the Big Three have been feeling the pressure to work these preferences into their proxy voting practices.

This year, they are showing some signs of change. Notably, the Big Three ultimately joined Engine No. 1’s coalition, which could not have prevailed against Exxon without their support. However, even if the Big Three, who manage trillions on behalf of individual investors, continue to side with the activists, what’s missing is a way for individuals to invest their dollars not just to support these campaigns, but to spearhead them as well.

What Makes $VOTE Special

Activist shareholder campaigns are generally led by hedge funds, and what happened with Exxon was no exception. However, by launching an ETF that anyone can invest in, Engine No. 1 is looking to break that mold.

In 2020, investors poured $50 billion into sustainable index funds—double that of 2019, and ten times that of 2018. The $VOTE ETF should bring even more investors off the sidelines, and into sustainable investing, for two reasons.

First, rather than dilute its efforts, $VOTE intends to spearhead a handful of campaigns, pushing companies to improve their environmental and social practices. A focus on the highest impact, and most powerful narratives, will continue to raise awareness for the power of shareholder activism.

Second, $VOTE is designed for mass adoption, not as a niche strategy. With a management fee of only .05%, and tracking a market cap weighted index, $VOTE is designed to ensure no trade-off to long-term returns. It is also well-suited for those investing for retirement—and as of today, it will make its way into its first ever 401(k) plan, via Betterment for Business.

What Does $VOTE Mean For Investors?

We know that many of our customers want to invest for real impact, especially if they can do so without sacrificing their long-term financial goals. If you’re investing through any of Betterment’s three Socially Responsible Investing portfolios, $VOTE will have a target weight equal to 10% of your exposure to the U.S. stocks.

With $VOTE in your portfolio, you’ll know that your dollars are directly supporting whatever engagements Engine No. 1 launches next. As their subsequent work unfolds, we will be monitoring their efforts, and updating our customers on the impact their investments are driving.

Now that $VOTE exists, anyone—not just Betterment customers—can invest in it, which is a great thing. The bigger it gets, the more it can drive change, and you, as an investor, get to help write the next chapter.

Invest for better
Join Betterment>

Why (And How) Betterment Is Using Julia

Why (And How) Betterment Is Using Julia

At Betterment, we’re using Julia to power the projections and recommendations we provide to help our customers achieve their financial goals. We’ve found it to be a great solution to our own version of the “two-language problem”–the idea that the language in which it is most convenient to write a program is not necessarily the language in which it makes the most sense to run that program. We’re excited to share the approach we took to incorporating it into our stack and the challenges we encountered along the way.

Working behind the scenes, the members of our Quantitative Investing team bring our customers the projections and recommendations they rely on for keeping their goals on-track. These hard-working and talented individuals spend a large portion of their time developing models, researching new investment ideas and maintaining our research libraries. While they’re not engineers, their jobs definitely involve a good amount of coding. Historically, the team has written code mostly in a research environment, implementing proof-of-concept models that are later translated into production code with help from the engineering team.

Recently, however, we’ve invested significant resources in modernizing this research pipeline by converting our codebase from R to Julia and we’re now able to ship updates to our quantitative models quicker, and with less risk of errors being introduced in translation. Currently, Julia powers all the projections shown inside our app, as well as a lot of the advice we provide to our customers. The Julia library we built for this purpose serves around 18 million requests per day, and very efficiently at that.


Examples of projections and recommendations at Betterment. Does not reflect any actual portfolio and is not a guarantee of performance.

Why Julia?

At QCon London 2019, Steve Klabnik gave a great talk on how the developers of the Rust programming language view tradeoffs in programming language design. The whole talk is worth a watch, but one idea that really resonated with us is that programming language design—and programming language choice—is a reflection of what the end-users of that language value and not a reflection of the objective superiority of one language over another. Julia is a newer language that looked like a perfect fit for the investing team for a number of reasons:

Speed. If you’ve heard one thing about Julia, it’s probably about it’s blazingly fast performance. For us, speed is important as we need to be able to provide real-time advice to our customers by incorporating their most up-to-date financial scenario in our projections and recommendations. It is also important in our research code where the iterative nature of research means we often have to re-run financial simulations or models multiple times with slight tweaks.Dynamicism. While speed of execution is important, we also require a dynamic language that allows us to test out new ideas and prototype rapidly. Julia ticks the box for this requirement as well by using a just-in-time compiler that accommodates both interactive and non-interactive workflows well. Julia also has a very rich type system where researchers can build prototypes without type declarations, and then later refactoring the code where needed with type declarations for dispatch or clarity. In either case, Julia is usually able to generate performant compiled code that we can run in production.Relevant ecosystem. While the nascency of Julia as a language means that the community and ecosystem is much smaller than those of other languages, we found that the code and community oversamples on the type of libraries that we care about. Julia has excellent support for technical computing and mathematical modelling.

Given these reasons, Julia is the perfect language to serve as a solution to the “two-language problem”. This concept is oft-quoted in Julian circles and is perfectly exemplified by the previous workflow of our team: Investing Subject Matter Experts (SMEs) write domain-specific code that’s solely meant to serve as research code, and that code then has to be translated into some more performant language for use in production. Julia solves this issue by making it very simple to take a piece of research code and refactor it for production use.

Our approach

We decided to build our Julia codebase inside a monorepo, with separate packages for each conceptual project we might work on, such as interest rate models, projections, social security amount calculations and so on. This works well from a development perspective, but we soon faced the question of how best to integrate this code with our production code, which is mostly developed in Ruby. We identified two viable alternatives:

Build a thin web service that will accept HTTP requests, call the underlying Julia functions, and then return a HTTP response.Compile the Julia code into a shared library, and call it directly from Ruby using FFI.

Option 1 is a very common pattern, and actually quite similar to what had been the status quo at Betterment, as most of the projections and recommendation code existed in a JavaScript service.

It may be surprising then to learn that we actually went with Option 2. We were deeply attracted to the idea of being able to fully integration-test our projections and recommendations working within our actual app (i.e. without the complication of a service boundary). Additionally, we wanted an integration that we could spin-up quickly and with low ongoing cost; there’s some fixed cost to getting a FFI-embed working right—but once you do, it’s an exceedingly low cost integration to maintain. Fully-fledged services require infrastructure to run and are (ideally) supported by a full team of engineers.

That said, we recognize the attractive properties of the more well-trodden Option 1 path and believe it could be the right solution in a lot of scenarios (and may become the right solution for us as our usage of Julia continues to evolve).


Given how new Julia is, there was minimal literature on true interoperability with other programming languages (particularly high-level languages–Ruby, Python, etc). But we saw that the right building blocks existed to do what we wanted and proceeded with the confidence that it was theoretically possible.

As mentioned earlier, Julia is a just-in-time compiled language, but it’s possible to compile Julia code ahead-of-time using PackageCompiler.jl. We built an additional package into our monorepo whose sole purpose was to expose an API for our Ruby application, as well as compile that exposed code into a C shared library. The code in this package is the glue between our pure Julia functions and the lower level library interface—it’s responsible for defining the functions that will be exported by the shared library and doing any necessary conversions on input/output.

As an example, consider the following simple Julia function which sorts an array of numbers using the insertion sort algorithm:

The insertion sort algorithm implemented in Julia.

In order to be able to expose this in a shared library, we would wrap it like this:

Insertion sort wrapped as C-callable function.

Here we’ve simplified memory management by requiring the caller to allocate memory for the result, and implemented primitive exception handling (see Challenges & Pitfalls below).

On the Ruby end, we built a gem which wraps our Julia library and attaches to it using Ruby-FFI. The gem includes a tiny Julia project with the API library as it’s only dependency. Upon gem installation, we fetch the Julia source and compile it as a native extension.

Attaching to our example function with Ruby-FFI is straightforward:

Ruby FFI binding for insertion sort

From here, we could begin using our function, but it wouldn’t be entirely pleasant to work with–converting an input array to a pointer and processing the result would require some tedious boilerplate. Luckily, we can use Ruby’s powerful metaprogramming abilities to abstract all that away–creating a declarative way to wrap an arbitrary Julia function which results in a familiar and easy-to-use interface for Ruby developers. In practice, that might look something like this:

Abstracted wrapper around Julia insertion sort

Resulting in a function for which the fact that the underlying implementation is in Julia has been completely abstracted away:

Challenges & Pitfalls

Debugging an FFI integration can be challenging; any misconfiguration is likely to result in the dreaded segmentation fault–the cause of which can be difficult to hunt down. Here are a few notes for practitioners about some nuanced issues we ran into, that will hopefully save you some headaches down the line:

The Julia runtime has to be initialized before calling the shared library. When loading the dynamic library (whether through Ruby-FFI or some other invocation of `dlopen`), make sure to pass the flags `RTLD_LAZY` and `RTLD_GLOBAL` (`ffi_lib_flags :lazy, :global` in Ruby-FFI).If embedding your Julia library into a multi-threaded application, you’ll need additional tooling to only initialize and make calls into the Julia library from a single thread, as multiple calls to `jl_init` will error. We use a multi-threaded web server for our production application, and so when we make a call into the Julia shared library, we push that call onto a queue where it gets picked up and performed by a single executor thread which then communicates the result back to the calling thread using a promise object.Memory management–if you’ll be passing anything other than primitive types back from Julia to Ruby (e.g. pointers to more complex objects), you’ll need to take care to ensure the memory containing the data you’re passing back isn’t cleared by the Julia garbage collector prior to being read on the Ruby side. Different approaches are possible. Perhaps the simplest is to have the Ruby side allocate the memory into which the Julia function should write it’s result (and pass the Julia function a pointer to that memory). Alternatively, if you want to actually pass complex objects out, you’ll have to ensure Julia holds a reference to the objects beyond the life of the function, in order to keep them from being garbage collected. And then you’ll probably want to expose a way for Ruby to instruct Julia to clean up that reference (i.e. free the memory) when it’s done with it (Ruby-FFI has good support for triggering a callback when an object goes out-of-scope on the Ruby side).Exception handling–conveying unhandled exceptions across the FFI boundary is generally not possible. This means any unhandled exception occurring in your Julia code will result in a segmentation fault. To avoid this, you’ll probably want to implement catch-all exception handling in your shared library exposed functions that will catch any exceptions that occur and return some context about the error to the caller (minimally, a boolean indicator of success/failure).


To simplify development, we use a lot of tooling and infrastructure developed both in-house and by the Julia community.

Since one of the draws of using Julia in the first place is the performance of the code, we make sure to benchmark our code during every pull request for potential performance regressions using the BenchmarkTools.jl package.

To facilitate versioning and sharing of our Julia packages internally (e.g. to share a version of the Ruby-API package with the Ruby gem which wraps it) we also maintain a private package registry. The registry is a separate Github repository, and we use tooling from the Registrator.jl package to register new versions. To process registration events, we maintain a registry server on an EC2 instance provisioned through Terraform, so updates to the configuration are as easy as running a single `terraform apply` command.

Once a new registration event is received, the registry server opens a pull request to the Julia registry. There, we have built in automated testing that resolves the version of the package that is being tested, looks up any reverse dependencies of that package, resolves the compatibility bounds of those packages to see if the newly registered version could lead to a breaking change, and if so, runs the full test suites of the reverse dependencies. By doing this, we can ensure that when we release a patch or minor version of one of our packages, we can ensure that it won’t break any packages that depend on it at registration time. If it would, the user is instead forced to either fix the changes that lead to a downstream breakage, or to modify the registration to be a major version increase.


Though our venture into the Julia world is still relatively young compared to most of the other code at Betterment, we have found Julia to be a perfect fit in solving our two-language problem within the Investing team. Getting the infrastructure into a production-ready format took a bit of tweaking, but we are now starting to realize a lot of the benefits we hoped for when setting out on this journey, including faster development of production ready models, and a clear separation of responsibilities between the SMEs on the Investing team who are best suited for designing and specifying the models, and the engineering team who have the knowledge on how to scale that code into a production-grade library. The switch to Julia has allowed us not only to optimize and speed up our code by multiple orders of magnitude, but also has given us the environment and ecosystem to explore ideas that would simply not be possible in our previous implementations.

Did you miss our previous article…