GenAI Course · Module 9 · Session 33

Chunking Strategies

⏱ 13 min read🎯 Intermediate🖼 2 diagrams🧪 1 live demo🐍 Python code

We cannot send a 200-page PDF to the retriever as one piece. We cut it into chunks. Cut too small and the meaning breaks; cut too big and the search becomes vague. Let us compare the main strategies on a real FD policy and see their actual output.

In this session

  • Why chunk size matters
  • Fixed-length vs recursive chunking (real outputs)
  • Semantic chunking
  • Structure-aware and parent–child chunking
  • Metadata that makes filtering and citations possible

01Why chunk size matters

Chunks too small• “reduces the interest rate by 1%”• missing: reduces WHAT?• many chunks, little contextChunks too big• whole 10-page section• one vector for many topics• search becomes blurry, costly prompts
Good chunks hold one complete idea: usually a few sentences to a paragraph.
🍕

Like cutting a pizza. Too many tiny pieces and each bite has no toppings; one giant piece and nobody can eat it. You want slices that each carry a full taste.

Typical starting point: 300–800 tokens per chunk with 10–20% overlap. Then measure retrieval quality (Session 36) and adjust.

02Fixed-length vs recursive chunking

Fixed-length cuts every N characters, even in the middle of a word. Recursive chunking tries to cut at natural boundaries first (blank lines, then lines, then spaces), and only goes smaller if needed. Here is the real output on our FD policy:

chunking.py
from langchain_text_splitters import RecursiveCharacterTextSplitter, MarkdownHeaderTextSplitter, CharacterTextSplitter

policy = """# Fixed Deposit Policy

## 1. Eligibility
Any resident individual above 18 years can open an FD. Minors can open an FD through a guardian.

## 2. Interest rates
Rates depend on tenure. Senior citizens get an extra 0.50% on all tenures.

## 3. Premature withdrawal
Breaking an FD early reduces the interest rate by 1%. Tax-saver FDs cannot be withdrawn before 5 years."""

fixed = CharacterTextSplitter(separator="", chunk_size=120, chunk_overlap=0)
print("FIXED:", len(fixed.split_text(policy)))
for c in fixed.split_text(policy)[:2]: print(repr(c))

rec = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=20)
chunks = rec.split_text(policy)
print("RECURSIVE:", len(chunks))
for c in chunks: print(repr(c))

md = MarkdownHeaderTextSplitter(headers_to_split_on=[("#", "doc"), ("##", "section")])
for d in md.split_text(policy): print(d.metadata, "|", d.page_content[:60])
FIXED: 4 '# Fixed Deposit Policy\n\n## 1. Eligibility\nAny resident individual above 18 years can open an FD. Minors can open an FD t' 'hrough a guardian.\n\n## 2. Interest rates\nRates depend on tenure. Senior citizens get an extra 0.50% on all tenures.\n\n##' RECURSIVE: 3 '# Fixed Deposit Policy\n\n## 1. Eligibility\nAny resident individual above 18 years can open an FD. Minors can open an FD through a guardian.' '## 2. Interest rates\nRates depend on tenure. Senior citizens get an extra 0.50% on all tenures.' '## 3. Premature withdrawal\nBreaking an FD early reduces the interest rate by 1%. Tax-saver FDs cannot be withdrawn before 5 years.' {'doc': 'Fixed Deposit Policy', 'section': '1. Eligibility'} | Any resident individual above 18 years can open an FD. Minor {'doc': 'Fixed Deposit Policy', 'section': '2. Interest rates'} | Rates depend on tenure. Senior citizens get an extra 0.50% o {'doc': 'Fixed Deposit Policy', 'section': '3. Premature withdrawal'} | Breaking an FD early reduces the interest rate by 1%. Tax-sa

See the difference: fixed-length broke "FD t" | "hrough" in the middle of a word, while recursive gave exactly one clean section per chunk. The Markdown splitter (last part) also attached the section name as metadata.

03Semantic chunking

Semantic chunking embeds each sentence and starts a new chunk where the meaning changes, that is, where the similarity between neighbouring sentences drops sharply.

Demo · Where does the topic change?
Trade-off: semantic chunking gives cleaner topics but costs an embedding call per sentence at indexing time and is harder to predict.

04Structure-aware and parent–child chunking

Parent–child chunkingParent: Section 3Premature withdrawal (whole section)Child 1“Breaking an FD early reduces the rate by 1%.”Child 2“Tax-saver FDs: no withdrawal before 5 years.”Child 3“Request through app or branch.”
Search matches a small child chunk, but the LLM receives the whole parent section.
StrategyBest forEffort
Fixed-lengthquick prototypes, uniform textlowest
Recursivegeneral default for most documentslow
Semanticlong text with topic shifts and few headingsmedium
Structure-awarepolicies, manuals, HTML/Markdown, reportsmedium
Parent–childprecise search + full context answersmedium

05Metadata design for filtering

Every chunk should carry metadata. It enables filters (“only FD policies from 2026”), citations (“p.4”) and access control (“HR documents only for HR”).

chunk_metadata.json
{
  "text": "Breaking an FD early reduces the interest rate by 1%.",
  "source": "FD_Policy_2026.pdf",
  "page": 4,
  "section": "3. Premature withdrawal",
  "doc_type": "policy",
  "department": "retail_banking",
  "effective_date": "2026-04-01",
  "access_level": "all_employees",
  "chunk_id": "fd2026-p4-c2"
}
Design tip: decide your filters first (by product, date, department, access level), then make sure every chunk has those fields. Adding metadata later means re-indexing everything.

06Summary

07Quick quiz

1. What went wrong with fixed-length chunking in our example?

2. Recursive chunking first tries to split at…

3. In parent–child chunking, what is sent to the LLM?

4. Semantic chunking cuts where…

5. Which metadata field enables access control?

08References

Created by Ajeetkumar