Calculating an Activity Streak in-app 🫠

I want to dynamically track users’ ā€˜streaks’ (specifcally, the current number of consecutive days in which they’ve performed any of a few different actions in-app; I’ll call them ā€˜logs’ for simplicty).

I’ve tried a number of solutions, and nothing is reliable, so far.

Currently, I am using a recursive backend workflow (calculate-streak) that checks one day at a time, moving backward from today.

  • If a log exists for the day, it continues checking the previous day.

  • If no log exists, it stops and saves the streak count.

  • A key variable here is that a user who signed up 10 days ago may have a current streak of 2, but is allowed to go back in time and log for previous days to recover their streak. (it’s a food logging app that allows you to log a meal with a photo, so it’s certainly conceivable that user would have a photo from a previous day).

My hope was to avoid doing complex list math and/or client-side looping.

It is triggered on page load AND when a new ā€˜log’ is created.

Parameter Type Purpose
user User whose streak we’re counting
day date today at midnight (used as a fixed ā€œanchorā€)
offset number how many days back we’re checking (starts at 0)
current-streak number how many consecutive days found so far (starts at 0)
max-days number safety limit for recursion (e.g., 365)

There are two mutually exclusive steps in the workflow.

Branch A - Log Exists:

  • Condition: Search for NutriTrackers:count > 0 and offset < max-days

  • Action: Schedule calculate-streak again with:

    • offset = offset + 1

    • current-streak = current-streak + 1

Keeps counting backward.

Branch B - No Log Found:

  • Condition: Search for NutriTrackers:count = 0

  • Action: Make changes to User:

    • streak = current-streak

Stops recursion and saves the total streak.


The issue is basically that this almost never works as expected. Very surprisingly sometimes it will. As I mentioned, I’ve tried other approaches, but nothing has worked yet.

If you see an obvious fatal flaw in my logic above, please advise. Alternatively, if you can think of a better way, please share. This feels like it ought to be MUCH simpler than this!

Help, advice, anything at all is greatly appreciated :folded_hands:t2:

The fire icon is where I want to display the users’ streak. So simple, right? :slight_smile:

Hi @subscriptions4 I think this is a really fun idea

I expect you are planning to use the streak in some sort of backend process to follow, such as verification, notification, prize awarding?

Client-side looping is a lot cheaper than backend looping.

In general try to avoid triggering backend workflows on page load - if the page is slow to load, the user is tempted to hit refresh …

I’d have both a backend calculation and a frontend calculation, using the backend one only where it is shared with other users, presumably less frequently than displaying for the current user.

Good that the calculation takes into considering the time component of the date. Take care that the timezone of the workflow matches the timezone of all the logs being searched. For example a user enters logs while traveling, and their phone adapts to local timezones.

Optimising the calculation … I’m guessing a lot of people will have different ideas for this :smiley:

How about something like,

  • search for logs back to max-days in past → list A
  • filter list A to logs that break the streak, save the date of the most current
  • filter list A to logs later than the most current streak breaker, count list items

As I haven’t tested it, I’m sure there is a flaw here haha

Edit flaw found, list A would have gaps; so instead:

  • create a list of dates back to max-days in past → list B
  • search for logs back to max-days in past → list C
  • filter list B to dates not in list C i.e. dates that break the streak, save the date of the most current
  • filter list C to logs later than the most current streak breaker, count list items

Note, list B could be a list of numbers instead of list of dates.

Edit - may be worth checking the forum’s ā€œrelated postsā€ :

Current Streak, Best streak logic help (I have a few ideas already…)
Streak count for consecutive dates in a Repeating group
How to create a ā€˜Streak’ i.e. number of daily consecutive entries?
How to calculate Duolingo-like streaks?
Need help with streak calculation functionality

1 Like

@subscriptions4 It seems like there is going to be a lot of consumption of WUs for this regardless of the approach taken.

I feel like maybe you can run a recurring daily event to check the logs, see if the user logged something (ie: performed their activity for the streak) or not…if they did, do nothing, if they did not, LOG a data entry with a simple field of yes/no that is labeled something like ā€˜streak’, where if they did not do something that field is set to ā€˜no’ and in the database structure make the default value yes, so that every time the user is doing something for their streak, that field is already set to yes.

Then when you want to show their streak, just do a very simple search that uses the constraint of the field ā€˜streak’ is equal to yes, then extrapolate from that set of values, the dates associated with the built in field of ā€˜created_date’ and determine the streaks.

This will provide you with more opportunity to give insights into past streaks as well, as well as filterable features for the user to see date ranges and streaks achieved within those filter date ranges…list could go on in terms of data insight and features that can be built around it with what I perceive as a simple twist on how to structure the data type of ā€˜streak log’ and how to create and why to create those data entries in the DB.

I agree with @boston85719 that @subscriptions4 ā€˜s and my first attempt aren’t great for WU. Having another go at a solution :smiley:

Additional data structure: new table streak with two fields start date, user

Logical data relation:

day1         day2       day3       day4        today         
log(s)       log(s)                log(s)      log(s)
streak start                       start

Each streak to have a streak start entry of midnight of matching day

View logic:

The idea here is a couple of low-WU searches

  • Search for the latest streak start, if none then no current streak
  • Search for log(s) matching yesterday, if none then no current streak
  • current midnight - latest streak start → difference in days → display if 2 or more

Streak start creation logic:

  • Create new log for date A
  • Search for streak start at date A + 1 day, if found then delete streak start A + 1
  • Search for existing log(s) at date A - 1, if not found then
    • Search for streak start A, if not found then create streak start A

Once-off batch to create historical streak starts for existing logs:

I’ll think about this more if OP is interested in this method :stuck_out_tongue:

Hello @subscriptions4

You don’t need to recompute iteratively each time. Add a few auxiliary fields and split logic into two focused methods: check-streak and log-action. Keep it idempotent and cheap.

User fields:

last_streak_action_date // date of last qualifying action
last_streak_check_date // date when streak last checked
current_streak

PSEUDOCODE for ā€˜check-streak’:
if current_streak is 0 or last_streak_check_date is today – terminate early
// as streak has been checked already or it's 0

if last_streak_action_date is LESS THAN yesterday then current_streak = 0
last_streak_check_date = Current Date/Time
return current_streak

  • Called on page_load and by a daily recurring job.
  • Does not increment the streak. Only validates and resets if necessary.

and PSEUDOCODE for ā€˜log-action’:
if last_streak_check_date is NOT today then call 'check-streak'
if last_streak_action_date is today – terminate early

if last_streak_action_date is NOT yesterday then current_streak = 0
current_streak += 1

last_streak_action_date = Current Date/Time
last_streak_check_date = Current Date/Time

// Optional: celebrate milestones
if current_streak is [3, 7, 14, 30, 100...] then notify...

return current_streak

  • Call when a new qualifying ā€œlogā€ is created.

  • Increments at most once per calendar day. If the last action was not yesterday, reset, then start at 1.

As a bonus, you can also incorporate the triggering of an email/notification when a streak is lost or incorporate a grace period and send warning emails.

And, yes, you can have a daily recurring workflow that executes ā€˜check-streak’ to reset for those who don’t interact at all with the app.

1 Like

And you almost must add a ā€œdays of forgivenessā€ rule.

When Bubble has major outages or downtime, users shouldn’t lose their streaks unfairly.

Just store something like:
forgiveness_days //list of dates

and before resetting in ā€˜check-streak’, skip reset if yesterday is in that list.

1 Like