After mastering text editing within a single file, it is time to manage multiple files at once: understand the difference between buffers, windows, and tabs, master split windows, buffer navigation, and the multi-file editing workflow of a professional engineer.

After covering efficient copy & paste with registers, recording repetitive editing steps with macros, and advanced search & replace in episode 4 — you now have a complete set of "editing weapons" for working with text in a single file. But there is one big limitation you will soon feel in the real world: code never lives in a single file.
Imagine you are debugging a bug in production. You need to read main.go, glance at a function in utils.go, compare the implementation with the test in main_test.go, while finding out where a constant is configured. In a regular editor, opening five files means five cramped tabs — and switching between tabs itself takes time. In Neovim, the right question is not "how do I open many files", but rather "how do I organize my workspace properly".
In this episode we will dissect the three layout components that form the foundation of the entire Neovim multi-file experience: buffers, windows, and tabs. We will learn to manage split windows, switch between buffers quickly, and build a workflow that lets you move between files as fast as thought — without ever touching the mouse. This is the gateway to PHASE 2 of this series, where we start moving from merely "using" Neovim toward "building" Neovim.
Before getting into the commands, we must truly understand the difference between these three terms. Most new users get stuck because they think buffer, window, and tab are the same thing — when in fact all three play very different roles.
Think of these three analogies:
auth module and another tab for the payment module.| Concept | Analogy | Brief Definition | Lifecycle |
|---|---|---|---|
| Buffer | Document | A file loaded into memory | Stays alive even when not visible |
| Window | Magnifying glass | A viewport for viewing a buffer | Dies when closed, the buffer remains |
| Tab | Workbench | A container for one or more windows | Dies when closed, the buffer remains |
Note
The most important consequence of this model: buffers outlive windows and tabs. Closing a window or tab does not close its buffer. Conversely, a buffer can stay "hidden" in memory as long as Neovim is running — and this is actually Neovim's strength, because switching between buffers is far faster than opening a file from disk.
There is one more concept to clarify from the start: a buffer is not a file on disk. A buffer is the file content currently loaded in memory. The changes you make to a buffer are only actually written to disk when you run :write. This is important to understand when we discuss :bdelete later — deleting a buffer does not delete the file on disk.
Buffers in Neovim are numbered sequentially. To see all currently loaded buffers, use :ls or :buffers:
:lsThe output will look something like this:
1 %a "main.go" line 12
2 h "utils.go" line 1
3 #h "config.go" line 0
4 u "lsp.log" line 0
5 - "README.md" line 0Let's break down the marker columns on the left:
| Marker | Meaning |
|---|---|
% | The currently active buffer (shown in a window) |
# | The alternate buffer (the last one visited) |
a | Active buffer (displayed in one of the windows) |
h | Hidden buffer (stored in memory, not visible) |
u | Unlisted buffer (does not appear in the regular list, e.g. help) |
- | Buffer not fully loaded yet (only referenced) |
+ | Buffer has unsaved changes (modified) |
This is the heart of multi-file productivity: switching buffers without closing the window layout you worked so hard to arrange.
| Command | Function |
|---|---|
:bnext / :bn | Move to the next buffer |
:bprevious / :bp | Move to the previous buffer |
:buffer <number> / :b <number> | Move directly to the buffer with a specific number |
:b <name> | Move to a buffer by name (autocomplete with Tab) |
:b# | Move to the alternate buffer (the last one visited) |
Tip
The most used combination among engineers is :b <name> with autocomplete. Imagine you have main.go, main_test.go, and utils.go. Just type :b main then press Tab to complete — far faster than remembering buffer numbers. For jumping back and forth between two files you are comparing, :b# (or Ctrl+^ / Ctrl+6) is a secret weapon: one keystroke to switch, one more to return.
The most common mistake in this episode: closing buffers with :q. Remember the model again — :q closes the window, not the buffer. The buffer displayed in that window stays alive in memory as a hidden buffer.
To truly remove a buffer from memory, use :bdelete (or its short form :bd):
| Command | Function |
|---|---|
:bdelete | Delete the active buffer from memory |
:bd 2 | Delete buffer number 2 |
:bd main.go | Delete a buffer by name |
:bd! | Force-delete a buffer that still has unsaved changes |
:bdelete a b c | Delete several buffers at once |
Warning
When you run :bd on a buffer with unsaved changes, Neovim refuses and shows the message E89: No write since last change. Do not rush to run :bd! — that will discard all your changes. Always save with :w first, unless you deliberately intend to discard those changes.
Now we get to one of the features that makes Neovim feel so "spacious": splitting the screen into multiple windows.
| Command / Shortcut | Split Direction |
|---|---|
:split / :sp | Splits the screen horizontally (top-bottom) |
:vsplit / :vs | Splits the screen vertically (left-right) |
Ctrl+w s | Horizontal split without a prompt |
Ctrl+w v | Vertical split without a prompt |
:new / :vnew | Horizontal/vertical split with an empty buffer |
You can also specify a file to open directly in the split: :vs utils.go will split the screen vertically and open utils.go in the new window.
Note
"Horizontal" and "vertical" here refer to the position of the dividing line, not the direction of addition. :split adds a window below and its dividing line is horizontal. :vsplit adds a window to the side and its dividing line is vertical. Many beginners get it backwards, so remember: v = vertical line.
Once the screen is split, you need a way to jump between windows without the mouse. This is the key: Ctrl+w followed by a direction key.
| Shortcut | Function |
|---|---|
Ctrl+w h | Move to the window on the left |
Ctrl+w j | Move to the window below |
Ctrl+w k | Move to the window above |
Ctrl+w l | Move to the window on the right |
Ctrl+w w | Move to the next window in sequence (cycle) |
Ctrl+w p | Return to the last active window |
Ctrl+w H / J / K / L | Move the active window's position to that side |
Tip
The right mindset: Ctrl+w h/j/k/l is deterministic — the keys follow the visual direction, exactly like h/j/k/l navigation in modal editing. Once you get used to it, try adding Ctrl+w w to cycle through all windows when there are many and their arrangement is irregular.
A split layout is rarely perfect right away. When one file needs more space, you can adjust window sizes:
| Shortcut | Function |
|---|---|
Ctrl+w = | Make all windows equal size proportionally |
Ctrl+w < | Shrink the active window's width |
Ctrl+w > | Grow the active window's width |
Ctrl+w - | Shrink the active window's height |
Ctrl+w + | Grow the active window's height |
Ctrl+w _ | Maximize the active window's height |
| `Ctrl+w | ` |
5 Ctrl+w > | Grow the width by 5 columns at once |
Important
The law you must remember: Ctrl+w = is the rescue button. When the split layout gets messy and you feel lost, one press of Ctrl+w = will tidy all windows to balanced sizes. This is analogous to terraform plan restoring a state to something predictable.
| Command / Shortcut | Function |
|---|---|
:q | Close the active window (the buffer stays alive in memory) |
Ctrl+w c | Close the active window |
:only / Ctrl+w o | Close all other windows, keep the active one |
:close / Ctrl+w q | Close the active window (if it is not the last one) |
Caution
In the last window, :q does not close the window but rather exits Neovim entirely — and if there is a modified buffer, Neovim refuses to exit and shows E37: No write since last change. So do not be confused: if you only want to "close the last window but stay in Neovim", that is impossible — the last window is Neovim's main screen. Your options are to close the buffer with :bd so the file you are viewing switches to another buffer, or leave that window open.
Tabs in Neovim have a different meaning than browser tabs. In a browser, a tab holds one page. In Neovim, a tab holds one complete layout that can contain many windows.
| Command / Shortcut | Function |
|---|---|
:tabnew / :tabe <file> | Create a new tab (optionally opening a file directly) |
gt / :tabnext | Move to the next tab |
gT / :tabprevious | Move to the previous tab |
:tabs | List all tabs along with their windows |
:tabclose | Close the active tab |
:tab split | Open the active buffer in a new tab |
:tab ball | Open all buffers in separate tabs |
Note
Best practice from most Neovim power users: do not overuse tabs. The recommended main workflow is buffers + windows, because buffers are lighter and switching between buffers (:b ...) is far faster than switching between tabs. Tabs should be used for truly separate work contexts — for example one tab for feature A and one tab for feature B, or one tab for code and one tab for a terminal. Use tabs like separate workbenches, not like a list of open documents.
Now let's string all the commands above together into one real scenario. Suppose you are asked to understand the flow of an HTTP handler in main.go that calls a function from utils.go, and you want to open its test as a reference.
" 1. Buka file utama
:e main.go
" 2. Belah layar vertikal dan buka utils.go di window kanan
:vsplit utils.go
" 3. Pindah kembali ke window kiri (main.go)
Ctrl-w h
" 4. Buka test file di window kiri bawah
:split main_test.go
" 5. Samakan ukuran semua window agar proporsional
Ctrl-w =
" 6. Lompat bolak-balik antara buffer alternatif
Ctrl-w h
:b#After mastering the flow above, notice that you never touched the mouse, never closed the layout, and can jump between four sources of code with just a few keystrokes. This is the difference between "reading code" and "navigating code" — and this is precisely the skill most tested when senior engineers do deep debugging.
Here are the mistakes I most often see — including ones I made myself when first learning:
:q closes a buffer. :q closes a window. If you want a file to "disappear from the list", use :bd. If you want the buffer to stay but be invisible, :q is enough — the buffer becomes hidden and can be called again anytime.:bd deletes the file on disk. Not at all. :bd only discards the buffer from memory. The file on disk is untouched as long as you do not write changes.:bd!. This discards all your changes without confirmation. Get in the habit of :w before :bd.main.go open and run :e main.go again, you get a duplicate buffer. Use :b main to switch to the existing buffer, or :set hidden so switching unsaved buffers does not trigger an error.:b to switch, than to force many windows side by side.Ctrl+w =. An unbalanced layout makes you lose focus. One keystroke tidies everything up.Tip
A very effective exercise: open a favorite open source project of yours (for example the Neovim codebase itself or a library you often use), then trace one feature from its front page all the way to its implementation using only :b, :vsplit, and Ctrl+w. Repeat until switching feels like a reflex, not a conscious decision.
In episode 5 we dissected Neovim's three layout components that are often confused: buffers as documents in memory, windows as viewports for viewing documents, and tabs as workbenches that hold several windows. You also learned to split the screen with :split and :vsplit, navigate between windows with Ctrl+w h/j/k/l, balance window sizes with Ctrl+w =, and manage buffers with :bnext, :b, and :bdelete.
The most important points to take with you:
:q closes a window; :bd removes a buffer from memory.Ctrl+w = is the rescue button for tidying up layouts.Now you can manage a multi-file workspace with confidence. However, the more buffers you open, the more you will feel the need to personalize Neovim — custom shortcuts, options to your taste, even automatic commands. In episode 6, we enter the heart of modern Neovim configuration: migrating from Vimscript to a Lua configuration with init.lua. We will dissect the ~/.config/nvim/ directory structure, the basics of the Lua language, and the Neovim Lua API that will equip you to build your own config from scratch. Stay motivated!