Welcome to the next lesson on preparing your knowledge base for Retrieval-Augmented Generation. In our last session, we implemented fixed-size chunking, a fast and simple method for splitting documents. We also uncovered its main weakness: by slicing text at arbitrary points, it can easily break apart sentences and destroy the semantic meaning we're trying to preserve.
This lesson introduces a more intelligent and widely-used approach that directly addresses this problem: recursive character-based chunking. Your goal is to understand and implement this strategy. We will explore its underlying recursive algorithm, which prioritizes a document's inherent structure—like paragraphs and sentences—to create more coherent and semantically meaningful chunks. This method is a significant step up in sophistication and is a cornerstone of most production-grade RAG systems.
The Problem with "Dumb" Splitting
As we saw previously, fixed-size chunking is oblivious to content. It's like cutting a photograph into equal-sized squares without any regard for the objects or people in the image. You might get a piece with just a nose, or half a tree, losing the context of the whole.
Recursive character chunking offers a more refined solution. It attempts to align the splits with the natural semantic boundaries of the text. The core principle is to split the text in the most meaningful way possible, and only break it down further if a resulting chunk is still too large.
The article from QualityPoint Technologies (QPT) provides a clear introduction to this topic. It starts by highlighting the issues with simpler methods before defining the recursive approach.
First, read the section The Problem With Simple Character Splitting to refresh your understanding of the limitations we're trying to overcome. Then, read the section What Is It? to grasp the fundamental principle of this method: preserving meaning first.
The Recursive Algorithm: A Divide-and-Conquer Approach
So, how does it work? The "recursive" part of the name is key. The algorithm uses a prioritized list of separators and attempts to split the text using them in order. If a split results in chunks that are still too large, it takes those oversized chunks and recursively applies the next separator in the list.
The default hierarchy of separators in most libraries, like LangChain, is designed for standard prose:
\n\n(Double newline) - Tries to keep paragraphs whole.\n(Single newline) - Tries to keep lines whole..or(Space) - Tries to split on sentences or words.- `` (Empty string) - As a last resort, splits by individual characters.
This flowchart provides a great visual representation of the decision-making process.

The following video segment provides an excellent walkthrough of this logic, explaining how the splitter intelligently "snaps" to the most appropriate semantic boundary.
LangChain: How to Properly Split your Chunks
This video from Prompt Engineering explains the hierarchical splitting process in detail.
Watch the section from the explanation where the presenter describes how the splitter moves from paragraphs to sentences to words, depending on the chunk_size constraint.
Implementing Recursive Chunking with LangChain
As a software engineer, you'll appreciate that while the from-scratch logic is interesting, you'll almost always use a library implementation in practice. LangChain's RecursiveCharacterTextSplitter is the de facto standard.
The following video from Greg Kamradt, a well-known practitioner in the RAG space, demonstrates this splitter and contrasts it with the fixed-size method we learned previously. He also uses a visual tool, chunkviz.com, which makes the difference in output crystal clear.
The 5 Levels Of Text Splitting For Retrieval
In his "5 Levels Of Text Splitting" video, Greg Kamradt introduces recursive character text splitting as "Level 2," the go-to method for most projects.
Watch the segment from the beginning. Pay close attention to how he explains the list of separators and how increasing the chunk_size allows the splitter to preserve entire paragraphs, which is the primary goal. The visualization at the end of the segment is particularly insightful.
Let's look at the code. The interface is nearly identical to the CharacterTextSplitter from our last lesson, but the underlying behavior is much smarter.
from langchain.text_splitter import RecursiveCharacterTextSplitter
# A sample text with distinct paragraphs.
pg_essay = """
One of the most important things I didn't understand about the world when I was a child is the degree to which the returns for performance are superlinear.
Teachers and coaches implicitly told us the returns were linear. "You get out," I heard a thousand times, "what you put in." They meant well, but this is rarely true. If your product is only half as good as your competitor's, you don't get half as many customers. You get no customers, and you go out of business.
It's obviously true that the returns for performance are superlinear in business. Some think this is a flaw of capitalism, and that if we changed the rules it would stop being true. But superlinear returns for performance are a feature of the world, not an artifact of rules we've invented. We see the same pattern in fame, power, military victories, knowledge, and even benefit to humanity. In all of these, the rich get richer.
"""
# Initialize the splitter.
# We choose a chunk size that is smaller than the second paragraph but larger than the first.
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=450,
chunk_overlap=0,
separators=["\n\n", "\n", " ", ""]
)
docs = text_splitter.create_documents([pg_essay])
# Let's inspect the results
for i, doc in enumerate(docs):
print(f"--- Chunk {i+1} ---")
print(f"Length: {len(doc.page_content)} characters")
print(f"Content: '{doc.page_content}'\n")
# --- Expected Output ---
# --- Chunk 1 ---
# Length: 157 characters
# Content: 'One of the most important things I didn't understand about the world when I was a child is the degree to which the returns for performance are superlinear.'
#
# --- Chunk 2 ---
# Length: 319 characters
# Content: 'Teachers and coaches implicitly told us the returns were linear. "You get out," I heard a thousand times, "what you put in." They meant well, but this is rarely true. If your product is only half as good as your competitor's, you don't get half as many customers. You get no customers, and you go out of business.'
#
# --- Chunk 3 ---
# Length: 420 characters
# Content: "It's obviously true that the returns for performance are superlinear in business. Some think this is a flaw of capitalism, and that if we changed the rules it would stop being true. But superlinear returns for performance are a feature of the world, not an artifact of rules we've invented. We see the same pattern in fame, power, military victories, knowledge, and even benefit to humanity. In all of these, the rich get richer."
Notice the output. The splitter successfully identified the double-newline \n\n separators and created a chunk for each paragraph. Because each paragraph's length was less than our chunk_size of 450, no further (and less desirable) splitting was necessary. This is precisely the behavior we want.
The image below shows the same text split with a chunk_size of 469, demonstrating how the splitter keeps paragraphs separate.

