Thumbtack’s Recurring Plan Journey: How we’re simplifying Home Care

Oliver Zhou
Thumbtack Engineering
6 min readJun 26, 2023
Working with time might haunt your dreams

An Introduction to Thumbtack and Home Care

At Thumbtack, our vision is to be the only app homeowners need to continuously care for their homes. We’re always trying to get to know the user, their home, and their goals better. We’re launching a feature called the Plan Tab, which revamped our legacy Checklist product with a large set of additional features; such as the ability to set up recurring events which improved the way users could keep track of projects they want to complete for their home.

That legacy Checklist allowed users to add Thumbtack generated generic recommendations to their list — visualized here:

The historical “checklist” which didn’t track the concept of recurrence.

However, implementing an updated version of this checklist with the ability to control recurring reminder schedules presented interesting product and technical challenges; especially when taking into account the seasons, the ideal recommended frequency for each type of task etc.

In this blog post, we’ll cover some of the difficulties we overcame when implementing this feature, including data modeling, database schema design, and general best practices while working with time.

This is the finished Plan Tab we ended up with.

A user’s plan with a monthly house cleaning reminder added

The Technical Challenges

With infinite approaches and design choices available, managing time is always tricky.

Check out some interesting pre-reading that influenced the direction we took: Brian Moeskau’s Thoughts on Recurrence and the ​​iCalendar RFC

A simplified view of the pre-existing Todos table from the original Checklist:

This served the basic Checklist well, but would need major work to support our updated Plans, where users will get notifications on a regular basis, similar to how a recurring calendar might behave. Users would also need to be able to mark both individual instances and the overall recurring todo as completed/deleted. Finally, the events and visualizations of these recurring todos needed to be able to have complex rollover rules.

We debated between quite a few options, and I’ll touch on them a bit and discuss our thought process as we worked our way to our solution.

Explorations

  1. Expand and force everything into the Todos Table. Add the necessary columns to be able to keep everything in one place. Add columns related to interval, frequency, nested JSON columns that track the life cycle of the recurring events. These JSON columns would store a series of kv pairs per month for the number of reminders left, and would be continuously updated via a cron job
  2. Todos becomes the parent, spawns new child instances for each expected instance of the recurring event.
  3. Use the existing Todos table to track each instance of a recurring item, and create a new ‘parent’ table with the interval and frequency information. This route would make backwards compatibility a bit easier too, since existing uses of fields would remain the same.
  4. Sunset the existing tables, and start over with idealized new tables based around existing standards for developing calendars.

Eventually, we decided on what we think provides the best experience balancing trade-offs between speed of development, ease of understanding for future developers, and our users. We picked a modified version of exploration 2, which allowed us to keep using the Todos table in place.

We created a “TodosLog” table — however, we would only store user actions into it, such as deletion or completion of an instance, rather than trying to store all possible future actions, dramatically reducing the rows needed. By keeping the todos table, and building on top of it, it also allowed for a reasonable set of backwards compatibility(i.e., we shipped this project on iOS first, and Web still relies on the historical checklist code).

We added these columns to our Todos table.

And added a new table, named TodoLogs, that will track the specific instances / dates that a user has marked as done, successfully initiated, or deleted.

In this way, we only need to track the user interactions, rather than needing to continuously generate new rows in our tables for events that the user might not end up interacting with.

Our service then takes the data in these tables and generates what to show a user on their plan tab.

What the screen for setting recurring reminder frequency looks like

Takeaways

Time is hard to change and modify. Don’t be afraid to take the necessary amount of time to plan out your approach and sketch the interactions out completely, since new interesting corner cases will keep revealing themselves. We had a large team involved with the scoping, design, and implementation, that helped continuously challenge and improve our approach from beginning to end. We would not have implemented a successful and extensible system without the input from a diverse set of engineers, designers, and product managers. Lots of our early explorations came from only an individual’s point of view, missing all the different nuanced ways our users might interact with the system.

Designing complex systems with existing data in place would always benefit from additional feature flag gates, even if it seems relatively “straight-forward” during the scoping phase. Since we were trying to preserve and iterate on top of the existing Checklist experience, migrating existing tables, modifying mutations and queries in a live production environment, we encountered several moments where we caused unintended side effects to existing users. While those might have been unavoidable in the development of any large set of changes, what was avoidable would have been to be extremely conservative with what would be “gated” by feature flags, such that existing behavior could be easily disabled without needing to redeploy a long series of reverts.

Designing recurring systems that track exclusions/deletions ends up creating less extraneous information in your database, and reduces the need to design cron jobs to continuously create new rows as time goes on. Accepting some additional runtime cost to generate desired ranges of potential future valid values rather than expecting all future values to be available in your database will simplify your application.

Acknowledgement

The evolution of the plan tab at Thumbtack is a result of direct & indirect contributions from current and former employees including but not limited to: Jelani Lewis, Yin Li, Anaïs Ziae-Mohseni, Grace Cha, Ryan Christensen-Schwarz, Will Mui, Jacob Mittelstaedt, Alex Karp, Michael Li, Jesse Stauffer, Pietro Montanarella, James Porcelli, Jeff Tabachnick, Alok Irde, Harrison Chun, Divya Mandyam, Kelsey Dunn, Rob Filipp, Erin Hogan, Dan Capo, Mahvish Syed, Mansoor Sayed, Benjamin Sam, Adam Ford, Kirisanth Subramaniam, Mong Zhao, Reuben George, Anthony Robinson, Vaishi Yogendran, Alexandra Goldstein, Danielle Shoshani, and the rest of the extended Home Care teams!

--

--