When teammates are on the same page about refactoring priorities, they constantly come up with small opportunities that eventually add up.

Growth Engineering

Growth Engineering is all about growing the user base. Of course, to grow a user base, the product quality is important, but there should also be some effort (lots of effort, actually), to persuade the user to try using the product in the first place.

As a result, we Growth Engineers work very closely with the Marketing team, designing ways to increase our visibility in search engines (with SEO - search engine optimization - techniques), to empower users to refer new users, and to call the user to action on our website to sign up.

Count how many Calls to Actions (CoA) are in this homepage. Hint: they want you to get started.

Friction in User Onboarding

So what happens after you “Get started”? Well, Gusto is a Payroll, Benefits and HR platform. Which means we need business owners to input a lot of information about themselves. To enable us to accurately run payroll for businesses, potential users have to first input a non-trivial amount of information. Such information includes:

  • Company address
  • Bank account information
  • Federal tax information
  • State tax information
  • Health benefits information
  • Your employees and contractors
  • Name, job title, compensation, addresses, etc. for each of them
  • Compliance details

…and so on.

Whew! That’s quite a list for folks. Imagine having to track all this down and fill it all out — it’s not surprising that they get discouraged.

Pre-Onboarding Survey (Wine Session)

When running A/B experiments, we run into the most counterintuitive results. It seems intuitive that adding a separate questionnaire in the signup process would frustrate the user - after all, it’s another form (to be more exact, set of forms) to fill out! But we found out just having that survey lifted the conversion rate by a pleasantly surprising amount.

This pre-onboarding survey was intended to be so short it could be completed while sipping on a glass of wine - hence the codename “Wine Session.” Perhaps the quick feeling of accomplishment and a sense of commitment was what spurred on our potential users to keep chugging through the rest of onboarding, which isn’t as quick and streamlined.

Step 1. Payroll timeline
Step 2. Team makeup
Step 3. Entity and industry

Opportunity for Code Refactoring

But the human behavioral and UI/UX questions aside, I want to talk about the engineering decisions we made as we got there. I think the engineering paradigm behind the Wine Session, although invisible, is part of what made the experience so streamlined and satisfying, and also easy to iterate on for us engineers.

Above, we saw how the onboarding process can be a bottleneck for the Gustomers (Gusto customers 😉). However, it was a place where Growth engineers also saw ways to streamline the development process as well.

To illustrate, onboarding steps related to taxes and payrolls use the actual tax and payroll Rails Active Record models that “the rest of ZP” uses. ZP stands for zenpayroll, which is the codebase for most of the Gusto apps. So in other words, the onboarding steps are not separated from the rest of the Gusto app.

As a result, the engineer making changes to an onboarding step, however small, has to work with the big Rails Active Record models. We intuited that separating the onboarding steps out would improve the development process, and consequently let the Growth team iterate faster with A/B experiments on UI changes.

However, we have many other exciting and urgent projects beyond refactoring the code. It didn’t seem like the best decision to sit down and decouple all of onboarding from the rest of ZP, while all of our other efforts are at halt. So instead, we are taking the pragmatic approach, going in the right direction whenever we have the opportunity.

Architectural Paradigm

Wine Session was one of those opportunities. It’s a pre-onboarding survey, so it was both part of the onboarding process and independent of it at the same time. In other words, the Wine Session collects information that used to be collected in the onboarding process, which makes it be sort of related to the onboarding process. But it’s a pre-onboarding survey, so it is completed before entering the established onboarding process.

The onboarding process so far was directly saving the information in the ZP models, which, as stated above, was inefficient. One solution we thought about was to save the information in an intermediary model just for the onboarding process, and then send the information to ZP once all the information is collected. The information has to be eventually saved into the monolith. Otherwise the app wouldn’t have all the information our new Gustomer has diligently entered.

Diagram credit to Sherry Chai

Yet, as stated above, refactoring the entire process to save to an intermediary model first and then save to ZP at the end would take too long. That is because there are more than ten onboarding steps, each of which has multiple information to enter (sometimes dozens, like the “Add Employee” step), and each of those information are saved in different ZP models, and go through multiple validation processes which might involve other models as well.

That’s why the Wine Session was a great opportunity to experiment with this paradigm. It would be sort of an MVP. And it’s not even refactoring - it’s building a new backend anyway, but we would approach it with a mindset for refactoring.

So what did the implementation details look like for the Wine Session?

First, we created a new model named CompanyOnboardingInput. This was to be the intermediary model containing all information collected from Wine Session. Instead of breaking apart the model into separate models for each section within Wine Session, we kept it a  very long model (as of now, it has 41 attributes, and that’s just the information collected from Wine Session). However, in the future, as the usage of this model expands to the main onboarding, we probably want to consider separating the model, maybe one per onboarding step.

Each section has more attributes connected to them than what the arrows show in the diagram. I just didn’t want to make a diagram of arrow spaghettis.

Second, we built a CompanyOnboardingInputSynchronizer service class. This class was responsible for saving the information saved in CompanyOnboardingInput into its corresponding ZP class at the end of the Wine Session flow. So it runs as soon as the user clicks the final “Submit” button.

By having this at the end of the flow, we concentrate the effort of saving the information to the other models in one place (and one service class). This makes it super easy to make changes in the code. Also, saving to the rest of ZP models takes some time, due to the complicated validation processes we have for them. As a result, if we were saving to the ZP models at the end of each section, then the user would experience friction between each section. However, we are concentrating the friction in one place, at the end of the Wine Session.

Combining the two diagrams, we get this big picture that summarizes the architecture of Wine Session:


Let’s recap. From each section in Wine Session, we save attributes to the CompanyOnboardingInput model without nesting any data. Then, once the user clicks “Submit” at the end of the Wine Session, the CompanyOnboardingInputSynchronizer service will save the attributes to corresponding fields in ZP models. This way, we have the benefit of modularizing part of onboarding, while making the Wine Session experience fast and seamless.