RAG · Module 4 of 14 · 10 concepts · about 15 min

Chunking

How documents are cut into pieces before they are embedded. The cut decides what can be found and what the model gets to read, and it is the cheapest thing to get wrong.

At work: The setting RAG teams tune most: how a policy manual, a contract or a codebase is divided so that a search returns a complete, self-contained answer.

Why documents are cut into chunks

What it is

A chunk is a passage cut from a document, typically a few hundred words. Documents are chunked for three reasons:

  • Embedding models have a maximum input length and ignore anything past it.
  • One vector for a whole 40-page manual is a blurry average of every topic in it, close to nothing in particular.
  • The model should be handed the relevant passage, not the whole manual, to keep the prompt small and focused.

Why it matters

The chunk is the unit of everything downstream: it is what gets embedded, what gets retrieved, what gets cited and what the model reads. A fact that was split across two chunks, or buried in a chunk about five other things, is effectively lost.

In the real world: A support assistant starts answering warranty questions correctly only after the 60-page manual, previously one giant vector per chapter, is re-cut by section.

Thinking of chunking as a formatting detail. Change the chunking and you change every vector in the index and every prompt the model sees. In practice it moves answer quality more than swapping the language model does.

Chunk size: the central trade-off

What it is

Chunk size is usually measured in tokens (a token is a word-piece; 100 tokens is roughly 75 English words).

  • Small chunks (100 to 300 tokens) have a precise, focused meaning, so they match questions well, but they may lack the surrounding context needed to answer.
  • Large chunks (800 to 1,500 tokens) carry full context, but their vector is a blur of several topics, so they match less precisely, and they fill the prompt faster.

Why it matters

There is no universally right size; there is a right size for your documents and questions.

  • Short factual questions over FAQs favour small chunks.
  • Questions that need reasoning over a long explanation favour larger ones.
  • A common starting point is 300 to 500 tokens, then measuring retrieval quality at two or three sizes on real questions.

In the real world: A bank finds 256-token chunks best for its FAQ pages and 1,000-token chunks best for its long regulatory circulars, and runs both, chosen by document type.

Visual example

Trade-off

Small chunks are easier to find. Large chunks are easier to answer from.

Small chunks versus large chunks
small (100 to 300 tokens)large (800 to 1,500 tokens)
Vector meaningSharp: one ideaBlurred: several ideas
Matching a specific questionStrongWeaker
Context around the factOften missingPresent
Chunks per promptMany fitFew fit
RiskFact split from its conditionsRelevant line buried in noise

Tuning chunk size by reading a few answers and going by feel. Use a fixed set of test questions and measure how often the right passage is retrieved at each size. The Evaluation module shows how.

Chunk overlap

What it is

Overlap means each chunk repeats the last part of the previous one.

  • With a size of 500 tokens and an overlap of 100, chunk 1 covers tokens 0 to 500, chunk 2 covers 400 to 900, and so on.
  • The window moves forward by size − overlap each time.

Why it matters

A cut can land in the middle of an idea: the rule in one chunk, its exception in the next.

  • Overlap means a sentence near a boundary appears whole in at least one chunk.
  • The cost is storage and some repetition in results.
  • An overlap of 10 to 20 percent of the chunk size is the usual range.

In the real world: A contracts team adds a 15 percent overlap after finding that a clause's 'except where' sentence kept landing in the chunk after the clause it qualified.

Visual example

Computed

A 2,000-token document, chunk size 500, overlap 100. Each chunk starts 400 tokens after the last, so neighbours share 100 tokens.

size = 500, overlap = 100, step = 400
chunkfrom tokento tokenshared with previous
10500none
2400900400 to 500
38001300800 to 900
4120017001200 to 1300
5160020001600 to 1700

What the overlap costs

no overlap: 4 chunks, 2,000 tokens stored

overlap 100: 5 chunks, 2,400 tokens stored

extra: 20% more to embed and store

