Area 02 // RAGLesson 35 / 14015 min read
Chunking strategies
Chunking splits documents into pieces small enough to retrieve precisely but complete enough to make sense on their own, and parent-child chunking gets both by searching small pieces while handing the model the larger section around them.
Think of it like this
Analogy
Cutting a cake for a party. Cut it into crumbs and nobody gets a proper piece with icing, sponge and filling. Hand out half the cake to one person and everyone else waits. You want slices that are each a complete, satisfying piece.
Chunking is slicing documents into pieces that each make sense on their own. Parent-child is serving a small taster to find what people like, then giving them the whole slice.
The problem it solves
You can't embed and retrieve a 200-page manual as one item: its single vector would be a blurry average of every topic, and you couldn't fit it in the prompt anyway. But cut it badly and you get chunks like "…not cover water damage. To claim, email sup…", which match nothing well and confuse the model.
Chunking quietly decides a large share of RAG quality. Good chunks are retrievable and understandable on their own.
How it works: chunking strategies
- Decide what a "good chunk" is for your questions: usually one idea, step or rule, with enough surrounding words to stand alone, often 200–800 tokens.
- Pick a strategy:
- Fixed-size with overlap: every N tokens or characters, repeating the last M (overlap). Simple, works for anything, but cuts mid-sentence.
- Sentence or paragraph-aware: pack whole sentences or paragraphs up to a size limit. Never cuts a sentence; a good default.
- Structure-aware: split on headings and sections (markdown, HTML, document layout), then by paragraph inside long sections. Best for manuals, docs and policies.
- Semantic chunking: split where the meaning changes (embedding similarity between neighbouring sentences drops). Helps with long unstructured text; costs more to compute.
- Special content: keep tables and code blocks whole; give each its own chunk.
- Add context to each chunk: the document title and heading path ("Warranty > Exclusions"). Cheap and very effective (see also contextual retrieval).
- Measure: build a set of questions with known answer passages and compare strategies and sizes using Recall@k.
| Strategy | Good for | Watch out |
|---|---|---|
| Fixed-size + overlap | Any text, quick start | Cuts sentences and tables |
| Sentence / paragraph | Most prose | Very long paragraphs |
| By headings | Docs, manuals, policies | Uneven section sizes |
| Semantic | Long transcripts, articles | Extra compute, harder to debug |
How it works: parent-child chunking
- Split each document into parents: large, meaningful sections (a full policy section, 1,000–2,000 tokens).
- Split each parent into children: small chunks (a sentence or two, ~100–300 tokens).
- Embed and index only the children, each storing its parent's ID.
- At query time, search the children: small chunks give sharp, precise matches.
- Return the parent (de-duplicated if several children match the same parent) to the LLM, so it sees the full context around the match.
- Variants: "sentence window" (return the matched sentence plus a few around it) and "small-to-big" retrieval are the same idea.
See it in code
- Needs
- Nothing: plain Python 3.
Fixed-size chunks with overlap vs sentence-aware chunks on the same text, then parent-child lookup.
text = ("Our warranty lasts two years. It covers hardware faults. "
"It does not cover water damage. To claim, email support with your order number. "
"Claims are answered within five days.")
def fixed_chunks(text, size=60, overlap=15):
"""Cut every `size` characters; repeat `overlap` characters so ideas aren't cut in half."""
step = size - overlap
return [text[i:i + size] for i in range(0, len(text), step) if text[i:i + size].strip()]
def sentence_chunks(text, max_chars=90):
"""Pack whole sentences into chunks up to max_chars: never cuts a sentence."""
chunks, cur = [], ""
for s in [s.strip() + "." for s in text.split(".") if s.strip()]:
if cur and len(cur) + len(s) + 1 > max_chars:
chunks.append(cur); cur = s
else:
cur = f"{cur} {s}".strip()
return chunks + [cur]
print("fixed-size with overlap:")
for c in fixed_chunks(text): print(" |", c)
print("sentence-aware:")
for c in sentence_chunks(text): print(" |", c)
# Parent-child: search small chunks, but hand the LLM the bigger parent around the match.
parents = sentence_chunks(text, max_chars=200)
children = [(pi, s) for pi, p in enumerate(parents) for s in sentence_chunks(p, max_chars=40)]
hit = next(pi for pi, s in children if "water" in s) # pretend vector search matched this child
print("matched child ->", [s for pi, s in children if "water" in s][0])
print("parent sent ->", parents[hit])fixed-size with overlap:
| Our warranty lasts two years. It covers hardware faults. It
| are faults. It does not cover water damage. To claim, email
| o claim, email support with your order number. Claims are an
| . Claims are answered within five days.
sentence-aware:
| Our warranty lasts two years. It covers hardware faults. It does not cover water damage.
| To claim, email support with your order number. Claims are answered within five days.
matched child -> It does not cover water damage.
parent sent -> Our warranty lasts two years. It covers hardware faults. It does not cover water damage. To claim, email support with your order number. Claims are answered within five days.Look at the fixed-size chunks: "are faults. It does not…" and "o claim, email…" are ugly but the overlap means every sentence appears whole in at least one chunk. Sentence-aware chunks read cleanly. Parent-child finds the precise sentence and still gives the model the whole warranty section. Real systems count tokens instead of characters; libraries such as LangChain's and LlamaIndex's text splitters do this for you.
A real example
Example
Product manuals: from 61% to 84% Recall@5
A hardware company's support bot used 1,000-character fixed chunks of its product manuals. Questions like "how do I reset the router?" often retrieved chunks that started halfway through the reset steps, or mixed two procedures.
They switched to structure-aware chunking (one chunk per manual section, split further by numbered step lists only when over 600 tokens), prefixed every chunk with "Product > Chapter > Section", and added parent-child retrieval so a matching step returned the whole procedure. On their 250-question eval, Recall@5 rose from 61% to 84%, and answers stopped skipping steps.
The easy way
Start here
Start with paragraph-aware chunks of about 300–500 tokens with a little overlap, and prefix each with its document title and heading. Measure Recall@5 on 30 real questions. Only try fancier strategies if that number is too low.
When you grow: the production setup
Teams pick chunking per document type (manuals by section, chats by conversation turn, code by function, tables whole), store chunking version in metadata, combine parent-child with contextual retrieval and reranking, and re-run retrieval evals whenever chunking changes, since it requires re-indexing.
Trade-offs
What you gain
- Good chunks make retrieval precise and answers complete.
- Parent-child gets precise matching and full context at the same time.
- Structure-aware chunking reuses the document's own organisation for free.
What it costs you
- No universal best size; it needs testing per corpus.
- Fancy strategies (semantic, parent-child) add complexity and storage.
- Changing chunking means re-embedding the whole corpus.
When not to use it
- Very short documents (FAQs, product cards) are already natural chunks: don't split them further.
Common mistakes
- Copying a chunk size from a tutorial without testing on your data.
- Cutting tables and code blocks in half.
- Chunks that lose their subject ("It does not cover…") because headings weren't attached.
- Returning many overlapping chunks from the same section, wasting the prompt.
- Changing chunking without re-indexing everything consistently.
Interview questions
Try answering out loud first, then open the model answer.
How do you choose a chunking strategy and size?
Model answer
Look at the documents and the questions. Prefer boundaries that match meaning: headings and sections for structured docs, paragraphs or sentences for prose, functions for code, whole tables. Add titles and heading paths to each chunk. Start around 300–500 tokens with modest overlap, then evaluate a few variants with Recall@k and answer quality on a labelled set, balancing precision, context completeness and prompt cost.
What is parent-child (small-to-big) retrieval and why does it help?
Model answer
Index small child chunks for precise similarity matching, each linked to a larger parent section. Retrieve by children, then pass the de-duplicated parents to the LLM. Small chunks avoid diluted embeddings and rank well; parents supply the surrounding context needed for complete, accurate answers. The trade-off is more storage and slightly more complex retrieval logic.
What are the signs that chunking is hurting your RAG system?
Model answer
Retrieved chunks start or end mid-thought, answers miss steps or conditions that sit just outside the retrieved chunk, many near-duplicate chunks fill the context, tables come back scrambled, or Recall@k is low even though the answer exists in the corpus. Inspecting retrieved chunks for failing questions usually reveals it quickly.
Quiz
Pick an answer to see whether it’s right and why.
0 of 6 answered
Key terms
- Chunk
- A small piece of a document (often a few hundred tokens) that is embedded, stored and retrieved as one unit.
- Chunk overlap
- Repeating a little text from the end of one chunk at the start of the next, so ideas near a boundary aren't lost.
- Parent-child chunking
- Searching small child chunks for precise matches, then giving the model the larger parent section they belong to.
- Semantic chunking
- Splitting text where its meaning changes, detected by comparing embeddings of neighbouring sentences.
- Token
- A small chunk of text (a word, part of a word or a symbol) that a language model reads and writes one at a time. Usage and prices are counted in tokens.
Before and after
Progress is saved in this browser only.