Free tool · No sign-up

See who doesn’t follow you back

This scans the accounts you follow on Instagram and lists the ones that don’t follow you back. No password, no app to install, and your data never leaves your browser.

Read the file first
  • Runs in your browser
  • Nothing is uploaded
  • CSV export included
instagram-unfollowers.js
/*! * Instagram Unfollowers - see who does not follow you back. * https://likesforinstagram.com/instagram-unfollowers * * Read-only. It reads YOUR own followers/following lists using the session you * are already logged into, compares them in memory, and shows the result in a * panel on the page. * * It never asks for your password, never unfollows anyone for you, and never * sends a single byte to likesforinstagram.com or anywhere else. Open your * DevTools Network tab while it runs and check for yourself. * * Not affiliated with, endorsed by, or sponsored by Instagram or Meta. */(() => {  'use strict';   /* ---------------------------------------------------------------- config */   const APP_ID = 'lfi-unfollowers-app';  const IG_APP_ID = '936619743392459';  const PAGE_SIZE = 50;   // Randomised pause between pages. Instagram rate-limits aggressive scans, and  // a soft-block is a lot more annoying than waiting an extra minute.  const DELAY_MIN_MS = 800;  const DELAY_MAX_MS = 1500;  const MAX_RETRIES = 4;   /* ------------------------------------------------------------- preflight */   if (!location.hostname.endsWith('instagram.com')) {    console.error(      '%cRun this on instagram.com',      'font-weight:bold;font-size:14px',      '\nOpen https://www.instagram.com, make sure you are logged in, then paste the script again.'    );    return;  }   const viewerId = document.cookie.match(/(?:^|;\s*)ds_user_id=([^;]+)/)?.[1];   if (!viewerId) {    console.error(      '%cYou are not logged in',      'font-weight:bold;font-size:14px',      '\nLog into instagram.com in this tab, then paste the script again.'    );    return;  }   // Pasting a second time replaces the old panel instead of stacking copies.  document.getElementById(APP_ID)?.remove();   /* ----------------------------------------------------------------- state */   const state = {    running: false,    stopped: false,    following: new Map(),    followers: new Set(),    results: [],    filter: '',    phase: 'idle',    scanned: 0,    total: 0,    error: '',  };   /* ----------------------------------------------------------- ig fetching */   const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));  const jitter = () =>    DELAY_MIN_MS + Math.floor(Math.random() * (DELAY_MAX_MS - DELAY_MIN_MS));   async function igFetch(url) {    for (let attempt = 0; attempt <= MAX_RETRIES; attempt += 1) {      if (state.stopped) throw new Error('stopped');       const response = await fetch(url, {        credentials: 'include',        headers: {          'x-ig-app-id': IG_APP_ID,          'x-requested-with': 'XMLHttpRequest',        },      });       if (response.ok) return response.json();       // 429 = too many requests, 560 = Instagram's own throttle response.      if (response.status === 429 || response.status === 560) {        const backoff = 5000 * 2 ** attempt;        setStatus(`Rate limited. Waiting ${Math.round(backoff / 1000)}s...`);        await sleep(backoff);        continue;      }       if (response.status === 401 || response.status === 403) {        throw new Error('Your session expired. Reload instagram.com and try again.');      }       throw new Error(`Instagram returned ${response.status}.`);    }     throw new Error('Instagram kept rate limiting the request. Try again in a few minutes.');  }   // Both endpoints share the same cursor-paginated shape.  async function* walkFriendships(kind) {    let maxId = '';     for (;;) {      const params = new URLSearchParams({ count: String(PAGE_SIZE) });      if (maxId) params.set('max_id', maxId);       const data = await igFetch(        `/api/v1/friendships/${viewerId}/${kind}/?${params}`      );       const users = data.users ?? [];      if (users.length) yield users;       maxId = data.next_max_id ? String(data.next_max_id) : '';      if (!maxId || !users.length) return;       await sleep(jitter());    }  }   async function getSelfCounts() {    try {      const data = await igFetch(`/api/v1/users/${viewerId}/info/`);      return {        following: data?.user?.following_count ?? 0,        followers: data?.user?.follower_count ?? 0,      };    } catch {      return { following: 0, followers: 0 };    }  }   /* ------------------------------------------------------------------ scan */   async function scan() {    state.running = true;    state.stopped = false;    state.error = '';    state.following.clear();    state.followers.clear();    state.results = [];    state.scanned = 0;    render();     try {      const counts = await getSelfCounts();       state.phase = 'following';      state.total = counts.following;      render();       for await (const users of walkFriendships('following')) {        for (const user of users) {          state.following.set(String(user.pk), {            id: String(user.pk),            username: user.username,            fullName: user.full_name || '',            avatar: user.profile_pic_url || '',            isVerified: Boolean(user.is_verified),            isPrivate: Boolean(user.is_private),          });        }        state.scanned = state.following.size;        render();      }       state.phase = 'followers';      state.total = counts.followers;      state.scanned = 0;      render();       for await (const users of walkFriendships('followers')) {        for (const user of users) state.followers.add(String(user.pk));        state.scanned = state.followers.size;        render();      }       // The whole point: everyone you follow who does not follow you back.      state.results = [...state.following.values()]        .filter((user) => !state.followers.has(user.id))        .sort((a, b) => a.username.localeCompare(b.username));       state.phase = 'done';    } catch (error) {      state.phase = state.stopped ? 'stopped' : 'error';      if (!state.stopped) state.error = error.message || String(error);       // Partial data is still useful - show what we managed to collect.      if (state.stopped && state.followers.size) {        state.results = [...state.following.values()]          .filter((user) => !state.followers.has(user.id))          .sort((a, b) => a.username.localeCompare(b.username));      }    } finally {      state.running = false;      render();    }  }   /* --------------------------------------------------------------- exports */   function copyUsernames() {    const text = visibleResults().map((user) => '@' + user.username).join('\n');    navigator.clipboard.writeText(text).then(      () => flash('Copied ' + visibleResults().length + ' usernames'),      () => flash('Could not access the clipboard')    );  }   function exportCsv() {    const escape = (value) => '"' + String(value).replace(/"/g, '""') + '"';    const rows = [      ['username', 'full_name', 'private', 'verified', 'profile_url'],      ...visibleResults().map((user) => [        user.username,        user.fullName,        user.isPrivate ? 'yes' : 'no',        user.isVerified ? 'yes' : 'no',        'https://www.instagram.com/' + user.username + '/',      ]),    ];     const blob = new Blob([rows.map((row) => row.map(escape).join(',')).join('\r\n')], {      type: 'text/csv;charset=utf-8',    });     const url = URL.createObjectURL(blob);    const link = document.createElement('a');    link.href = url;    link.download = 'instagram-non-followers.csv';    link.click();    URL.revokeObjectURL(url);  }   /* ---------------------------------------------------------------- render */   const host = document.createElement('div');  host.id = APP_ID;  const shadow = host.attachShadow({ mode: 'open' });  document.body.appendChild(host);   shadow.innerHTML = `    <style>      :host { all: initial; }      * { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }      .panel {        position: fixed; top: 24px; right: 24px; z-index: 2147483647;        width: 380px; max-width: calc(100vw - 32px);        max-height: calc(100vh - 48px); display: flex; flex-direction: column;        background: #1E1A24; color: #fff; border-radius: 16px;        border: 1px solid rgba(255,255,255,.12);        box-shadow: 0 24px 60px rgba(0,0,0,.45); overflow: hidden;      }      .head { display:flex; align-items:center; gap:8px; padding:14px 16px; border-bottom:1px solid rgba(255,255,255,.1); }      .dot { width:8px; height:8px; border-radius:50%; background:#F472B6; flex:none; }      .title { font-size:14px; font-weight:700; flex:1; }      .x { background:none; border:none; color:rgba(255,255,255,.6); font-size:18px; line-height:1; cursor:pointer; padding:2px 4px; }      .x:hover { color:#fff; }      .body { padding:16px; overflow-y:auto; }      .status { font-size:13px; color:rgba(255,255,255,.7); margin-bottom:12px; }      .bar { height:6px; border-radius:999px; background:rgba(255,255,255,.12); overflow:hidden; margin-bottom:14px; }      .fill { height:100%; background:#F472B6; border-radius:999px; transition:width .25s ease; }      .row { display:flex; gap:8px; margin-bottom:12px; }      button.act {        flex:1; padding:9px 12px; border-radius:10px; border:1px solid rgba(255,255,255,.16);        background:rgba(255,255,255,.06); color:#fff; font-size:13px; font-weight:600; cursor:pointer;      }      button.act:hover:not(:disabled) { background:rgba(255,255,255,.12); }      button.act:disabled { opacity:.4; cursor:not-allowed; }      button.primary { background:#F472B6; border-color:#F472B6; color:#1E1A24; }      button.primary:hover:not(:disabled) { background:#f58fc6; }      input.search {        width:100%; padding:9px 12px; border-radius:10px; margin-bottom:12px;        border:1px solid rgba(255,255,255,.16); background:rgba(255,255,255,.06);        color:#fff; font-size:13px;      }      input.search::placeholder { color:rgba(255,255,255,.4); }      .count { font-size:12px; color:rgba(255,255,255,.55); margin-bottom:8px; }      .list { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px; }      .item { display:flex; align-items:center; gap:10px; padding:8px; border-radius:10px; text-decoration:none; color:inherit; }      .item:hover { background:rgba(255,255,255,.07); }      .av { width:32px; height:32px; border-radius:50%; flex:none; background:rgba(255,255,255,.1); object-fit:cover; }      .meta { min-width:0; flex:1; }      .user { font-size:13px; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }      .name { font-size:11px; color:rgba(255,255,255,.5); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }      .badge { font-size:10px; color:#F472B6; flex:none; }      .empty { font-size:13px; color:rgba(255,255,255,.5); padding:12px 0; }      .err { font-size:12px; color:#FCA5A5; margin-bottom:12px; }      .toast {        position:absolute; left:16px; right:16px; bottom:16px; padding:9px 12px;        border-radius:10px; background:#fff; color:#1E1A24; font-size:12px; font-weight:600; text-align:center;      }      .foot { padding:10px 16px; border-top:1px solid rgba(255,255,255,.1); font-size:10px; color:rgba(255,255,255,.35); }      .foot a { color:rgba(255,255,255,.55); }    </style>    <div class="panel">      <div class="head">        <span class="dot"></span>        <span class="title">Who doesn't follow you back</span>        <button class="x" title="Close">&times;</button>      </div>      <div class="body"></div>      <div class="foot">        Runs entirely in your browser. Nothing is uploaded.        <a href="https://likesforinstagram.com/instagram-unfollowers" target="_blank" rel="noopener">likesforinstagram.com</a>      </div>    </div>  `;   const bodyEl = shadow.querySelector('.body');  const panelEl = shadow.querySelector('.panel');  shadow.querySelector('.x').addEventListener('click', () => host.remove());   function visibleResults() {    const needle = state.filter.trim().toLowerCase();    if (!needle) return state.results;    return state.results.filter(      (user) =>        user.username.toLowerCase().includes(needle) ||        user.fullName.toLowerCase().includes(needle)    );  }   function setStatus(text) {    const el = bodyEl.querySelector('.status');    if (el) el.textContent = text;  }   let toastTimer;  function flash(message) {    clearTimeout(toastTimer);    panelEl.querySelector('.toast')?.remove();    const toast = document.createElement('div');    toast.className = 'toast';    toast.textContent = message;    panelEl.appendChild(toast);    toastTimer = setTimeout(() => toast.remove(), 2200);  }   const escapeHtml = (value) =>    String(value).replace(/[&<>"']/g, (char) => ({      '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',    })[char]);   function statusText() {    switch (state.phase) {      case 'idle':        return 'Ready. Start the scan whenever you are.';      case 'following':        return `Reading who you follow — ${state.scanned}${state.total ? ' of ' + state.total : ''}`;      case 'followers':        return `Reading your followers — ${state.scanned}${state.total ? ' of ' + state.total : ''}`;      case 'done':        return `Done. ${state.results.length} of ${state.following.size} don't follow you back.`;      case 'stopped':        return 'Stopped. Showing what was scanned so far.';      case 'error':        return 'Scan interrupted.';      default:        return '';    }  }   function render() {    const progress = state.total ? Math.min(100, (state.scanned / state.total) * 100) : 0;    const showProgress = state.phase === 'following' || state.phase === 'followers';    const shown = visibleResults();    const hasResults = state.phase === 'done' || state.phase === 'stopped';     bodyEl.innerHTML = `      <div class="status">${escapeHtml(statusText())}</div>      ${showProgress ? `<div class="bar"><div class="fill" style="width:${progress}%"></div></div>` : ''}      ${state.error ? `<div class="err">${escapeHtml(state.error)}</div>` : ''}      <div class="row">        <button class="act primary" data-action="${state.running ? 'stop' : 'scan'}">          ${state.running ? 'Stop' : state.phase === 'idle' ? 'Start scan' : 'Scan again'}        </button>      </div>      ${        hasResults && state.results.length          ? `        <div class="row">          <button class="act" data-action="copy">Copy usernames</button>          <button class="act" data-action="csv">Export CSV</button>        </div>        <input class="search" type="text" placeholder="Search username or name" value="${escapeHtml(state.filter)}" />        <div class="count">${shown.length} account${shown.length === 1 ? '' : 's'} — click one to open the profile</div>        <ul class="list">          ${shown            .map(              (user) => `            <li>              <a class="item" href="https://www.instagram.com/${encodeURIComponent(user.username)}/" target="_blank" rel="noopener">                <img class="av" src="${escapeHtml(user.avatar)}" alt="" referrerpolicy="no-referrer" />                <span class="meta">                  <span class="user">@${escapeHtml(user.username)}</span>                  <span class="name">${escapeHtml(user.fullName || (user.isPrivate ? 'Private account' : ' '))}</span>                </span>                ${user.isVerified ? '<span class="badge">verified</span>' : ''}              </a>            </li>`            )            .join('')}        </ul>      `          : hasResults            ? '<div class="empty">Everyone you follow follows you back. Nice.</div>'            : ''      }    `;     bodyEl.querySelectorAll('[data-action]').forEach((button) => {      button.addEventListener('click', () => {        const action = button.dataset.action;        if (action === 'scan') scan();        if (action === 'stop') { state.stopped = true; setStatus('Stopping...'); }        if (action === 'copy') copyUsernames();        if (action === 'csv') exportCsv();      });    });     const search = bodyEl.querySelector('.search');    if (search) {      search.addEventListener('input', (event) => {        state.filter = event.target.value;        const scrollTop = bodyEl.scrollTop;        render();        bodyEl.scrollTop = scrollTop;        bodyEl.querySelector('.search')?.focus();      });    }  }   render();  console.log(    '%cInstagram Unfollowers ready',    'font-weight:bold;font-size:14px;color:#F472B6',    '\nClick "Start scan" in the panel on the right.'  );})(); 

How to use it

Five steps and about two minutes. You need a computer, a browser and an Instagram account — nothing else to install, sign up for or pay for.

  1. 1

    Open Instagram on a computer

    Go to instagram.com in a desktop browser and sign in to the account you want to check.

    • Works in Chrome, Edge, Brave, Opera and Firefox. Safari works too, but you have to enable the Develop menu first: Safari → Settings → Advanced → “Show features for web developers”.
    • It has to be a computer. Mobile browsers on iOS and Android have no JavaScript console, so there is no way to run the script from a phone.
    • If you manage more than one account, switch to the right one before you start. The scan always reads whichever account is currently active.
    • Stay on instagram.com the whole time. The script checks the domain and refuses to run anywhere else.
  2. 2

    Open the developer console

    The console is a panel built into every desktop browser. Open it with the keyboard shortcut for your browser, then click the tab labelled “Console”.

    BrowserWindows / LinuxmacOS
    Chrome, Edge, BraveCtrl + Shift + J⌘ + ⌥ + J
    FirefoxCtrl + Shift + K⌘ + ⌥ + K
    Safari⌘ + ⌥ + C
    • A panel opens at the bottom or the side of the window. If it lands on “Elements” or “Network”, click across to the “Console” tab.
    • Prefer the mouse? Right-click anywhere on the page, choose “Inspect”, then switch to the Console tab.
    • You will probably see existing yellow and red messages from Instagram itself. That is normal — you can ignore them.

    Heads up: Instagram prints a big red “Stop!” warning in the console. It is a generic warning aimed at scams that talk people into pasting code that hijacks their account. It is genuinely good advice, which is exactly why this script is published in plain, commented, unminified form. Read it before you paste it.

  3. 3

    Paste the code and press Enter

    Click “Copy the code” at the top of this page, click once inside the console so the cursor is blinking next to the > prompt, then paste and press Enter.

    • Paste with Ctrl + V, or ⌘ + V on macOS.
    • Chrome and Edge block the very first paste into the console. If you see “Warning: Don’t paste code you don’t understand”, type allow pasting, press Enter, then paste the script again. You only ever do this once per browser.
    • Press Enter to run it. A panel slides in at the top right corner of the page and the console prints Instagram Unfollowers ready.
    • Nothing appeared? Read the message in the console and check the troubleshooting list below — it usually means you are on the wrong page or signed out.
  4. 4

    Press “Start scan” and let it run

    Click Start scan in the panel. It reads the accounts you follow first, then the accounts that follow you, showing a progress bar for each pass.

    • Instagram returns 50 accounts per request, and the tool waits about a second between them. An account with 1,000 following and 1,000 followers needs roughly 40 requests — about a minute.
    • That pause is deliberate. Hammering the endpoint is what triggers Instagram’s rate limits and temporary action blocks, so the tool stays at a comfortable pace on purpose.
    • Keep the tab open and in the foreground. Browsers throttle timers in background tabs, which makes the scan crawl.
    • If Instagram throttles anyway, the panel shows “Rate limited. Waiting…” and retries automatically with a longer pause each time. You do not need to do anything.
    • You can hit Stop at any point and still see the results from everything scanned so far.
  5. 5

    Work through your results

    When the scan finishes you get the full list of accounts you follow that do not follow you back, newest data first.

    • Type in the search box to filter by username or display name.
    • Click any row to open that profile in a new tab, where you can unfollow it yourself.
    • Use “Copy usernames” to put the whole list on your clipboard, or “Export CSV” to open it in Excel, Numbers or Google Sheets.
    • Unfollow at a human pace — a handful at a time, not hundreds in one sitting. Rapid mass-unfollowing is the single fastest way to get an account temporarily action-blocked.
    • The list is a snapshot. Run the scan again whenever you want fresh numbers.

If something goes wrong

Almost every problem is one of these six, and all of them are fixable in a few seconds.

“Run this on instagram.com”
The script only runs on Instagram itself. Navigate to instagram.com in the same tab and paste it again.
“You are not logged in”
The script identifies you from the session cookie Instagram already set. Sign in, refresh the page, then paste the script again.
Nothing happened after I pressed Enter
Check that you are on the Console tab rather than Elements or Network, and that the whole script was pasted — a partial paste throws a syntax error in red. Copy it again and retry.
It keeps saying “Rate limited. Waiting…”
Instagram is throttling you. The tool backs off and retries on its own. If it does not recover, close the panel and come back in ten to fifteen minutes — this happens most often on very large accounts or after several scans in a row.
“Your session expired”
Reload instagram.com, confirm you are still signed in, and run the script again.
Accounts I already unfollowed are still listed
Results are a snapshot from when the scan ran. Press “Scan again” to refresh the list.

What you get

Everything happens in one panel on top of Instagram.

The full non-follower list

Every account you follow that does not follow you back, with their profile picture, username, and display name. Click any row to open that profile in a new tab.

Search and filter

Type any username or name to narrow a list of hundreds down to the handful you actually care about.

Copy or export

Copy every username to your clipboard in one click, or download the whole list as a CSV to open in Excel, Numbers, or Google Sheets.

No account, no password

There is no sign-up, no email, and nothing to install. You never hand over your Instagram password to anyone — including us.

Privacy & security

Most “unfollowers” apps ask you to log in with your Instagram credentials. That is the one thing you should never do. Here is exactly what this tool does instead.

It never asks for your password
The script uses the session already active in your browser tab. There is no login form anywhere in it.
Nothing is sent to us
The only network requests the script makes are to instagram.com — the same ones the app itself makes when you scroll your followers list. No analytics, no telemetry, no upload. Open the Network tab and watch it run.
It is read-only
The script reads your follower and following lists. It never follows, unfollows, blocks, posts, or changes anything on your account.
You can read the code before you run it
The whole thing is a single readable, commented file — nothing minified or obfuscated. Open it, read it, and only paste it if you are comfortable with what it does.
Our app

Cleaned up your following list? Now grow it.

Unfollowing accounts that never followed you back is the easy half. The hard half is getting real engagement on what you post. That is what we built Boostly for — more likes and views on your Instagram posts, straight from your iPhone.

Download on the
App Store

Frequently asked questions

Is this Instagram unfollowers tool free?

Yes, completely. There is no sign-up, no trial, no credit card and no usage limit. You can run it as often as you like.

Do I have to give you my Instagram password?

No, and you should never give your Instagram password to any third-party unfollowers app. This tool runs inside your own browser and uses the session you are already logged into. It never sees or asks for your password.

Is it safe? Where does my data go?

Nowhere. The script reads your follower and following lists, compares them in your browser’s memory, and shows the result on screen. It makes no requests to likesforinstagram.com or any other server. You can verify this yourself: open the Network tab in DevTools while it runs and watch — the only requests are to instagram.com.

Can I get banned for using it?

The tool only reads data that Instagram already shows you, and it does not unfollow anyone automatically — that is exactly why we left bulk unfollowing out. It also pauses between requests to stay well inside normal usage. That said, very large accounts scanning repeatedly in a short window can hit Instagram’s temporary rate limits, so give it a break between runs.

Does it work on a phone?

Not really. You need a desktop browser with developer tools, so use Chrome, Edge, Firefox, or Safari on a computer. Once you have the list, you can export it as a CSV and work through it on your phone.

Will it unfollow people for me?

No, by design. Automated mass-unfollowing breaks Instagram’s terms of use and is a fast way to get an account restricted. This tool gives you the list and a direct link to each profile so you can unfollow the ones you actually want to, at your own pace.

Why does the scan take a few minutes?

Instagram returns followers 50 at a time, and the tool deliberately waits about a second between pages. An account following 2,000 people needs roughly 40 requests per list. Rushing that is what triggers rate limits, so the wait is intentional.

Read next

Guides on following, unfollowing and what your numbers actually mean.

More follower & unfollower guides

25 more walkthroughs on tracking, checking and cleaning up who you follow.

This tool is not affiliated with, endorsed by, or sponsored by Instagram or Meta. Instagram is a trademark of Meta Platforms, Inc. Use it on your own account and at your own discretion. Looking for more? Browse our free Instagram tools and guides.