Under the Hood and Customization
For a deeper understanding of the algorithm, it's helpful to see a pure Python implementation. The following resource provides a recursive_chunk function that reveals the mechanics of the process. You don't need to memorize it, but reviewing its structure will solidify your understanding of the recursive calls and separator logic.
How to Implement Recursive Chunking - OneUptime
This article from OneUptime provides an excellent from-scratch implementation before moving on to library usage.
First, read the brief sections on Why It Matters and How It Works. Then, in the "Implementation from Scratch" section, examine the recursive_chunk Python function. Focus on its logic: the base cases for when the text is small enough or when separators run out, and the main loop that splits the text and makes the recursive call if a segment is still too large.
The true power of this method lies in its customizability. The default separator list ["\n\n", "\n", " ", ""] is great for prose, but what about code or Markdown? For RAG to be effective on structured documents, your chunking strategy must be structure-aware.
You can achieve this by providing a custom separators list to the RecursiveCharacterTextSplitter. For example, when chunking Python code, you might prioritize splitting on class and function definitions. For Markdown, you would prioritize splitting on headings.
How to Implement Recursive Chunking - OneUptime
This part of the OneUptime article gives concrete examples of customizing separators for different file types.
Read the section Chunking Strategy by Document Type. Study the provided separators lists for Markdown, Python, and HTML. This demonstrates how you can tailor the chunking logic to preserve the specific structure of your source data, which is a critical concept for building effective RAG systems.
Conclusion
In this lesson, we have significantly upgraded our document processing toolkit. By moving from fixed-size to recursive chunking, we've adopted a strategy that respects the semantic structure of our documents, leading to more coherent chunks and, ultimately, better retrieval performance.
Here are the key takeaways:
- Recursive character chunking is a "structure-aware" method that splits text along natural semantic boundaries.
- It uses a hierarchical list of separators (e.g., paragraphs, lines, words) and applies them recursively.
- This approach avoids the common pitfall of breaking sentences or ideas, which is a major weakness of fixed-size chunking.
- Libraries like LangChain provide robust implementations (
RecursiveCharacterTextSplitter) that are the recommended choice for production systems. - The real power of this technique comes from customizing the separator list to match the structure of your specific document type (e.g., Markdown, source code).
You are now able to load documents and split them into high-quality, semantically coherent chunks. But a chunk is only useful if you can find it when you need it. Our next lesson will focus on exactly that: attaching metadata to document chunks. This will allow us to perform filtered searches, a crucial capability for building powerful and precise RAG applications.