Skip to content
AI360XpertBeta

Chunking Strategies

How you slice a document before embedding it decides whether each retrieved passage holds a whole idea or half of one. Even cuts are simple but blind; cuts that follow the document keep ideas intact.

Fixed-size chunking cuts a 1,000-token document into 7 even pieces and can split a sentence across a boundary, while structure-aware chunking cuts along section boundaries so each chunk holds one whole idea
Fixed-size chunking cuts a 1,000-token document into 7 even pieces and can split a sentence across a boundary, while structure-aware chunking cuts along section boundaries so each chunk holds one whole idea

Why Does This Exist?

An embedding turns a passage into one vector, a list of numbers that captures what the passage means. That works well when the passage is about one thing. Embed a whole 50-page manual as a single vector and you get an average over every topic it touches, too blurry to match any specific query. So real retrieval systems split each document into smaller pieces called chunks first, then embed every chunk on its own. (The glossary version of the term is chunking.)

Where the cuts fall decides whether a retrieved passage reads as a complete thought or a fragment. A boundary that lands mid-sentence leaves each half without the context the other half carried, and the model receives both halves separately, in different retrieved results, or only one of them. This is the most avoidable source of bad retrieval in a RAG system, and it is set entirely by the chunking strategy.

This page covers the strategy choice itself: fixed-size cuts, structure-aware cuts, how overlap rescues split sentences, and the size tradeoff underneath everything. Two neighbours go deep on one strategy each: semantic chunking splits where the topic shifts, and parent-child chunking searches small chunks but hands the model the large one. RAG evaluation is where you measure whether a chunking change actually helped.

Think of It Like This

Cutting a printed cookbook into strips

You have a printed cookbook and a pair of scissors, and you want every strip to contain exactly one recipe.

One option is to cut the book into equal-width strips with a ruler. Every strip is the same size, which is tidy, but the cuts land wherever the ruler says. Some strips hold a whole recipe. Others hold the end of one recipe plus the start of the next, or half a recipe whose ingredient list is on a different strip. Anyone handed a single strip has to hope it is one of the lucky ones.

The other option is to cut along the recipe boundaries the book already gives you: one recipe, one strip. The strips come out uneven, because recipes are uneven, but every strip is self-contained. Nobody needs the neighbouring strip to make sense of theirs.

The analogy stops in one place. Paper strips never get blurry: a big strip is just as readable as a small one. Embeddings are different. A very large chunk averages many ideas into one vector and matches queries worse, so chunk size carries a precision cost that paper never charges. That tradeoff is the rest of this page.

How It Actually Works

Call the document length LL, measured in tokens. (LL is just the count of token pieces the document splits into; a token is roughly three quarters of an English word.) A strategy picks a chunk size CC and an overlap OO, the number of trailing tokens each chunk repeats as the start of the next one. The window moves forward by the stride S=C−OS = C - O each time, and the chunk count is N=1+⌈(L−C)/S⌉N = 1 + \lceil (L - C) / S \rceil, where ⌈⋅⌉\lceil \cdot \rceil rounds up. If the document is shorter than one chunk (L≤CL \le C), there is a single chunk and none of this matters.

Fixed-size splitting and its arithmetic

Fixed-size chunking walks the window forward by SS tokens and cuts wherever it lands, with no regard for sentences or sections. It is cheap, predictable, and blind. Every boundary is a gamble: most land between ideas and do no harm, but some land inside a sentence and split one thought across two independently embedded chunks.

Overlap is the direct fix for that gamble. The last OO tokens of chunk ii are also the first OO tokens of chunk i+1i + 1. So any sentence of at most OO tokens that straddles a boundary starts no earlier than OO tokens before the cut, which puts its whole span inside the next chunk. Overlap does not make cuts smart. It gives every cut a second chance.

Worked example: 1,000 tokens into 7 chunks

Take L=1000L = 1000 tokens with C=200C = 200 and O=50O = 50, so S=150S = 150. Then N=1+⌈800/150⌉=1+6=7N = 1 + \lceil 800 / 150 \rceil = 1 + 6 = 7 chunks. Their token spans are 1 to 200, 151 to 350, 301 to 500, 451 to 650, 601 to 800, 751 to 950, and 901 to 1000. Notice the last chunk holds only 100 tokens: unless the length divides evenly, the tail is always a runt.

Now put a 30-token definition at tokens 186 to 215. The cut at token 200 splits it: chunk 1 holds tokens 186 to 200, which is not the definition. But the sentence starts at 186, which is later than 200−50+1=151200 - 50 + 1 = 151, so tokens 186 to 215 sit entirely inside chunk 2 (151 to 350). The overlap rescued exactly the case it exists for. Had the definition been 80 tokens long, longer than the 50-token overlap, no overlap could have saved it, and that failure would have been structural, not bad luck.

Structure-aware splitting

