HomeArticlesPiece Table: The Text Editor's Secret Edit Buffer

Piece Table: The Text Editor's Secret Edit Buffer

Every keystroke you type into a text editor has to update the document somehow, and the naive approach of keeping the entire document as one giant mutable string falls apart quickly: inserting a single character in the middle of a million-character file would mean shifting everything after it, an O(n) operation on every keypress that makes large documents feel sluggish. The piece table is an elegant answer to this problem, used in production editors including early versions of Microsoft Word and more recently as the backing data structure for VS Code's text buffer. Instead of storing the document as one mutable blob, a piece table keeps two append-only buffers, an original buffer holding the file's initial content exactly as loaded, and an add buffer that grows by appending every new character the user types, and it never modifies either buffer in place. The actual document is represented indirectly as an ordered sequence of small descriptors called pieces, each one simply a pointer into one of the two buffers along with a start offset and a length, describing a contiguous run of text. Editing the document then becomes an operation on this lightweight sequence of descriptors rather than on the text itself: inserting text splits an existing piece into two and slots in a new piece pointing at freshly appended add-buffer content, while deleting text similarly splits and trims pieces without ever touching the underlying buffers. This simulation lets you type and delete inside a live piece table, watching the original buffer, the add buffer, and the linked list of piece descriptors update in real time.

mysimulator teamUpdated June 2026≈ 8 min read▶ Open the simulation

Two buffers, never modified in place

The original buffer is loaded once when a file is opened and is treated as strictly read-only for the entire editing session; nothing is ever inserted into it or deleted from it. The add buffer starts empty and grows purely by appending, every character the user types anywhere in the document gets tacked onto the end of this buffer, regardless of where in the logical document it is meant to appear. This append-only discipline is what makes the piece table efficient: since neither buffer is ever mutated in the middle, there is no need for expensive memory shifting when handling an edit, and both buffers can be implemented as simple, contiguous, growable arrays that are extremely cache-friendly and fast to append to. The actual visual position of a character in the document is entirely determined by where its describing piece sits in the sequence of pieces, not by its physical offset in either buffer, which is the conceptual leap that decouples logical document order from physical storage order.

The piece sequence: an ordered list of small descriptors

The document itself is represented as an ordered sequence of piece descriptors, commonly implemented as a doubly linked list or occasionally a more advanced structure like a balanced tree for very large documents, where each piece is a small tuple recording which buffer it points into, the starting offset within that buffer, and the length of the run of text it covers. Reading the whole document in order simply means walking this sequence from start to end and concatenating each piece's referenced text; a freshly opened, unedited file starts as a single piece spanning the entire original buffer. Because pieces are small fixed-size records rather than copies of actual text, the piece sequence stays compact even for a heavily edited document, growing only in proportion to the number of edit operations performed, not the size of the document or the amount of text inserted, which is a crucial distinction from approaches that would need to duplicate text spans on every change.

Inserting text: split and splice

To insert new text at some position in the document, the editor first appends the newly typed characters to the end of the add buffer, then locates which existing piece in the sequence currently covers that insertion position. If the insertion point falls exactly at a piece boundary, a brand-new piece describing the freshly appended add-buffer text can simply be spliced into the linked list at that point with no further work. If the insertion point falls in the middle of an existing piece, that piece must be split into two pieces, one covering the text before the insertion point and one covering the text after it, both still pointing into whichever original buffer the split piece referenced, and the new piece for the typed text is inserted between them. Either way, this operation touches only a small constant number of piece descriptors, splitting at most one existing piece and inserting one new piece, regardless of how large the document is, which is why typing into a piece-table-backed editor stays fast even in documents with millions of characters.

Deleting text: split and trim, no data destroyed

Deletion works by the same split-and-splice logic in reverse. If the deleted range exactly covers one or more whole pieces, those pieces are simply removed from the sequence. If the deleted range starts or ends in the middle of a piece, that piece is split at the boundary of the deletion, and only the portion outside the deleted range is kept in the sequence; the portion falling inside the deleted range is simply excluded from the piece list going forward, even though its underlying bytes still physically exist untouched in the original or add buffer. This is one of the most elegant properties of the piece table: because the underlying buffers are never actually modified or shrunk, deleted text is never truly destroyed, it is merely no longer referenced by any piece in the current sequence, meaning the data structure is naturally forgiving of a design that wants to keep old text around for other purposes.

Fast, cheap undo and redo for free

Because every edit operation is expressed purely as a small, local change to the piece sequence, an editor can implement full undo and redo simply by recording, for every edit, which pieces were removed and which were added, essentially a tiny diff of the linked list rather than a diff or snapshot of the entire document text. Undoing an insertion means removing the piece or pieces that were added and restoring whichever piece or pieces were split, all without touching either buffer, and redoing simply replays the same small change again. This is dramatically cheaper than naive undo implementations that snapshot the whole document string on every keystroke, and it is a major reason piece tables remain popular in modern editor implementations: they give you not just fast typing but essentially free, memory-efficient undo history as a natural side effect of how edits are already represented internally, which is exactly the kind of design where getting the core data structure right pays dividends throughout the rest of the system.

Frequently asked questions

Why not just use a mutable string or array for the whole document?

Inserting or deleting in the middle of a large mutable array requires shifting every character after the edit point, an O(n) cost per keystroke that becomes noticeably slow for large files. Piece tables avoid this by never moving existing text, only adjusting small descriptors.

What exactly is stored in a piece descriptor?

A piece descriptor records which buffer, original or add, the piece points into, the starting offset within that buffer, and the length of the text span it covers. It contains no actual text itself, just metadata describing a range.

Does deleting text ever actually erase bytes from memory?

No. Deletion only removes or trims piece descriptors from the sequence so the deleted text is no longer part of the visible document; the underlying bytes remain physically present in the original or add buffer, simply unreferenced.

How does the piece table make undo and redo efficient?

Since every edit is just a small, localized change to the piece sequence, such as splitting one piece into two or splicing in a new one, undo only needs to record and reverse that small change rather than snapshot the entire document, making undo history extremely cheap to maintain.

Which real editors use piece tables?

Early versions of Microsoft Word used a piece table style approach, and VS Code's core text buffer implementation, described in its engineering blog, uses a piece table variant built on a balanced tree for efficient large-file editing.

Try it live

Everything above runs in your browser — open Piece Table: The Text Editor's Secret Edit Buffer and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.

▶ Open Piece Table: The Text Editor's Secret Edit Buffer simulation

What did you find?

Add reproduction steps (optional)