TechByteByByte

Text Splitting: Chunking Documents That Actually Work

See what real chunk sizes and overlap settings actually produce on real text, and understand why recursive, structure-aware splitting is the practical default.

#LangChain#Text Splitting#RAG

Recall Module 22’s PDF loader — one Document per page, sometimes thousands of words long. Handing an entire page to an embedding model buries the one relevant sentence a user’s question actually needs inside a huge amount of irrelevant surrounding text, hurting retrieval precision. Text splitters break documents into smaller, genuinely more useful pieces first.

A real piece of text to work with

text = """
LangChain provides a set of building blocks for LLM applications. It includes
chat models, prompt templates, and tools. Agents combine a model with tools in
a loop, deciding what actions to take based on the current state. Retrieval
lets an application ground its answers in real documents, rather than relying
purely on what a model memorized during training. Structured output turns a
model's reply into validated, typed data your application code can actually use.
"""

Example 1: a chunk size that’s too large

from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0, separator=" ")
chunks = splitter.split_text(text)

print(f"Number of chunks: {len(chunks)}")
for c in chunks:
    print("---", len(c), "characters ---")

With chunk_size=1000 on this short text, you’ll likely get back just one chunk — the whole thing, unsplit. For a genuinely long document, an oversized chunk buries the specific, relevant sentence inside a large block, hurting how precisely retrieval can find it.

Example 2: a chunk size that’s too small

from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(chunk_size=50, chunk_overlap=0, separator=" ")
chunks = splitter.split_text(text)

print(f"Number of chunks: {len(chunks)}")
for c in chunks:
    print(repr(c))

Now you’ll get many small chunks — and notice some genuinely cut a sentence in half, losing its complete meaning. A chunk too small can leave a retrieved piece without enough surrounding context to actually answer a question on its own.

Example 3: overlap, and why it genuinely helps

from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(chunk_size=150, chunk_overlap=30, separator=" ")
chunks = splitter.split_text(text)

for c in chunks:
    print(repr(c))
    print("---")

chunk_overlap=30 means each chunk repeats the last 30 characters of the previous one. This is a genuine, deliberate trade-off: a small amount of duplicated content, in exchange for protecting against a sentence or idea being awkwardly cut exactly at a chunk boundary, with its second half losing connection to its first half.

Example 4: recursive splitting — the practical default

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(chunk_size=150, chunk_overlap=30)
chunks = splitter.split_text(text)

for c in chunks:
    print(repr(c))
    print("---")

RecursiveCharacterTextSplitter is genuinely smarter than the plain version from Examples 1-3: rather than cutting at a fixed character count regardless of what’s there, it tries splitting on paragraph breaks first, then sentences, then words, only falling back to a hard character cut as a last resort. This is why it’s the practical, real default for most text — it respects the actual structure of the writing wherever it reasonably can.

Example 5: structure-aware splitting for code

from langchain_text_splitters import RecursiveCharacterTextSplitter, Language

python_code = """
def get_weather(city):
    return f"Weather data for {city}"

class WeatherService:
    def __init__(self, api_key):
        self.api_key = api_key

    def fetch(self, city):
        return get_weather(city)
"""

splitter = RecursiveCharacterTextSplitter.from_language(
    language=Language.PYTHON, chunk_size=100, chunk_overlap=0
)
chunks = splitter.split_text(python_code)

for c in chunks:
    print(repr(c))
    print("---")

Notice this splitter understands Python’s actual structure — it prefers splitting between functions and classes, rather than in the middle of one, keeping each chunk’s code genuinely coherent. This matters directly for code-assistant applications, echoing Module 16’s Agent 10, where a chunk that cuts a function definition in half is far less useful than one that keeps it whole.

Common mistakes worth avoiding

Picking a chunk size without ever looking at what it actually produces. Recall Examples 1 and 2 — the only reliable way to know if a chunk size is genuinely right for your content is to print real chunks and read them, exactly as this module did, rather than trusting a number that “sounds reasonable.”

Using chunk_overlap=0 by default. It’s a smaller file size, but recall Example 3’s real trade-off — zero overlap means any idea that happens to fall exactly on a chunk boundary loses its connection to the content before or after it. A small, deliberate overlap is usually worth its modest cost in duplicated content.

Using the plain CharacterTextSplitter for genuinely structured content, like code. Recall Example 5 — RecursiveCharacterTextSplitter.from_language() understands real code structure; the plain splitter from Examples 1-3 doesn’t, and will cut a function definition in half without any awareness that it’s done something genuinely disruptive.

What you should take away from this module

  • Chunk size is a genuine trade-off: too large buries relevant content in noise; too small loses necessary context, sometimes mid-sentence.
  • chunk_overlap deliberately duplicates a small amount of content across chunk boundaries, protecting against ideas being awkwardly split apart.
  • RecursiveCharacterTextSplitter is the practical default — it respects paragraph and sentence structure before falling back to a hard character cut.
  • Structure-aware splitting, like Language.PYTHON, respects a document’s actual real structure — genuinely important for code and other structured content.

Where this goes next

The next module covers Embeddings — the actual mechanism that turns these text chunks into the numerical vectors a vector store can search by meaning, not just matching words.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed