Growth

Last updated •

Share count, not follower count

My first analysis of 13 months of my own Instagram data produced a confident, wrong story, because it compared reels measured at different ages. This is the measurement system I built to fix that, what survived, and what one account can and cannot tell you. · 10 min read

I grew a personal account on Instagram, @imshogo.k, from zero to 30,000 followers in motivation, fitness and self-growth. Several reels passed a million views. In the summer of 2026 a normal reel was reaching fewer people than it had a year earlier, when the account was a tenth of the size. I wanted to know why, with numbers rather than a feeling.

The first analysis gave me a clean answer. It was wrong, and the reason it was wrong is the most useful thing in this post.

335

reels in 13 monthly cohorts, August 2025 to August 2026

8

fixed ages at which every reel is now measured

27 days

until a reel stops gaining reach, measured on this account

Where the data comes from

The Instagram app shows insights per post, but there is no export and no history. The official API has the same gap: it returns the current cumulative value of each metric and nothing about how it got there. A reading you did not take is gone.

So I built a small private dashboard: Next.js on Vercel, Postgres on Supabase, and Meta's official Instagram API (Graph API v25.0) behind an OAuth login. No scraping and no password login. An hourly cron job lists recent media, decides which ones are due for a reading, and stores each reading as one row with its capture time and the metrics as JSON. For reels that is views, reach, likes, comments, shares, saves, reposts and watch time. There is no per-reel follow count in the API, so the dashboard shows that as unavailable instead of inventing it.

Requests run one at a time under three fixed caps: 100 per run, 150 per hour, 1,000 per day. Meta's own allowance scales with the account's impressions, so it shrinks exactly when the account is doing badly; my caps do not depend on it. A 5xx response is retried twice with backoff. A rate-limit response stops the run.

The first answer, and why it was wrong

On August 24 I grouped reels by month and compared medians. The story wrote itself: median reach down from above 10,000 to 1,366, the best reel of the month down by a factor of 300, and no reel above 10,000 reach since July 4.

Then I tried to measure how fast a reel grows, and the query returned zero rows. No reel had both an early reading and a late one. The back catalogue had been read once, between 27 and 358 days after posting. New reels were read only during their first eight days. I had been comparing 313 days of accumulation against four and calling the difference a decline.

lib/meta/snapshot.ts
/** * A reel keeps accumulating reach for weeks. That means a raw reach number is * meaningless without the age it was taken at: 674,894 measured at 101 days * and 2,237 measured at 4 days are not two data points on a trend, they are * two different questions. */export const MILESTONES: readonly Milestone[] = [  { label: "1h", from: 0, to: 3 * HOUR },  { label: "6h", from: 3 * HOUR, to: 15 * HOUR },  { label: "24h", from: 15 * HOUR, to: 48 * HOUR },  { label: "72h", from: 48 * HOUR, to: 120 * HOUR },  { label: "7d", from: 120 * HOUR, to: 14 * DAY },  { label: "30d", from: 14 * DAY, to: 60 * DAY },  { label: "90d", from: 60 * DAY, to: 180 * DAY },  // Past six months a reel is done moving. One reading, so the whole back  // catalogue has a comparable "this is where it finished" number.  { label: "final", from: 180 * DAY, to: Number.POSITIVE_INFINITY },];
Trimmed: the file's header comment (first sentences) is shown directly above the constant, which sits further down the file. Windows are in hours and do not overlap. They are wide because the cron is hourly and a run can be cut short by the budget; a window that is missed cannot be recovered.

Fixing the measurement before the analysis

The old planner read recent reels often and re-read anything stale every few days, which produced readings at arbitrary ages and re-read the archive forever. I replaced both rules with one: a reel is due when it sits inside a milestone window it has no reading for. Every reel now lands on the same set of ages. A reel costs at most nine requests in its lifetime (the first reading plus eight milestones), and a test walks two years of hourly runs to pin that. When the budget cuts a run short, targets are ordered by how soon their window closes, so a reading that can wait a month never displaces one that exists for three hours.

I then re-read every reel on the same day and measured the growth curve directly. Between day 1.3 and day 21.3 reach grew 3.19 times (26 reels). Between day 26.9 and day 56.4 it grew 1.01 times (23 reels). Between day 320 and day 349, 1.00 times (482 reels). On this account a reel is finished after about 27 days. Months older than that can be compared with each other. August 2026, at 11 days, could not be judged at all.

supabase/migrations/0012_sync_diagnostics.sql
create or replace view public.reel_day7 asselect distinct on (m.id)  m.id                                   as media_id,  m.posted_at,  s.captured_at,  round((extract(epoch from (s.captured_at - m.posted_at)) / 86400)::numeric, 2)                                         as measured_at_days,  (s.metrics->>'reach')::numeric         as reach,  (s.metrics->>'shares')::numeric        as shares,  (s.metrics->>'ig_reels_avg_watch_time')::numeric / 1000 as watch_secondsfrom public.instagram_media mjoin public.media_insight_snapshots s on s.media_id = m.idwhere m.raw_metadata->>'media_product_type' = 'REELS'  and m.posted_at is not null  -- Only readings near the seven-day mark qualify. A reel measured at four  -- days has not finished; one measured at ninety is a different question.  and extract(epoch from (s.captured_at - m.posted_at)) / 86400 between 4 and 14order by  m.id,  abs(extract(epoch from (s.captured_at - m.posted_at)) / 86400 - 7);
The comparable series: one reading per reel, the one closest to day seven. Trimmed; the full view also exposes views, saves, comments, likes, caption and permalink. A reel with no reading in the band is left out, not substituted.

What survived

With every month read at 39 days or older (analysis date August 25, 2026, source Meta Graph API), the monthly picture looked like this. Reach is the median per reel. A breakout is a reel above 10,000 reach.

  • August 2025: 34 reels, median reach 3,421, 45.4 shares per reel, 3% breakouts
  • September 2025: 28 reels, median reach 10,564, 8,677 shares per reel, 57% breakouts
  • November 2025: 30 reels, median reach 15,243, 3,911 shares per reel, 63% breakouts
  • March 2026: 21 reels, median reach 5,935, 3,554 shares per reel, 38% breakouts
  • May 2026: 26 reels, median reach 4,063, 366 shares per reel, 27% breakouts
  • July 2026: 21 reels, median reach 1,700, 15.9 shares per reel, 5% breakouts

The decline was real, but its shape was different from the first story. From March to July, shares per reel went 3,554, 987, 366, 122, 15.9. Each month kept between 13% and 37% of the month before. That is a slope, not a cliff, and nothing happened on July 4. The 300-times collapse was gone.

Three findings held up on comparable data:

  • **Followers did not raise the floor.** In August 2025 the account had about 3,000 followers and a non-breakout reel reached 3,397 people. In July 2026 it had about 28,600 followers and a non-breakout reel reached 1,694 (checked August 25, 2026). Reach had always come from recommendation, not from the follower list
  • **Shares tracked reach; watch time did not.** Across 20 reels from August 2026, all read at 0 to 5 days old, share count correlated with reach at r = 0.57 and average watch time at r = 0.25
  • **Shares moved first.** In weekly cohorts all read at day five, shares per reel jumped in the week of August 17 (4.7 to 33.7) while median reach stayed flat. Reach moved two weeks later (3,934 in the week of August 31, 14,276 the week after). The follower count, which had fallen for most of August, bottomed at 28,335 on September 2 and was 30,212 on September 18
lib/report.ts
/** Days after posting at which a reel has stopped accumulating. */export const MATURE_DAYS = 27;/** Reach above which a reel counts as having broken out. */export const BREAKOUT_REACH = 10_000;// inside monthlyRollup(), for each month's reels:      const ages = list.map(        (r) =>          (new Date(r.capturedAt).getTime() - new Date(r.postedAt).getTime()) /          86_400_000      );      const meanAge = ages.reduce((s, v) => s + v, 0) / ages.length;      return {        month,        reels: list.length,        measuredAtDays: round(meanAge),        mature: meanAge >= MATURE_DAYS,        medianReach: round(median(numbers(list.map((r) => r.reach)))),        sharesPerReel: round(total(list.map((r) => r.shares)) / list.length, 1),        breakouts: list.filter((r) => (r.reach ?? 0) > BREAKOUT_REACH).length,      };
Every month carries the age it was read at and a mature flag. An unfinished month is still reported, but labelled, so it cannot be silently compared with a finished one.

The checks

  • **Mixed time windows.** Every number travels with the age it was read at. Reach comparisons use the day-seven view or mature months only
  • **Rates that rise because the denominator shrank.** In June 2026 the share rate was 0.88% of reach, the best in twelve months, which looked like good news. In absolute terms it was about 23 shares per ordinary reel, the same as a year earlier, and July fell to about 6.6. I track the count, not the rate
  • **Mean against median.** Shares per reel is a mean and reach is a median. One reel with a lot of shares can lift the mean while the median reach is decided by the other five. That would produce the same "shares moved first" table without shares leading anything. The check is the weekly median of shares. I have not run it yet, so I treat the lead as an observation, not a rule
  • **Small samples.** Tag comparisons show no aggregate below three reels, and the top-against-bottom comparison prints the average age of each half
  • **Looking only at winners.** Nine of the top 15 reels from the strong months (September 2025 to February 2026) mentioned the season or the calendar, against two of the bottom 15. I wrote a strategy around that. Then I looked at the losers: five calendar reels from the weak months reached between 1,524 and 39,102, and of the 16 reels that broke out between April and July only one mentioned the calendar. The hypothesis was dead
  • **Explaining with the columns I happen to have.** Two reels with nearly the same caption reached 2,240 and 26,077. What decides a reel is in the video: the first two seconds, the audio, the drop-off curve. None of that is in my database
  • **A second reader.** Each conclusion was reviewed by a separate Claude session that had not produced it. Four times I proposed that something happened on a specific date. Four times no such date existed

What broke while running it

  • For a month every sync run reported success with 100 media and zero insights. Nothing was being measured and nothing said so. Runs now count the targets that were due and the ones Meta answered with nothing, and an empty answer downgrades the run to partial
  • Five reels from December 2024 return no insights at all. They sat in the one window that never closes and were asked again every hour: 360 requests a day, a third of the daily cap. I recorded the failed attempt as the reading, but only for that last window. A September feed post then did the same inside the 30-day window and turned all 24 daily runs partial, which made the status useless again. The rule now covers every window open longer than a day
  • The hourly sync has to stay on when nobody is looking. The follower turning point on September 2 is only known because something wrote the number down every hour through August

Limits

This is one account, observed, with no control group. A correlation of 0.57 on 20 reels explains about a third of the variance and says nothing about direction. Those 20 reels were between 0 and 5 days old, not at one fixed age, and shares and reach both grow with age. The jump in shares and reach in September coincided with reels announcing a seasonal challenge that my audience takes part in, in 2025 and again in 2026. The same cause could have moved both numbers at different speeds. Two Septembers is two observations.

I used to write that share count is the only reliable leading indicator of reach. What the data supports is narrower: on this account, absolute shares are the best early signal I have, and follower count did not predict reach.

What changed

I compare reels only at the same age. I read absolute shares per reel first, reach second, and followers as a running total, not a goal. And I check the measurement before I believe the analysis.

Why this is on a developer's portfolio

Most developers can build a product but can't get anyone to see it. Most creators can get attention but can't build anything. I do both, and I measure both. The habit is the same on either side: make sure the two things you are comparing were measured the same way. In a booking funnel that meant an event on every step. Here it meant a fixed age on every reading.

Read more

Have something that needs to be built?

Tell me the workflow and what a good outcome looks like. I'll reply with a clear next step.

  • 1,200+ clients handled
  • Published at Mercari
  • 30K followers