AI Engineering Learning Hub

Chunking Strategy

How to split documents into chunks for RAG: paragraph vs fixed-size vs recursive splitting, overlap, and the self-contained rule.

What it is

Chunking is splitting a document into smaller pieces before embedding them. Each piece, or chunk, becomes one row in your vector store and is the unit you retrieve. The chunk is what the LLM eventually reads, so its boundaries decide whether the model sees a complete fact or half of one.

There is no single correct chunk size. A legal contract, a Slack export and a resume need different strategies. The decision that matters is: when a chunk is pulled out on its own, with no neighbours, does it still make sense and still answer a question?

For this site I chose paragraph chunks. Every paragraph in my knowledge base is one complete idea: one job, one project, one skill group. That made the self-contained rule easy to satisfy and gave me 50-odd clean chunks instead of hundreds of fragments.

Why it's needed

If the whole document is one chunk, retrieval returns everything every time, the prompt fills with irrelevant text, cost goes up and the model gets distracted. If chunks are too small, a fact gets cut in half and the retrieved piece says 'he worked there for two years' with no mention of where.

Chunking also sets your retrieval precision ceiling. The embedding of a chunk is an average of everything in it, so a chunk that mixes three topics has a blurry vector that matches none of them well. Clean chunks produce sharp vectors.

How it works

  • Paragraph splitting: split on blank lines. Works when the author already wrote one idea per paragraph, which is true for resumes, FAQs and most documentation.
  • Fixed-size splitting: cut every N characters or tokens, typically 300 to 800 tokens. Simple and predictable, but it ignores structure and cuts mid-sentence.
  • Recursive splitting: try to split on headings, then paragraphs, then sentences, then characters, until every piece is under the size limit. This is what LangChain's RecursiveCharacterTextSplitter does and it is the sensible default for mixed documents.
  • Overlap: repeat the last 10 to 20 percent of one chunk at the start of the next, so a sentence that straddles a boundary appears whole in at least one chunk.
  • Make chunks self-contained: replace pronouns with names ('Abdur led the migration' instead of 'he led the migration') and keep the entity being described inside each chunk.
  • Add metadata: store the source file, heading and position with each chunk so you can filter, cite and debug retrieval later.

Code example

import re

def paragraph_chunks(text: str) -> list[str]:
    """One chunk per paragraph. Used for the portfolio knowledge base."""
    return [p.strip() for p in text.split("\n\n") if p.strip()]

def fixed_chunks(text: str, size: int = 500, overlap: int = 80) -> list[str]:
    """Fixed-size character windows with overlap, snapped to sentence ends."""
    sentences = re.split(r"(?<=[.!?])\s+", text)
    chunks, current = [], ""
    for s in sentences:
        if len(current) + len(s) > size and current:
            chunks.append(current.strip())
            current = current[-overlap:]  # carry the tail forward
        current += " " + s
    if current.strip():
        chunks.append(current.strip())
    return chunks

doc = open("knowledge_base.txt", encoding="utf-8").read()
paras = paragraph_chunks(doc)
fixed = fixed_chunks(doc)
print(len(paras), "paragraph chunks;", len(fixed), "fixed chunks")
print(paras[0][:120])

Where I used this

The portfolio backend splits knowledge_base.txt on blank lines, so each paragraph (one job, one project, one skill group) becomes a chunk. Paragraph chunks worked better than fixed-size windows because each fact stays intact and every chunk already names the subject.

Use cases

  • Preparing any long document (resume, manual, policy, contract) for RAG
  • Splitting PDFs or web articles into searchable units
  • Building a support knowledge base from existing help-centre articles
  • Indexing code repositories by function or class rather than by file

Common mistakes

  • Cutting mid-sentence or mid-table so the retrieved chunk is grammatically broken and the LLM has to guess the missing half.
  • Pronouns everywhere: a chunk that says 'it reduced costs by 30%' is useless on its own because 'it' is in the previous chunk.
  • Chunks that are too big: a 2000-token chunk has a vague embedding and wastes the context window on text that is mostly irrelevant.
  • Forgetting that the embedding model truncates: a 600-word chunk fed to MiniLM only has its first ~200 words embedded.
  • Never re-checking after the source changes: a new document format can silently break the splitter and produce one giant chunk.

Interview questions

What is the trade-off in choosing chunk size?
Small chunks give precise retrieval but can lose context, so an answer needs several of them. Large chunks keep context but have blurry embeddings and pull irrelevant text into the prompt. Most teams land between 200 and 500 words and then tune using a retrieval test set.
What does it mean for a chunk to be self-contained?
That a reader given only that chunk could answer a question about it without the surrounding text. In practice: full names instead of pronouns, the subject stated in each chunk, no 'as mentioned above'. This single rule fixed more retrieval misses for me than any model change.
Why use overlap between chunks?
So that a sentence or fact that lands on a boundary is present in full in at least one chunk. The cost is some duplicated text in the index. Paragraph chunking usually does not need overlap because paragraph boundaries are already natural breaks.
How would you chunk a document with headings and tables?
Split by heading first so every chunk belongs to one section, prepend the heading text to each chunk so the context travels with it, and keep tables whole or convert each row into a sentence. Recursive splitting with a size cap handles the rest.
How do you know your chunking is good?
Measure it. Write 20 to 30 real questions, note which chunk should answer each, and check how often that chunk appears in the top k. Change the strategy and re-run. Looking at a few results by hand is not evaluation.
Should chunk size depend on the embedding model?
Yes. Every embedding model has a maximum input length, and quality degrades before that limit. MiniLM handles 256 word pieces, roughly 200 English words, so chunks larger than that lose their tail. Larger-context models like text-embedding-3 allow bigger chunks.
Ask the AI assistant about this topic

Get new topics by email

One short, practical AI-engineering lesson whenever a new topic is published. No spam, unsubscribe any time.

Need this built for your product?

I build production RAG pipelines, LLM automations and voice agents. Open to freelance projects and full-time roles.