def chunk_tokens(tokens, size=500, overlap=100):
    """tokens: a list of strings. Returns a list of chunks (lists of tokens)."""
    step = size - overlap
    chunks = []
    for start in range(0, len(tokens), step):
        chunks.append(tokens[start:start + size])
        if start + size >= len(tokens):
            break
    return chunks

Setting a large overlap 'to be safe'. At 50 percent overlap you store and embed everything twice, and the top results become near-copies of each other, which wastes the prompt on repeated text.

Fixed-size and recursive splitting

What it is

  • Fixed-size splitting cuts every N tokens, wherever that lands, even mid-sentence.
  • Recursive splitting is the sensible default: it tries to cut at the biggest natural boundary first (a blank line between paragraphs), and only if a piece is still too long does it fall back to the next boundary (a line break, then a sentence end, then a space).

Why it matters

Recursive splitting keeps paragraphs and sentences whole wherever the size limit allows, at no extra cost. It is what most libraries offer as their standard text splitter, and it is a reasonable baseline to measure everything fancier against.

In the real world: A team's first working pipeline uses a recursive splitter at 400 tokens; it becomes the baseline that every later chunking experiment has to beat on the test set.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=400,          # in tokens
    chunk_overlap=60,
    separators=["\n\n", "\n", ". ", " ", ""],   # tried in this order
)
chunks = splitter.split_text(document_text)

Measuring chunk size in characters while thinking in tokens. A size of 1,000 characters is only about 250 tokens. Check which unit your splitter counts in, or your chunks will be four times smaller or larger than intended.

Structure-aware chunking

What it is

Structure-aware chunking cuts along the document's own outline: one chunk per section or sub-section, using the headings the parser preserved.

  • Markdown is split at # headings, HTML at heading tags, a contract at numbered clauses, code at functions and classes.
  • Sections that are too long are then split further; very short ones are merged with a neighbour.

Why it matters

Authors already grouped related sentences under a heading, so a section is usually a complete thought. Each chunk can also carry its heading path ("Refunds > Late orders") as metadata, which improves search, gives a precise citation and tells the model where the passage sits.

In the real world: A software company chunks its API documentation one endpoint per chunk, so a question about one endpoint never retrieves half of it mixed with half of the next.

Source (Markdown from the parser):
  # Refunds
  ## Late orders
  If your order arrives more than 30 minutes late, ...
  ## Damaged items
  Report a damaged item within 48 hours ...

Chunks:
  1. path: Refunds > Late orders     text: If your order arrives ...
  2. path: Refunds > Damaged items   text: Report a damaged item ...

Relying on structure the parser did not actually keep. If headings came out as ordinary lines of text, a structure-aware splitter has nothing to cut on and quietly behaves like a fixed-size one. Check the parsed output first.

Practise it: Design a Semantic Search System, Design a Production RAG System

Semantic chunking

What it is

Semantic chunking looks for where the topic changes. It embeds each sentence, compares every sentence with the next, and starts a new chunk wherever the similarity drops sharply, which signals a change of subject.

Why it matters

It helps with text that has no headings to cut on: transcripts, long emails, chat logs.

  • The chunks follow the content instead of a token count.
  • It costs an embedding call per sentence at indexing time and produces chunks of uneven length, so it needs a maximum size as a guard.

In the real world: A call-centre analytics team uses semantic chunking on hour-long call transcripts, where the conversation drifts between billing, delivery and complaints with no headings at all.

Assuming the cleverest method must be best. On documents that already have clear headings, semantic chunking often does no better than structure-aware splitting, at many times the indexing cost. Measure before adopting it.

Practise it: Design a Semantic Search System

Parent-child retrieval: search small, read big

What it is

A way to escape the size trade-off.

  • Index small child chunks (a few sentences) for searching, and keep a link from each to its larger parent (the whole section).
  • Search matches against the precise child vectors, then the system hands the model the parent text.
  • A close relative, sentence-window retrieval, matches a single sentence and returns it with the few sentences on either side.

Why it matters

It gives you both halves of the trade-off: the sharp matching of small chunks and the full context of large ones. It is one of the most reliable upgrades over plain chunking, and a common interview answer to 'how would you improve retrieval without changing the model?'.

