Most of the time, fuzzy-finding isn't what you need. You already know which file you want — you were just there. The real problem is getting back to it fast.
Here's a progression from the fastest built-ins to a full fuzzy picker, ordered by how much setup each requires.
1) <C-^> — toggle the file you were just in
If you're jumping between exactly two files — say, auth.go and auth_test.go — this is the only command you need.
<C-^>
Why it matters
<C-^> switches to the alternate file, which is simply the last buffer you had open. No filename to type, no fuzzy search, no menu. Just one keystroke back where you were.
Real scenario
You jump to a test file with gd to look up an interface definition. You've read what you need. <C-^> lands you back at your implementation file, cursor exactly where you left it.
Caveat
This only remembers one previous file. The moment you open a third buffer, the earlier one is no longer the alternate. For two-file back-and-forth it's unbeatable; for broader navigation you need something else.
2) Global marks (mA, 'A) — pin files you return to constantly
When you have 2-3 files you keep coming back to during a session — config, main handler, test file — global marks let you warp there instantly.
" Set a global mark (uppercase letter = global, persists across files)
mA
" Jump to it from anywhere
'A
Why it matters
Lowercase marks (ma, 'a) are local to the buffer. Uppercase marks (mA, 'A) are global — they remember both the file and line number. Press 'A from anywhere in Vim and you jump directly to that exact line in that file.
Real scenario
You're working on a feature and constantly need to reference routes.go. Press mR once on the relevant line. For the rest of the session, 'R gets you there in one keystroke from any file.
Caveat
Global marks are persistent across Vim sessions (written to ~/.viminfo or ~/.local/share/nvim/shada/main.shada). That's useful but also means stale marks from last week can send you to the wrong place. Clean them up occasionally with :delmarks A-Z.
3) <C-o> / <C-i> — retrace your navigation history
When you've been jumping around a codebase following references — gd to definition, then another gd, then a search — <C-o> walks you back through the full path.
<C-o> " jump back (older position in jump list)
<C-i> " jump forward (newer position)
Why it matters
Vim keeps a jump list — a history of every position you've jumped to (via gd, gg, /pattern<CR>, 'mark, etc.). <C-o> steps backward through that history, including across files. It's like a browser back button for your editing session.
Real scenario
You press gd to follow a function call into a helper, then follow another reference deeper. After reading the implementation, <C-o><C-o> brings you back through the call chain to where you started — no need to remember file names or line numbers.
Caveat
The jump list tracks positions, not intent. If you've been jumping around a lot, <C-o> might take you through more stops than expected before landing where you wanted. Use :jumps to see the full list and get your bearings.
4) Native buffer commands — :b partial<Tab>, ]b/[b, :ls
Before reaching for plugins, the built-in buffer commands cover most cases.
:ls " list all open buffers with numbers and names
:b partial " switch to buffer by partial filename match
:b 3 " switch to buffer number 3
]b " next buffer (vim-unimpaired or manual mapping)
[b " previous buffer
Why it matters
:b partial<Tab> tab-completes buffer names, so typing :b auth<Tab> switches to auth.go without typing the full name. :ls gives you a quick overview when you're not sure what's open. ]b/[b are useful when you want to cycle through a small number of buffers sequentially.
Real scenario
You remember you had some migration file open but can't remember the full name. :b migr<Tab> completes to 2024_add_users_migration.sql — done.
Caveat
:b partial matches against the full path, not just the filename. If you have two files with similar paths, the completion might behave unexpectedly. Use :ls first to check what's open and note the buffer numbers.
5) wildmode=lastused + wildcharm — a Tab-completion buffer picker
With two config lines, you can turn :b <Tab> into a fast MRU-ordered buffer picker, and then bind it to a single keypress.
set wildmenu
set wildmode=lastused
set wildcharm=<Tab>
nnoremap <leader>b :b <Tab>
Why it matters
wildmode=lastused reorders buffer completions so the most recently used buffers appear first. wildcharm=<Tab> is the less-known piece: it tells Vim which key triggers wildmenu expansion inside mappings (Tab doesn't work in mappings by default). Together, <leader>b drops you into a visual buffer list ordered by recency.
Real scenario
You've been alternating between four files all morning. Press <leader>b and the wildmenu immediately shows the last file you had open — one more <Tab> for the one before that. No plugin needed.
Caveat
wildmode=lastused is available in Vim 9.1+ and modern Neovim. Older Vim installations won't have it — check :help 'wildmode' and look for the lastused entry.
6) Harpoon — a persistent pinned-file list
If you work on the same 4-5 files across multiple sessions, Harpoon gives you numbered slots that survive restarts.
" Add current file to Harpoon list
:lua require('harpoon.mark').add_file()
" Open the Harpoon menu
:lua require('harpoon.ui').toggle_quick_menu()
" Jump directly to slot 1-4
<leader>1 <leader>2 <leader>3 <leader>4
Why it matters
Global marks are session-persistent but limited to 26 slots and tied to specific line numbers. Harpoon gives you a small, curated list of files you're actively working on — the 4-6 files that matter for this task — and lets you jump between them with numbered shortcuts that stay the same all day.
Real scenario
You're implementing a feature that touches handler.go, service.go, service_test.go, and schema.sql. Mark all four in Harpoon at the start of the day. Now <leader>1 through <leader>4 get you to each one instantly, no matter how many other buffers you open in between.
Caveat
Harpoon is Neovim-only and requires Lua configuration. The list is project-aware (scoped to the git root), which is useful but means you maintain separate lists per project. It's intentionally minimal — if you want fuzzy search over the pinned files, you'll need Telescope integration.
7) :Telescope buffers — fuzzy search over all open buffers
When you have many buffers open and don't remember enough of the filename to use :b partial, the Telescope buffer picker lets you fuzzy-search everything.
:Telescope buffers
" Recommended binding:
nnoremap <leader>fb <cmd>Telescope buffers<CR>
Why it matters
The Telescope buffer picker shows all open buffers with their numbers and modification state ([+] for unsaved changes). Type any substring to filter in real time. <C-d> deletes a buffer from the list without leaving the picker — useful for trimming stale buffers during a long session.
Real scenario
You've been working for three hours and have 15 buffers open. You need to get back to a migration file but can't remember the exact name. Type migr in the picker and it narrows immediately to the one you want.
Caveat
Neovim only. If you're on vanilla Vim, fzf.vim's :Buffers command gives you essentially the same picker. Neither requires you to know buffer numbers — just type enough of the name and pick.
Wrap-up
These techniques stack. In practice: <C-^> for the last file, 'A/'B for pinned files you visit constantly, <C-o> to retrace after diving into definitions, <leader>b for a quick MRU-ordered list when you have a few buffers open, and :Telescope buffers when you need to search across many.
The sweet spot for most people is <C-^> + global marks + wildmode=lastused — zero plugins, huge improvement over default behavior.
If you want more of this, the full explanations are on vimtricks.wiki. What file-switching workflow are you currently using that still feels clunky?
Top comments (0)