In 1966, a computer scientist at MIT named Joseph Weizenbaum wrote a program called ELIZA. ELIZA pretended to be a psychotherapist. You typed a sentence, and it typed back something that sounded thoughtful. Type "I am feeling sad" and ELIZA would answer "Why do you say you are feeling sad?" Type "My mother annoys me" and it would answer "Tell me more about your family." Some of the people who tested it refused to believe they were talking to a program — they thought ELIZA actually understood them. Weizenbaum was disturbed by this, because he knew exactly how little ELIZA understood. It never learned anything, never read a book, never "thought" about family or sadness. It just searched your sentence for a handful of keywords like "mother" or "sad," picked a pre-written template that matched, and slotted your own words back into it. That is the entire trick. In this chapter, you are going to build that same trick yourself, in Python, and by the end you will know exactly why it works, exactly why it breaks, and exactly what separates it from the AI chatbots you use today.
What "Rule-Based" Actually Means
A rule-based chatbot is a program that answers using a fixed list of if-this-then-that instructions written entirely by a human programmer, before the program ever runs. There is no learning, no training data, and no adjustment based on past conversations. If a rule says "if the message contains the word 'homework', reply with the assignment link," then that is exactly what happens, every single time, for every user, forever — unless a programmer opens the code and edits the rule by hand.
This is different from the AI chatbots you may have used (like the assistant answering your questions right now), which are built on machine learning models trained on huge amounts of text so they can generate a fresh response to sentences they have never seen before. A rule-based bot cannot do that. It can only recognize patterns that a human explicitly told it to look for. That sounds primitive, and in some ways it is — but rule-based bots are still exactly what runs behind a huge number of real systems: a bank's SMS keyword service ("type BAL to check your balance"), a college's FAQ bot on WhatsApp, or the automated "Press 1 for English" voice menu you hear when you call a customer care number. They are cheap to build, completely predictable, and never say anything embarrassing, because they can only ever say what you programmed them to say. That predictability is precisely why they are still worth learning to build.
Version 1: The Simplest Possible Bot
Let's start with the smallest chatbot that can exist — one rule, checked with a single if statement.
user_input = input("You: ")
if user_input == "hi":
print("Bot: Hello!")
elif user_input == "bye":
print("Bot: Goodbye!")
else:
print("Bot: I don't understand.")
Run this and type exactly hi and it replies "Hello!" as expected. Now type Hi with a capital H, or hi there with an extra word. Both fall straight into the else branch and get "I don't understand." Why? Because == checks for an exact match, character by character. Python treats "Hi" and "hi" as completely different strings, the same way it treats 7 and "7" as different values. A human reading "Hi there" instantly knows the person is greeting them; your program, as written, sees two totally unrelated pieces of text. This is the first and most important lesson of building a chatbot: a computer never sees a "meaning," it only ever sees a sequence of characters, and every rule you write is really a statement about how those characters must be arranged.
Version 2: Normalizing and Matching Substrings
The fix for capitalization is to force every input into one consistent shape before comparing it — this is called normalization. We use Python's built-in .lower() method:
user_input = input("You: ").lower()
if "hi" in user_input:
print("Bot: Hello!")
elif "bye" in user_input:
print("Bot: Goodbye!")
else:
print("Bot: I don't understand.")
Notice two changes: we lowercase the input, and we switched from == to in. The in operator on strings checks whether one string appears anywhere inside another — it is a substring test, not an exact-match test. Now "Hi there, how are you?" becomes "hi there, how are you?" after lowering, and since the three characters h, i appear inside that longer string, "hi" in user_input evaluates to True. Progress. But this fix quietly introduces a new bug, and it is exactly the kind of bug that shows up in real production code, so let's find it deliberately.
Type the sentence: "I need help with my history project". Trace it by hand. Lowercased, it is "i need help with my history project". Now ask: is the two-character string "hi" found anywhere inside that sentence? Look at the word "history": h-i-s-t-o-r-y. The very first two letters are h and i. So "hi" in "i need help with my history project" is True — and the bot cheerfully replies "Hello!" to a student asking for help with their History project. The substring check does not care about word boundaries at all; it will happily match "hi" inside "history," "this," "chill," or "which." This is a genuine, common failure mode of naive keyword matching, and it is worth remembering: checking "does this text contain this snippet" is not the same as "does this text contain this word."
Version 3: Matching Whole Words, Correctly
The fix is to break the sentence into individual words and check whether our keyword is one of those exact words — not just a fragment hiding inside a longer word. Python's .split() method does exactly this: it breaks a string into a list of words wherever it finds a space.
text = "i need help with my history project"
words = text.split()
print(words)
# ['i', 'need', 'help', 'with', 'my', 'history', 'project']
print("hi" in words) # False — "hi" is not one of these words
Trace it: words is now a list of seven separate strings. Checking "hi" in words asks "is the exact string 'hi' one of the items in this list?" — and since the list contains 'history' as one indivisible item (not 'hi' followed by 'story'), the answer is correctly False. This one change — splitting into words before matching, instead of scanning raw characters — fixes the false-positive bug completely.
But there is still a second, sneakier problem, and you should hunt for it before reading on: what happens with punctuation? Try the sentence "Hi, when is my exam?". Lowercased: "hi, when is my exam?". Split by spaces: ['hi,', 'when', 'is', 'my', 'exam?']. Look closely at the first and last items — they are 'hi,' with a trailing comma, and 'exam?' with a trailing question mark, not the clean words 'hi' and 'exam'. A check like "hi" in words would now fail, because the list contains 'hi,', not 'hi' — and these are different strings to Python. So we need to strip punctuation too, before splitting:
def clean(text):
text = text.lower()
for punct in ".,!?":
text = text.replace(punct, "")
return text
print(clean("Hi, when is my exam?"))
# "hi when is my exam"
Trace this function on the input "Hi, when is my exam?". First, .lower() gives "hi, when is my exam?". Then the loop runs four times, once per punctuation character: replacing "." does nothing (there isn't one), replacing "," turns it into "hi when is my exam?", replacing "!" does nothing, and replacing "?" gives the final result "hi when is my exam". Now .split() on this clean string correctly produces ['hi', 'when', 'is', 'my', 'exam'], with "hi" and "exam" as clean, matchable words. This two-step process — clean the text, then split it into words, then check word membership — is the standard, correct way to do keyword matching, and it is the backbone of nearly every rule-based system you will ever build.
Version 4: Many Rules, Organized as a Dictionary
A real chatbot needs more than two or three rules, and a long chain of if / elif / elif / elif... becomes hard to read and easy to break. A cleaner structure is to store the rules in a Python dictionary, where each key is a tuple of keywords and each value is the response to give when one of those keywords is found:
def clean(text):
text = text.lower()
for punct in ".,!?":
text = text.replace(punct, "")
return text
rules = {
("hi", "hello", "hey"): "Hello! How can I help you today?",
("homework", "assignment"): "Please check the assignment tab on AICI.",
("exam", "test", "marks", "datesheet"):
"The CBSE datesheet is usually released in December-January. Check cbse.gov.in for the official date.",
("thanks", "thank"): "You're welcome! Keep learning.",
}
def get_response(user_input):
words = clean(user_input).split()
for keywords, response in rules.items():
if any(word in keywords for word in words):
return response
return "I didn't quite get that. Try asking about homework or exams."
Read get_response one line at a time. First, words = clean(user_input).split() turns the raw sentence into a clean list of words, exactly as before. Then for keywords, response in rules.items() walks through the dictionary one rule at a time, in the order the rules were written — rules.items() gives you each key-value pair, so on the first loop pass keywords is the tuple ("hi", "hello", "hey") and response is "Hello! How can I help you today?". The line any(word in keywords for word in words) is a compact way of asking: "going through every word in the user's sentence, is any of them found inside this rule's keyword tuple?" If yes, we immediately return that rule's response and the function ends right there — the remaining rules are never even checked. If no rule matches after the loop finishes, the function falls through to the final line and returns the fallback message.
Now trace a tricky example: get_response("Hi, when is my exam?"). After cleaning and splitting, words is ['hi', 'when', 'is', 'my', 'exam']. The loop checks the first rule, ("hi", "hello", "hey"): is any word in this list found in that tuple? Yes — 'hi' is literally the first word in words, and it is in the tuple. So any(...) is True on the very first rule, and the function returns "Hello! How can I help you today?" immediately — even though the student was actually asking about an exam date. The exam rule, sitting later in the dictionary, is never reached at all, because Python dictionaries (from Python 3.7 onward) iterate in the exact order the entries were written, and our loop returns on the very first match it finds.
This is not a bug in the code — the code is doing exactly what it was told. It is a design flaw in the rules themselves, and it is one of the most important lessons a rule-based system teaches you: when more than one rule could match the same message, the order in which you write your rules decides the outcome. A real chatbot builder has to think about this on purpose — for instance, by checking the more specific, information-seeking rules (like "exam") before the generic social ones (like "hi"), or by scanning the whole sentence for every matching rule and combining the responses instead of stopping at the first hit. There is no single "correct" fix; the point is that ordering is a design decision you must make deliberately, not an accident to discover later when a real user gets confused.
Version 5: Giving the Bot Memory
Every version so far treats each message as if it just fell out of the sky, with zero memory of anything said before. That is actually true of the get_response function itself — call it twice in a row and it has no idea the two calls are related. But a chatbot can still feel like it remembers things, if the surrounding program stores information in a variable and reuses it. Here is a complete, runnable program that does this:
def clean(text):
text = text.lower()
for punct in ".,!?":
text = text.replace(punct, "")
return text
rules = {
("hi", "hello", "hey"): "Hello! How can I help you today?",
("homework", "assignment"): "Please check the assignment tab on AICI.",
("exam", "test", "marks", "datesheet"):
"The CBSE datesheet is usually released in December-January. Check cbse.gov.in for the official date.",
("thanks", "thank"): "You're welcome! Keep learning.",
}
def get_response(user_input):
words = clean(user_input).split()
for keywords, response in rules.items():
if any(word in keywords for word in words):
return response
return "I didn't quite get that. Try asking about homework or exams."
print("Bot: Hi! I'm the AICI Study Buddy. What's your name?")
name = input("You: ").strip().title()
print(f"Bot: Nice to meet you, {name}! Ask about homework or exams. Type 'bye' to quit.")
while True:
user_input = input(f"{name}: ")
if "bye" in clean(user_input).split():
print(f"Bot: Goodbye, {name}! Good luck for your exams.")
break
print("Bot:", get_response(user_input))
Walk through what happens when this runs. The program prints its opening question, then name = input("You: ").strip().title() waits for a reply — if the student types " shivani " with stray spaces, .strip() removes them and .title() capitalizes it into "Shivani", which is stored in the variable name. That variable does not disappear after the line runs; it stays in memory for the rest of the program. Every message printed afterward, inside the while True loop, reuses it — the prompt shows f"{name}: ", so the terminal literally displays "Shivani: " before each input, and the farewell message says "Goodbye, Shivani!" This is the entire secret behind a chatbot "remembering your name": there was never any understanding involved, just one ordinary variable, read again later. The while True loop itself keeps the conversation going indefinitely, checking after every message whether the cleaned, split input contains the word "bye," and only then breaking out with break; otherwise it calls get_response and prints whatever that function returns, then loops back to ask for the next line.
How the Program Decides: A Flowchart
Before moving on, it helps to see the whole decision process as a single picture rather than as separate code snippets. Every message the bot receives goes through exactly the same four stages: clean it, split it into words, check it against each rule in order, and either answer or fall back.
Notice that the diamond-shaped decision box is inside a loop: if the current rule does not match, control goes back up and tries the next rule, until either a rule matches (green box) or the rules run out (red fallback box). This loop-until-match-or-exhausted shape is the core algorithm of every rule-based system, whether it has 4 rules or 4,000.
Where Rule-Based Bots Break Down
Once you have built and traced a rule-based bot yourself, its limitations stop being abstract warnings and become things you have personally watched happen. A student who types "kal exam hai kya" (mixing Hindi and English, as many Indian students naturally do while chatting) will not match any English keyword rule at all, even though a human reading it instantly understands the question. A student who types "eczam" as a typo, or "when's the test" using a contraction, will also miss, because the code only recognizes the exact strings a programmer thought to list. A rule-based bot cannot generalize from "exam" to "test" unless a human explicitly adds "test" to that rule's tuple — it has no concept that the two words are related in meaning. This is precisely the gap that machine-learning-based chatbots are built to close: instead of a human writing every rule by hand, an ML model is trained on enormous amounts of real text so it can recognize that "exam," "test," and "board paper" are related, even for word combinations no one explicitly programmed.
It's worth stating a common misconception directly, since it trips up almost everyone the first time: not every chatbot is "an AI." The word "chatbot" only describes what a program does — hold a back-and-forth text conversation — not how it does it. The rule-based bot you just built is a completely ordinary program made of if statements, dictionaries, and string methods; there is no learning algorithm anywhere inside it, and calling it "AI" would be inaccurate. Many real customer-support bots on Indian websites and apps genuinely are rule-based systems just like this one, wired up to a bigger set of keyword rules — and knowing how to build one yourself is what lets you tell the difference between a chatbot that is quietly running an if-elif chain and one that is running a trained language model, just by testing it with a slightly unusual phrasing and seeing whether it breaks.
Summary
- A rule-based chatbot answers using a fixed set of human-written if-this-then-that rules, checked in order, with no learning involved.
- Raw text must be normalized (lowercased) and cleaned (punctuation stripped) before matching, or identical-looking inputs like "Hi" and "hi," will be treated as different.
- Matching on whether a keyword is a substring of the whole message (using
inon the raw string) causes false matches — "hi" hides inside "history." Splitting into a list of separate words and checking membership in that list fixes this. - Storing rules as a dictionary of keyword-tuples mapped to responses, and looping through them with
any(), scales better than a longif/elifchain — but the loop still stops at the first matching rule, so rule order is a real design decision, not a detail. - A chatbot can appear to "remember" things like a user's name only because the surrounding program stores that value in an ordinary variable and reuses it — the response-matching function itself has no memory at all.
- Rule-based bots cannot generalize to synonyms, typos, or mixed-language input they weren't explicitly programmed for; that limitation is exactly what motivates machine-learning-based chatbots, which is a different, later topic.
Practice
- Trace the
get_responsefunction from Version 4 by hand for the input"THANK YOU so much!!". Write out the value ofwordsafter cleaning and splitting, then state which rule (if any) matches, and what gets returned. - The rules dictionary currently has no rule for the keyword "syllabus." Write the new dictionary entry you would add, and explain where in the dictionary you would place it and why the position might matter.
- A student types
"hi, thanks for the homework help". Using the current rule order in Version 4/5, which single response will the bot give, and which two other rules does it wrongly skip? Explain, in one or two sentences, why this happens. - Explain, in your own words, why
"hi" in "chill out"evaluates toTruein Python, while"hi" in "chill out".split()evaluates toFalse. Your explanation should mention what each side ofinactually is (a string versus a list) in both cases. - A friend says, "Any chatbot is AI, because it talks like a person." Using what you built in this chapter, write a two-to-three sentence reply that corrects this, using the word "rule-based" and naming one specific limitation you personally traced above.