In the real world: A pharmaceutical company matches a question to one sentence in a clinical guideline but shows the model the whole surrounding section, so warnings and conditions are not left behind.

# index time: embed children, remember their parent
for section in sections:                       # parent: ~1,000 tokens
    for child in split(section.text, size=150):
        index.add(vector=embed(child), metadata={"parent_id": section.id})

# query time: search children, return parents (each parent once)
hits = index.search(embed(question), k=20)
parent_ids = list(dict.fromkeys(h.metadata["parent_id"] for h in hits))
context = [parents[pid].text for pid in parent_ids[:4]]

Forgetting to remove repeated parents. Five matching children from the same section would otherwise put that section into the prompt five times.

Contextual chunk headers

What it is

A chunk read on its own often does not say what it is about.

  • "The fee is refunded within 5 days": which fee, which policy?
  • A contextual header fixes this by putting a line of context in front of the chunk before it is embedded: the document title and section path, or a one-sentence description written by a language model that has seen the whole document.

Why it matters

The added line gives the vector the missing subject, so the chunk can be found by questions that name it, and gives the model the missing subject when it reads the chunk. It is cheap, and it reliably improves retrieval on documents full of 'it', 'this plan' and 'the above'.

In the real world: A financial research firm prefixes every chunk of a quarterly report with the company name and quarter, so 'revenue grew 8%' is no longer retrievable for the wrong company.

Without a header (ambiguous on its own):
  The fee is refunded to your wallet within 5 working days.

With a contextual header (what actually gets embedded):
  Document: Kirana Kart Refund Policy > Late orders
  This passage explains how the delivery fee is returned
  when an order arrives late.

  The fee is refunded to your wallet within 5 working days.

Showing the header text to the user as if it were a quote from the document. Keep the original text separately for display and citation; the header is for search and for the model.

Chunking tables and code

What it is

Tables and code break under text splitters. A table cut in the middle leaves rows without their column headings. The fixes for tables:

  • keep a small table as one chunk;
  • split a large one by rows and repeat the header row in every chunk;
  • or turn each row into a sentence.

Code should be cut at function and class boundaries, with the file path and, for a method, its class name attached.

Why it matters

A chunk has to make sense by itself.

  • | 199 and above | Free | means nothing without the header that says the columns are order value and delivery fee.
  • Half a function is equally unreadable to both the search and the model.

In the real world: A developer-tools company indexes its codebase by function, with the file path in each chunk, so the assistant can cite the exact file and function it is quoting.

Running one splitter over everything. Detect content type during parsing and send tables, code and prose to different chunkers.

Practise it: Design a Semantic Search System

Choosing a chunking strategy

What it is

A workable order of attack:

  • Start with structure-aware chunking if the documents have headings, or recursive splitting if they do not, at 300 to 500 tokens with 10 to 15 percent overlap.
  • Add contextual headers.
  • If answers lack context, move to parent-child.
  • Reach for semantic chunking only for unstructured text.

Decide each step by measuring retrieval on a fixed set of test questions.

Why it matters

Interviewers ask 'how would you chunk this?' to see whether you reason from the documents and the questions, not from a favourite number. The strong answer names the document type, proposes a default, says what you would measure, and says what you would change if the measurement were poor.

In the real world: Asked to index contracts, FAQs and call transcripts together, a team uses three chunkers, one per document type, and verifies each against its own test questions.

Visual example

Decision guide

Start from what the document looks like.

Which chunking method for which content
contentmethodwhy
Docs with headings (manuals, wikis)Structure-awareSections are already complete thoughts
Plain prose, no headingsRecursiveKeeps paragraphs and sentences whole
Transcripts, long emailsSemanticFinds topic changes with no markup
Precise facts needing contextParent-childMatch small, read big
TablesBy rows, header repeatedRows are meaningless without headings
CodeBy function or classHalf a function is unreadable

Answering with a single number ('512 tokens'). The right reply starts with a question back: what do the documents look like, and what do users ask?

Practise it: Design a Semantic Search System