This analysis follows up on a previous analysis on posting frequency and follower growth. In this analysis we’ll focus on Instagram, with the aim of trying to find a sweet spot for the amount of posts one should share.
What We Found
The findings in this analysis reveal a clear pattern: posting more frequently to Instagram leads to more follower growth and reach.
Posting at least 1-2 times per week is crucial in order to avoid the no-post penalty, and posting 3-5 times per week is likely a sweet spot for most accounts. It provides substantial growth benefits without requiring an excessive amount of effort.
That being said, bigger gains in follower growth are available for those that are able post more in a sustainable way.The data shows that Instagram increasingly rewards higher posting frequencies, with the jump from 6-9 to 10+ posts per week showing the largest incremental gain.
However, for most accounts, posting 3-5 times per week is probably sufficient. This frequency more than doubles your follower growth compared to posting just once or twice, while still being manageable enough to maintain content quality.
The reach findings are compelling because they show that more frequent posting could result in more reach per post. This is further evidence that Instagram’s algorithm rewards consistent, active accounts by amplifying their content to larger audiences. However, there are diminishing returns in terms of reach per post.
As always, it’s important to remember that sustainable consistency beats unsustainable volume.
Data Collection
The SQL query below returns approximately 2.1 million records that include the number of posts each channel creates per week as well as their weekly follower growth. Approximately 102k profiles are included in this dataset.
Code
sql <-"with profile_activity_windows as ( -- Find the first and last week of posting activity for each Instagram profile select service_id as channel_id , 'instagram' as service , min(timestamp_trunc(created_at, week)) as first_post_week , max(timestamp_trunc(created_at, week)) as last_post_week from dbt_buffer.analyze_instagram_analytics_user_media_totals where created_at >= timestamp_sub(current_timestamp, interval 365 day) group by 1, 2),weekly_instagram_posts as ( select service_id as channel_id , 'instagram' as service , timestamp_trunc(created_at, week) as week , count(distinct service_update_id) as posts , sum(impressions) as total_impressions , sum(reach) as total_reach , sum(likes) as total_likes , sum(comments) as total_comments , sum(shares) as total_shares , sum(comments + likes + shares) as total_engagements , sum(plays) as total_plays from dbt_buffer.analyze_instagram_analytics_user_media_totals where created_at >= timestamp_sub(current_timestamp, interval 365 day) group by 1,2,3),weekly_instagram_followers_base as ( select service_id as channel_id , 'instagram' as service , timestamp_trunc(checked_at, week) as week , max(followers_count) as week_end_followers from dbt_buffer.analyze_instagram_analytics_user_daily_totals where checked_at >= timestamp_sub(current_timestamp, interval 365 day) group by 1,2,3),weekly_instagram_followers as ( select channel_id , service , week , week_end_followers , lag(week_end_followers) over (partition by channel_id order by week) as prev_week_followers from weekly_instagram_followers_base),combined_data as ( select f.channel_id , f.service , f.week , coalesce(p.posts, 0) as posts , f.week_end_followers , f.prev_week_followers , case when f.prev_week_followers > 0 then ((f.week_end_followers - f.prev_week_followers) * 100.0 / f.prev_week_followers) else null end as follower_growth_pct , f.week_end_followers - f.prev_week_followers as follower_growth_absolute , coalesce(p.total_impressions, 0) as total_impressions , coalesce(p.total_reach, 0) as total_reach , coalesce(p.total_likes, 0) as total_likes , coalesce(p.total_comments, 0) as total_comments , coalesce(p.total_shares, 0) as total_shares , coalesce(p.total_engagements, 0) as total_engagements , coalesce(p.total_plays, 0) as total_plays from weekly_instagram_followers f left join weekly_instagram_posts p on f.channel_id = p.channel_id and f.week = p.week -- Only include weeks within the active posting window inner join profile_activity_windows paw on f.channel_id = paw.channel_id and f.service = paw.service and f.week >= paw.first_post_week and f.week <= paw.last_post_week)select service , channel_id , week , posts , week_end_followers , prev_week_followers , follower_growth_pct , follower_growth_absolute , total_impressions , total_reach , total_likes , total_comments , total_shares , total_engagements , total_plays , case when posts = 0 then 'No Posts' when posts between 1 and 2 then '1-2 Posts' when posts between 3 and 5 then '3-5 Posts' when posts between 6 and 10 then '6-10 Posts' when posts > 10 then '10+ Posts' end as posting_frequency_binfrom combined_datawhere prev_week_followers is not nullorder by channel_id, week"# get data from BigQueryposts <-bq_query(sql = sql)
We’ll group the number of posts sent in a given week into five separate buckets.
Next we’ll calculate the average follower growth rate by the number of posts sent in a given week. The plot below shows a strong positive correlation, which is what we might expect.
This relationship is nice to see, but it may not tell us the full story. We need to control for differences in accounts’ natural growth rates. As an example, imagine that the fastest growing accounts naturally post more frequently than accounts with more modest growth.
For example, imagine a fast growth account that gains 1,000 followers per week and posts 20 times, while a smaller gains 10 followers per week and only posts twice. If we simply average these together, we might conclude that posting 20 times leads to fast growth. However, the larger account would likely gain a higher number of followers even if they posted less frequently.
Without accounting for these inherent differences in accounts, we can’t tell whether frequent posting causes growth, or whether naturally fast-growing accounts just happen to post more. That’s why we need to employ statistical methods that compare each account against itself, essentially asking the question “when this specific account posts more versus less, how does their growth change?”
How We Account for Inherent Account Differences
We use two complementary approaches to ensure you’re measuring the true effect of posting frequency:
Z-Score Analysis Instead of comparing different channels to each other, which could result in unfair comparisons because some channels naturally grow faster, we compare each channel to its own typical performance. The Z-score tells us h much better or worse did a channel performed compared to its own average.
For example, if Channel A typically grows at 2% per week and Channel B typically grows at 0.5% per week, a 1% growth week would be below average for Channel A, represented by a negative Z-score, and above average for channel B, represented by a positive Z-score.
Fixed Effects Regression This statistical technique takes the control even further. It asks the question “when the same channel posts more in some weeks and less in others, how does that affect their growth?”
This method essentially compares each channel’s high posting weeks to their own low-posting weeks. This should remove any confusion about whether successful accounts just happen to post more because we calculate the effect within the same account.
Z-Score Analysis
This is the approach to calculate a Z-score, which we’ll do for each channel:
Calculate the mean and standard deviation of follower growth rates across all weeks
Calculate a Z-score for each week. Z = (follower growth - average growth) ÷ standard deviation.
For each channel and week, the Z-score tells us how many standard deviations above or below average a channel performed in a given week:
Z = 0: Typical performance for this channel Z = +1: Strong positive week (better than ~84% of weeks) Z = +2: Exceptional week (better than ~97% of weeks) Z = -1: Poor week (worse than ~84% of weeks)
Once we’ve calculated Z-scores for each channel, we average them for all channels.
Code
# calculate channel-specific mean and standard deviationchannel_stats <- posts %>%group_by(channel_id) %>%summarise(mean_growth =mean(follower_growth_pct, na.rm =TRUE),sd_growth =sd(follower_growth_pct, na.rm =TRUE),n_weeks =n() ) %>%# only keep channels with sufficient data and variationfilter(n_weeks >=3, sd_growth >0)# merge back and calculate Z-scoresposts_with_z <- posts %>%inner_join(channel_stats, by ="channel_id") %>%mutate(z_score = (follower_growth_pct - mean_growth) / sd_growth)# calculate summary statisticsposts_with_z %>%group_by(posting_frequency_bin) %>%summarise(n_observations =n(),mean_z_score =mean(z_score, na.rm =TRUE),median_z_score =median(z_score, na.rm =TRUE) )
This data suggests that there is a strong positive relationship between posting frequency and follower growth on Instagram. Channels that post more frequently consistently gain more followers relative to their baseline.
It also suggests that there is a cost to not posting. Channels that don’t post at all significantly under-perform their baseline growth rates. Even posting once or twice a week results in a significant increase in follower growth compared to weeks with no posts.
Fixed Effects Regression Model
A fixed effects regression model compares the same channel against itself over time, rather than comparing different channels to each other.
The model essentially creates a separate baseline for each channel, then measures how posting frequency affects growth relative to that channel’s own average performance.
The key advantage is that we can make stronger causal claims about posting frequency. When we see that the same channel grows faster during weeks when it posts more, we can be more confident that posting is actually driving the growth rather than just being correlated with it.
Code
# fit fixed effects modelfe_model <-feols(follower_growth_pct ~ posting_frequency_bin | channel_id, data = posts, cluster ="channel_id")# summarise modelsummary(fe_model)
The fixed effects model confirms a strong, statistically significant relationship between posting frequency and follower growth on Instagram. All posting frequency categories show positive and highly significant effects compared to weeks with no posts.
This effect grows progressively larger with more frequent posting. Posting 3-5 times per week yields a 0.26 percentage point increase from 1-2 posts, 6-9 posts delivers more gains, and 10+ posts provides the largest follower boost of all.
The increasing incremental gains suggest that Instagram’s algorithm could reward highly active accounts. However, it’s worth mentioning that the effort required to maintain 10 or more posts per week could be substantial.
For most accounts, the 3-5 post range likely represents the sweet spot where meaningful growth benefits can be achieved with manageable effort.
Either way, these results provide pretty clear evidence that consistent, frequent posting is a major driver of follower growth.
Posting Frequency and Reach
Next we’ll look at how posting frequency affects reach. We’ll use a similar approach to what we did with follower growth.
Code
# calculate median reach by posting frequency binposts %>%group_by(posting_frequency_bin) %>%summarise(n_observations =n(),avg_total_reach =mean(total_reach, na.rm =TRUE),median_total_reach =median(total_reach, na.rm =TRUE),avg_reach_per_post =mean(total_reach /pmax(posts, 1), na.rm =TRUE) ) %>%ggplot(aes(x = posting_frequency_bin, y = median_total_reach)) +geom_col() +scale_y_continuous(labels = comma) +labs(x ="Weekly Posts Shared",y ="Median Total Reach",title ="Total Weekly Reach by Posting Frequency",subtitle ="More posts generally lead to higher total reach" )
Naturally, accounts that post more get more total reach. Next, let’s looks at reach per post.
Code
# calculate median reach by posting frequency binposts %>%group_by(posting_frequency_bin) %>%summarise(n_observations =n(),avg_total_reach =mean(total_reach, na.rm =TRUE),median_total_reach =median(total_reach, na.rm =TRUE),avg_reach_per_post =mean(total_reach /pmax(posts, 1), na.rm =TRUE),med_reach_per_post =median(total_reach /pmax(posts, 1), na.rm =TRUE) ) %>%ggplot(aes(x = posting_frequency_bin, y = med_reach_per_post)) +geom_col() +scale_y_continuous(labels = comma) +labs(x ="Weekly Posts Shared",y ="Median Reach Per Post",title ="Reach Per Post by Posting Frequency",subtitle ="Accounts that post more tend to get more reach per post" )
Now let’s apply the same fixed effects approach to control for channel differences.
Code
# create a log-transformed version of reach for the regressionposts_reach <- posts %>%mutate(reach_per_post =log(total_reach /pmax(posts, 1) +1))# fit fixed effects model for total reachfe_model_reach <-feols(reach_per_post ~ posting_frequency_bin | channel_id, data = posts_reach, cluster ="channel_id")# summary of the modelsummary(fe_model_reach)
Each step up in posting frequency delivers substantial percentage gains in reach per post, with the largest jump occurring when moving from 1-2 to 3-5 posts. Even at higher frequencies, there are still meaningful 20-25% incremental gains for posting more frequently, however the returns are diminishing.
Moving from 1-2 to 3-5 posts yields an additional ~56% gain in reach per post
Moving from 3-5 to 6-9 posts provides another ~26% gain
Moving from 6-9 to 10+ posts delivers an additional ~24% gain