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.
/*! * 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">×</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) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', })[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.' );})(); 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.
Go to instagram.com in a desktop browser and sign in to the account you want to check.
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”.
| Browser | Windows / Linux | macOS |
|---|---|---|
| Chrome, Edge, Brave | Ctrl + Shift + J | ⌘ + ⌥ + J |
| Firefox | Ctrl + Shift + K | ⌘ + ⌥ + K |
| Safari | — | ⌘ + ⌥ + C |
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.
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.
Ctrl + V, or ⌘ + V on macOS.allow pasting, press Enter, then paste the script again. You only ever do this once per browser.Instagram Unfollowers ready.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.
When the scan finishes you get the full list of accounts you follow that do not follow you back, newest data first.
Almost every problem is one of these six, and all of them are fixable in a few seconds.
Everything happens in one panel on top of Instagram.
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.
Type any username or name to narrow a list of hundreds down to the handful you actually care about.
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.
There is no sign-up, no email, and nothing to install. You never hand over your Instagram password to anyone — including us.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Guides on following, unfollowing and what your numbers actually mean.
Instagram never tells you who left. Four methods that actually work, and the one category of app you should never touch.
Read the guide6 min readWhat triggers an Instagram action block, why the “safe limits” you see quoted are guesswork, and how to clean up without one.
Read the guide5 min readWhat a good ratio looks like, why the algorithm does not care about it, and where it genuinely counts against you.
Read the guide7 min readWhy it still tempts people, what it costs in engagement rate and reach, and what to do instead in 2026.
Read the guide25 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.