Structure-aware chunking cuts where the document already changes: section headers first, then paragraphs, then sentences, then, only for a piece still too large, fixed-size splitting as the last resort. Trying the largest boundary first and falling back to smaller ones is called recursive splitting, and it is the standard practical form of this idea. Chunk sizes come out uneven, because sections are uneven, but each chunk tends to be one coherent unit that reads sensibly on its own.

Two flavours deserve names. Splitting on the document's own markup, headers and lists is rule-based and nearly free. Splitting where the meaning shifts, measured with embeddings, is semantic chunking: more accurate on messy prose, more expensive at ingestion. Either way, structure-aware splitting assumes the document has structure worth following. Chat logs, transcripts, and concatenated reviews often do not, and there fixed-size chunking with generous overlap is the honest choice, not a lazy one.

The size tradeoff that never goes away

Small chunks match precisely. A short, focused chunk produces a sharp embedding, and a specific query lands on it cleanly. But each chunk carries little surrounding context, so the model may receive the answer's sentence without the caveat two sentences away.

Large chunks keep context. The model sees the caveat, the setup, and the worked detail together. But the embedding averages more ideas, so a precise query struggles to tell the right large chunk from a wrong one that mentions the topic in passing.

There is no correct size, only a size tuned against your documents and your queries. Start near a few hundred tokens with 10 to 25 percent overlap, then move the numbers while watching retrieval scores, not while rereading samples. And when one size cannot serve both matching and context, stop tuning one size: parent-child chunking searches small chunks and hands the model the large parent instead.

One assumption underlies all of this: each chunk is judged on its own. Anything whose meaning lives far from its words breaks that assumption. Pronouns pointing three paragraphs back, "as shown above", a table split from its caption. When your documents are full of cross-references, fix the references (resolve them, prepend the section header to each chunk) before you tune anything else.

Code

Two splitters on the same text: fixed-size with overlap, and a paragraph-aware splitter. Watch where the fixed window lands.

def fixed_size_chunks(text: str, size: int, overlap: int) -> list[str]:    chunks: list[str] = []    step: int = size - overlap  # window moves less than its width    start: int = 0    while start < len(text):        chunks.append(text[start:start + size])        start += step    return chunks

def paragraph_chunks(text: str) -> list[str]:    return [p.strip() for p in text.split("\n\n") if p.strip()]

text: str = "AAA BBB.\n\nCCC DDD."print(fixed_size_chunks(text, size=12, overlap=6))# -> ['AAA BBB.\n\nCC', 'B.\n\nCCC DDD.', 'C DDD.']print(paragraph_chunks(text))# -> ['AAA BBB.', 'CCC DDD.']

The 18-character text is sliced at stride 6, so chunks start at 0, 6, and 12. The first chunk cuts "CCC" after "CC", and the third chunk is a runt fragment, "C DDD.". The overlap earns its keep in the middle chunk, which holds the whole second paragraph, "CCC DDD.", plus the tail of the first. The paragraph splitter keeps both ideas intact, because the blank line told it exactly where the boundary was.

Watch Out For

Stripping markup before you split

Pipelines often clean documents first: drop markdown headers, flatten lists, collapse whitespace. Then the structure-aware splitter runs on text whose structure you just deleted, and it silently degrades to fixed-size cutting. The symptom is specific: chunks that start mid-idea in documents you know are well organised, plus section context ("which product does this warning apply to?") missing from every retrieved passage.

Split first, clean later. Keep headers, list markers, and table captions through ingestion, cut on them, and only then strip formatting inside each chunk. If you must clean first, prepend the section path to every chunk so each one carries its address with it.

Setting chunk size once and never remeasuring

A size tuned on short FAQ entries misfires on long procedures with interdependent steps, and a corpus drifts: new document types arrive, query patterns shift, and the once-good size quietly stops being good. The symptom is a slow retrieval decay nobody can date, because nothing deployed and nothing broke.

Treat size and overlap as measured settings. Change one, score retrieval on a fixed query set with RAG evaluation practice, keep the winner. Re-run that check whenever the corpus mix changes. It is the cheapest experiment in the whole pipeline.

The Quick Version

  • Chunking splits each document before embedding, because one vector for a whole document averages every topic it touches into blur.
  • Fixed-size chunking cuts every CC tokens with stride C−OC - O. It is cheap, predictable, and occasionally cuts a sentence in half.
  • Overlap repeats the last OO tokens as the next chunk's start, so any sentence of at most OO tokens that straddles a cut still lands whole inside the next chunk.
  • Structure-aware chunking cuts at section, paragraph, and sentence boundaries first and falls back to fixed-size only for oversize pieces, trading even sizes for coherent chunks.
  • Small chunks match queries precisely but starve the model of context. Large chunks feed context but match vaguely. No size is correct in general.
  • When one size cannot do both jobs, search small chunks and hand the model the large parent, instead of splitting the difference badly.