Build a DSA Learning Assistant

Build a browser-based DSA tutor powered by Google Gemini and Flask.

Introduction

30 Second Summary

Studying data structures and algorithms is one of those things where having someone to explain concepts at your pace makes all the difference. What if you could build your own personal tutor that never gets tired of your questions?

In this project, you will build a browser-based chat application powered by Google's Gemini API that acts as your personal DSA tutor. You type a question, and it responds with structured, step-by-step explanations complete with Python code examples.

What You'll Build

You type "explain binary search trees" into a clean chat interface, hit send, and get back a tailored explanation with Python code, complexity analysis, and a suggestion for what to learn next.

By the end of this project, you'll have:

  • A working AI learning assistant running in your browser where you can ask any DSA question and get a structured, step-by-step explanation with Python examples.
  • Hands-on experience with system prompt engineering that shapes a general-purpose LLM into a focused tutor with a consistent teaching style.
  • A Flask web app with conversation memory so you can say "give me a harder example" and the assistant knows exactly what you were discussing.
  • Secret Mission: Add a "quiz me" toggle that switches the assistant from explanation mode to quiz mode, testing your understanding with targeted questions.

Are there any prerequisites?

You should be comfortable with intermediate Python (functions, loops, dictionaries) and have a basic familiarity with HTML. No prior experience with AI APIs or Flask is needed.

Before We Start

To build our DSA learning assistant, we need three things in place: the Google GenAI SDK to talk to Gemini, Flask to serve the web interface, and an API key to authenticate with Google's LLM service.

In this step, you will install both packages, grab your free Gemini API key, and confirm everything connects successfully.

In this step, get ready to:
  • Install the Google GenAI SDK and Flask.
  • Create and store your Gemini API key.
  • Verify the SDK connects to Gemini from a test script.
Install the required Python packages

The google-genai SDK is Google's official Python library for interacting with Gemini models. Flask is a lightweight web framework you will use later to serve your chat interface.

  • In VS Code, open a new terminal by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Install both packages by running this command:
pip install google-genai==2.11.0 Flask==3.1.3

You will see download progress bars followed by a success message once both packages finish installing.

  • Confirm both packages installed by running:
pip show google-genai Flask

You should see version 2.11.0 for google-genai and version 3.1.3 for Flask in the output.

Seeing 'pip not found'?

Try using pip3 instead of pip. On some systems, Python 3 uses pip3 as the default command.

If that still fails, make sure Python 3.13 is on your system PATH. You can verify by running python --version in your terminal.

Help me fix my pip installation issue.

Get your Gemini API key

An API key is a unique string that identifies your requests to Google's servers. The Gemini free tier gives you access to Gemini 2.5 Flash at no cost.

  • Open your browser and go to:
  • https://aistudio.google.com/app/apikey
  • Sign in with your Google account if prompted.
  • Click Create API key.
  • Copy the key that appears. You will only see the full key once, so keep this tab open until you have pasted it into your terminal.

What is an API key?

Think of an API key like a library card. It tells Google who is making the request so it can track usage and enforce rate limits. Your key is free, but treat it like a password. Do not commit it to Git or share it publicly.

  • Back in your VS Code terminal, set the key as an environment variable so your Python code can read it.

macOS

  • Run this command, replacing your-api-key-here with the key you just copied:
export GEMINI_API_KEY=your-api-key-here

Windows

  • Run this command, replacing your-api-key-here with the key you just copied:
set GEMINI_API_KEY=your-api-key-here

Why an environment variable?

Storing the key in an environment variable keeps it out of your source code. If you close this terminal and open a new one, you will need to set the variable again in that new session.

Verify the connection works

Now that the SDK and key are in place, you will create a small test script to confirm everything connects.

  • In VS Code, create a new file called assistant.py by clicking File then New File.
  • Save it as assistant.py on your Desktop.
  • Add the following test code to assistant.py:
# empty file created to verify SDK import
from google import genai
client = genai.Client(api_key=__import__('os').environ.get('GEMINI_API_KEY'))
print('SDK ready')

What does this code do?

  • from google import genai imports the official Google GenAI SDK.
  • genai.Client(api_key=...) creates a client that authenticates with your API key from the environment variable.
  • print('SDK ready') confirms the import and client creation both succeeded without errors.
  • Save assistant.py.

Before you run this, what do you expect to see if everything is set up correctly?

  • Run the script by entering this command in your terminal:
python assistant.py

You should see SDK ready printed with no errors. This confirms the SDK is installed, your API key is set, and the client can be created successfully.

Seeing a ModuleNotFoundError?

This means google-genai did not install into the same Python that your terminal is using. Try running python3 assistant.py instead. If that works, use python3 for all commands going forward.

If you see a warning about GEMINI_API_KEY being None, make sure you set the environment variable in the same terminal session where you are running the script.

Help me fix my SDK import error.

✔️ Awesome, I've got everything!

Great. Double check you have saved assistant.py and that your terminal printed SDK ready with no errors.

ⓧ I'd like to double check the full code

Here is the complete assistant.py file at this point:

# empty file created to verify SDK import
from google import genai
client = genai.Client(api_key=__import__('os').environ.get('GEMINI_API_KEY'))
print('SDK ready')

Your environment is ready. Next up, you will send your first question to Gemini and see what a raw, un-guided LLM response looks like.

Get Your First Response from Gemini

Your SDK is installed and your API key is ready. Now it is time to actually talk to Gemini and see what comes back.

Before building a tutor, you need to confirm the API works and see what a raw, un-guided LLM response looks like. This exposes an important problem that you will solve in the next steps.

In this step, get ready to:
  • Send your first DSA question to Gemini and see its response.
  • Discover the limitations of single-shot API calls.
Send a DSA question to Gemini

The generate_content() method sends a single prompt to the model and gets a response back. Think of it like asking one question to a stranger. They answer, but they have no context about you or anything you discussed before.

  • In VS Code, switch back to assistant.py.
  • Select all the existing code and replace it with the following:
import os
from google import genai

# Create a client using your API key
client = genai.Client(api_key=os.environ.get('GEMINI_API_KEY'))

# Send a single question to Gemini
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Explain how a linked list works'
)
print(response.text)

What does this code do?

  • import os gives you access to environment variables where your API key lives.
  • client.models.generate_content() sends a one-shot request to the Gemini model. You specify which model to use and what to ask.
  • response.text contains the model's answer as a plain string.

Before you run this, what kind of response do you expect? Will it be tailored to your level, or more of a textbook-style generic explanation?

  • Save assistant.py.
  • Run the script in your terminal:
python assistant.py

You should see Gemini's explanation of linked lists printed to your terminal. The response is correct, but notice how it is generic. It might be too basic, too advanced, or not structured in a way that helps you learn step-by-step.

Seeing an error instead of a response?

  • If you see a permissions or authentication error, make sure your GEMINI_API_KEY environment variable is set in the same terminal session where you run the script.
  • If you see a module not found error, confirm you installed the SDK with pip show google-genai.

Still stuck? Ask for help.

Observe the memory problem

The response worked, but what happens if you try to ask a follow-up question that references the first one? A real tutor would understand context like "now explain the doubly linked version." Let's see if Gemini can.

  • In assistant.py, add the following code below the existing print(response.text) line:
# Try a follow-up question that references the first
response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Now explain doubly linked lists'
)
print(response2.text)

What does this code do?

This sends a second, completely independent request to Gemini. The word "Now" implies a continuation, as if the model remembers explaining linked lists a moment ago. Each generate_content() call is stateless. The model receives only the text you pass in contents and nothing else.

Before you run this, do you think the model will treat the second question as a follow-up to the first? Or will it answer as if it has never heard of linked lists from you?

  • Save assistant.py and run it again:
python assistant.py

You will see two separate responses. The second response explains doubly linked lists from scratch. It has no idea you just asked about singly linked lists. The word "Now" meant nothing to it because each call is completely independent.

Why is this a problem for a learning assistant?

A good tutor builds on what it already taught you. If you say "give me a harder example," it should know what topic you are working on. Right now, the model has two limitations holding it back:

  • No direction: responses are generic because the model has no instructions about how to teach or at what level.
  • No memory: each call is a blank slate with no history of the conversation.

You will fix the first problem in the next step with a system instruction. You will fix the second in the step after that with conversation memory.

Both responses look identical?

  • Make sure you have two separate generate_content() calls with different contents values. The first asks about linked lists, the second asks about doubly linked lists.
  • Check that you are printing both response.text and response2.text.

Need a hand debugging?

✔️ Awesome, I've got everything!

Great. Double check you have saved assistant.py with both API calls.

ⓧ I'd like to double check the full code

Here is the full assistant.py at this point:

import os
from google import genai

client = genai.Client(api_key=os.environ.get('GEMINI_API_KEY'))

response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Explain how a linked list works'
)
print(response.text)

response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Now explain doubly linked lists'
)
print(response2.text)

You have confirmed the API works and exposed two clear gaps. The responses are correct but generic, and the model forgets everything between calls. Next up, you will fix the first gap by giving Gemini a detailed system instruction that transforms it into a focused DSA tutor.

Engineer the Tutor Persona

In Step 2, you saw the problem firsthand. Gemini gives a correct answer, but it is generic and unfocused.

A system instruction tells the model WHO it is, HOW to respond, and WHAT format to follow. In this step, you will write a system prompt that transforms Gemini from a general-purpose LLM into a focused DSA tutor that explains at your level, uses Python examples, and follows a consistent teaching structure.

In this step, get ready to:
  • Add a system instruction that defines the tutor's personality, rules, and response format.
  • Test the guided responses and compare them to the generic output from Step 2.
  • Observe the remaining limitation: each call is still stateless with no conversation memory.
Add a system instruction to your generate_content call

The google-genai SDK lets you pass a system instruction through the config parameter. This instruction is invisible to the user but shapes every response the model generates.

  • In assistant.py, find the line from google import genai.
  • Add this new import directly below it:
from google.genai import types
  • Below the client = genai.Client(...) line, add the system instruction constant by pasting this code:
SYSTEM_INSTRUCTION = """You are a Data Structures and Algorithms (DSA) tutor for an intermediate Python programmer who is just starting to learn DSA concepts.

Your rules:
1. Explain concepts step-by-step, starting from the intuition before diving into implementation.
2. Always use Python for code examples.
3. When explaining a data structure, cover: what it is, why it exists, when to use it, and a simple Python implementation.
4. When explaining an algorithm, cover: the problem it solves, the intuition behind it, step-by-step walkthrough with a small example, Python code, and time/space complexity.
5. Keep explanations concise but thorough. Use analogies when helpful.
6. If the user asks a vague question, ask a clarifying question before answering.
7. Encourage the user and suggest related topics they might want to explore next."""

What does this system instruction do?

Each numbered rule constrains the model's behavior. Rule 1 forces step-by-step structure instead of a wall of text. Rule 2 ensures Python examples instead of pseudocode. Rules 3 and 4 define consistent response templates for data structures vs algorithms.

The opening line sets the audience level ("intermediate Python programmer who is just starting to learn DSA"). This prevents the model from assuming too much or too little knowledge.

  • Now update your first generate_content() call to use this system instruction. Find this code:
response = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Explain how a linked list works'
)
  • Add the config parameter so it now looks like this:
response = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Explain how a linked list works'
)
  • Do the same for the second generate_content() call. Find this code:
response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    contents='Now explain doubly linked lists'
)
  • Replace it with this updated version that adds the config and changes the question to test follow-up behavior:
response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Can you show me a harder example?'
)
  • Save assistant.py.
Test the tutor with the same question from Step 2

Before you run this, think back to the response you got in Step 2. What do you expect will be different now that the model has a system instruction telling it to explain step-by-step with Python examples?

  • Run your updated script:
python assistant.py

The first response is now dramatically different from Step 2. You should see a structured, step-by-step explanation that includes Python code, complexity analysis, and encouragement to explore related topics.

What should I see?

The linked list explanation now follows the template from your system instruction: what it is, why it exists, when to use it, and a Python implementation. Compare this to the generic paragraph you got in Step 2. Same question, completely different quality of answer.

Still seeing a generic response?

  • Make sure the config parameter is inside the generate_content() call, not outside it.
  • Check that SYSTEM_INSTRUCTION is defined before the generate_content() call. Python reads top to bottom.
  • Verify you imported types with from google.genai import types. Without it, types.GenerateContentConfig will throw a NameError.
  • help me debug my system instruction not working
Experience the remaining limitation

Now look at the second response in your terminal output. The question was "Can you show me a harder example?" Do you think the model understood what you meant by "harder example"?

The second response gives a generic or unrelated answer. It has no idea what "harder" refers to because each generate_content() call is completely independent. The model does not remember the linked list explanation from the first call.

Why does this happen?

Each call to generate_content() is stateless. The model receives only the contents string you pass in that single call. It has zero awareness of any previous call.

For a tutor to work, follow-up questions like "give me a harder example" or "explain that differently" must reference earlier context. You need conversation memory.

✔️ Awesome, I've got everything!

Great. Double check you have saved assistant.py.

ⓧ I'd like to double check the full code

Here is the complete assistant.py at this point:

import os
from google import genai
from google.genai import types

client = genai.Client(api_key=os.environ.get('GEMINI_API_KEY'))

SYSTEM_INSTRUCTION = """You are a Data Structures and Algorithms (DSA) tutor for an intermediate Python programmer who is just starting to learn DSA concepts.

Your rules:
1. Explain concepts step-by-step, starting from the intuition before diving into implementation.
2. Always use Python for code examples.
3. When explaining a data structure, cover: what it is, why it exists, when to use it, and a simple Python implementation.
4. When explaining an algorithm, cover: the problem it solves, the intuition behind it, step-by-step walkthrough with a small example, Python code, and time/space complexity.
5. Keep explanations concise but thorough. Use analogies when helpful.
6. If the user asks a vague question, ask a clarifying question before answering.
7. Encourage the user and suggest related topics they might want to explore next."""

response = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Explain how a linked list works'
)
print(response.text)

response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Can you show me a harder example?'
)
print(response2.text)

Your system instruction works. The model now responds like a proper tutor with structure, Python code, and complexity analysis. The missing piece is conversation memory, so follow-up questions actually make sense. In the next step, you will solve that by switching to the SDK's chat helper.

Add Conversation Memory

In Step 3, you saw the problem firsthand. When you asked "Can you show me a harder example?", the model had no idea what "harder" referred to. Each generate_content() call starts completely fresh with no memory of previous exchanges.

The fix is the SDK's chat helper. Instead of one-off calls, client.chats.create() opens a persistent session that tracks every message exchanged. Follow-up questions and progressive learning become natural because the model can see your full conversation history.

In this step, get ready to:
  • Replace stateless generate_content() calls with the SDK's chat helper.
  • Build a terminal REPL loop for interactive conversations.
  • Test multi-turn memory with follow-up questions.
Replace generate_content() with the chat helper

The SDK's client.chats.create() method opens a chat session that stores every message. When you call chat.send_message(), the SDK automatically sends the full conversation history to the model so it can see everything you've discussed.

  • In assistant.py, find the two generate_content() blocks at the bottom of the file (everything below the SYSTEM_INSTRUCTION string).
  • Delete all of the following code:
response = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Explain how a linked list works'
)
print(response.text)

response2 = client.models.generate_content(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(system_instruction=SYSTEM_INSTRUCTION),
    contents='Can you show me a harder example?'
)
print(response2.text)
  • Replace it with the following chat session and interactive loop:
chat = client.chats.create(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(
        system_instruction=SYSTEM_INSTRUCTION
    ),
)

print('DSA Learning Assistant (terminal mode)')
print("Type 'quit' to exit.\n")

while True:
    user_input = input('You: ')
    if user_input.lower() == 'quit':
        break
    response = chat.send_message(message=user_input)
    print(f'\nAssistant: {response.text}\n')

What does this code do?

  • client.chats.create() opens a chat session with your model and system instruction. The SDK stores every message you send and every response you receive.
  • chat.send_message() sends a new message along with the full conversation history. The model sees everything that came before, so follow-ups make sense.
  • The while True loop creates a terminal REPL where you can type questions back and forth. Type quit to exit.

Why a chat helper instead of managing history yourself?

You could manually build a list of messages and pass it to generate_content() each time. The chat helper does this bookkeeping for you automatically. It appends each user message and model response to an internal history list, so you never have to manage the state yourself.

  • Save assistant.py.

Seeing a syntax error?

  • Make sure you deleted all of the old generate_content() code. Leftover lines will conflict with the new structure.
  • Check that the closing parentheses on client.chats.create() match up. There are two closing parens on separate lines: ) for the config and ) for chats.create.
  • help me fix a syntax error in my assistant.py chat helper code
Test multi-turn conversation

Now for the moment of truth. The whole reason you switched to the chat helper was to fix the memory problem from Step 3.

  • Before you run the script, predict: if you ask "Explain binary search" and then ask "Can you show me a harder example?", what do you expect the assistant to say this time?
  • Run your assistant by executing this command:
python assistant.py
  • At the You: prompt, type:
Explain binary search
  • Wait for the structured explanation to appear. Then at the next You: prompt, type:
Can you show me a harder example?

This time, the assistant correctly builds on its binary search explanation. It knows what "harder" refers to because the chat session gave it the full conversation history.

  • Try one more follow-up to confirm memory persists across multiple turns. Type:
What about the time complexity?
  • The assistant answers about binary search time complexity without you needing to re-specify the topic.
  • Type quit to exit the REPL.

Assistant still giving generic answers?

  • Make sure you are using chat.send_message() and not client.models.generate_content(). The chat method is the one that tracks history.
  • Confirm that client.chats.create() is defined outside the while loop. If it is inside the loop, it creates a fresh session on every question.
  • help me debug my assistant.py conversation memory

✔️ Awesome, I've got everything!

Great. Double check you have saved assistant.py and the REPL is working with multi-turn memory.

ⓧ I'd like to double check the full code

import os
from google import genai
from google.genai import types

client = genai.Client(api_key=os.environ.get('GEMINI_API_KEY'))

SYSTEM_INSTRUCTION = """You are a Data Structures and Algorithms (DSA) tutor for an intermediate Python programmer who is just starting to learn DSA concepts.

Your rules:
1. Explain concepts step-by-step, starting from the intuition before diving into implementation.
2. Always use Python for code examples.
3. When explaining a data structure, cover: what it is, why it exists, when to use it, and a simple Python implementation.
4. When explaining an algorithm, cover: the problem it solves, the intuition behind it, step-by-step walkthrough with a small example, Python code, and time/space complexity.
5. Keep explanations concise but thorough. Use analogies when helpful.
6. If the user asks a vague question, ask a clarifying question before answering.
7. Encourage the user and suggest related topics they might want to explore next."""

chat = client.chats.create(
    model='gemini-2.5-flash',
    config=types.GenerateContentConfig(
        system_instruction=SYSTEM_INSTRUCTION
    ),
)

print('DSA Learning Assistant (terminal mode)')
print("Type 'quit' to exit.\n")

while True:
    user_input = input('You: ')
    if user_input.lower() == 'quit':
        break
    response = chat.send_message(message=user_input)
    print(f'\nAssistant: {response.text}\n')

Your assistant now remembers everything you discuss in a session. Next up, you'll wrap this in a Flask web app with a proper chat interface so you can use it comfortably in your browser.

Build the Web Interface

Your terminal assistant works, but it is not something you can demo or use comfortably during a long study session. A proper chat interface makes this feel like a real product you will actually want to open every day.

In this step, you will wrap your existing Gemini integration in a Flask web app and build a browser-based chat UI that talks to it. The result is a dark-themed chat interface where you type questions and see structured DSA explanations appear in real time.

In this step, get ready to:
  • Create a Flask backend with routes that serve the chat page and handle messages.
  • Build a dark-themed HTML chat interface with JavaScript that sends requests to your backend.
  • Run the full app in your browser and hold a multi-turn DSA conversation.
Create the Flask backend

The Flask app needs two routes: one to serve the HTML page (and start a fresh chat session), and one to receive messages from the browser and return Gemini's response as JSON.

  • In VS Code, create a new file called app.py in the same folder as assistant.py.
  • Add the imports, Flask app setup, Gemini client, and system instruction by pasting this code:
import os
from flask import Flask, render_template, request, jsonify

from google import genai
from google.genai import types

# Create the Flask application
app = Flask(__name__)

# Reuse the same Gemini client from assistant.py
client = genai.Client(api_key=os.environ.get('GEMINI_API_KEY'))

# Same system instruction that shapes the tutor persona
SYSTEM_INSTRUCTION = """You are a Data Structures and Algorithms (DSA) tutor for an intermediate Python programmer who is just starting to learn DSA concepts.

Your rules:
1. Explain concepts step-by-step, starting from the intuition before diving into implementation.
2. Always use Python for code examples.
3. When explaining a data structure, cover: what it is, why it exists, when to use it, and a simple Python implementation.
4. When explaining an algorithm, cover: the problem it solves, the intuition behind it, step-by-step walkthrough with a small example, Python code, and time/space complexity.
5. Keep explanations concise but thorough. Use analogies when helpful.
6. If the user asks a vague question, ask a clarifying question before answering.
7. Encourage the user and suggest related topics they might want to explore next."""

What does this code do?

This sets up the same foundation you already have in assistant.py, but adapted for a web context.

  • Flask handles HTTP requests. render_template serves HTML files from a templates/ folder.
  • request reads incoming JSON from the browser. jsonify sends JSON responses back.
  • The SYSTEM_INSTRUCTION is identical to what you used in the terminal version.
  • Now add the routes and the server entry point below the system instruction in the same file:
# Module-level variable to hold the active chat session
chat_session = None


@app.route('/')
def index():
    # Start a fresh chat session each time the page loads
    global chat_session
    chat_session = client.chats.create(
        model='gemini-2.5-flash',
        config=types.GenerateContentConfig(
            system_instruction=SYSTEM_INSTRUCTION
        ),
    )
    return render_template('index.html')


@app.route('/chat', methods=['POST'])
def chat():
    # Read the user's message from the JSON body
    user_message = request.json.get('message', '')
    response = chat_session.send_message(message=user_message)
    return jsonify({'response': response.text})


if __name__ == '__main__':
    app.run(debug=True)

What do the routes do?

The / route creates a brand-new Gemini chat session (with conversation memory) and serves the HTML page. Every browser refresh starts a clean conversation.

The /chat route receives POST requests containing the user's message, forwards it to the active chat session, and returns the assistant's response as JSON. The browser JavaScript will call this endpoint for every message.

Why a global variable for chat_session?

A global variable is the simplest way to persist the chat session across requests. It works perfectly for single-user local development, which is this project's scope. A production app would use server-side session storage instead.

  • Save app.py.
  • Start the Flask server by running this command:
python app.py

You should see output that includes a line like * Running on http://127.0.0.1:5000. The server is now waiting for requests.

Server not starting?

  • If you see ModuleNotFoundError: No module named 'flask', make sure you are in the same terminal session where you ran pip install earlier.
  • If you see Address already in use, another process is using port 5000. On macOS, AirPlay Receiver uses this port by default. Disable it in System Settings or stop the other process.

help me fix my Flask server startup error

Create the chat interface

Flask looks for HTML templates in a folder called templates/ by default. You will create that folder and add an index.html file with a dark-themed chat layout.

  • Stop the Flask server by pressing Ctrl+C in your terminal.
  • Create the templates folder by running:
mkdir templates
  • Inside the templates/ folder, create a new file called index.html.
  • Add the HTML head section with all the CSS styling by pasting this code:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DSA Learning Assistant</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #1a1a2e; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; }
        header { background-color: #16213e; padding: 1rem 2rem; border-bottom: 1px solid #0f3460; }
        header h1 { font-size: 1.3rem; color: #4fc3f7; }
        header p { font-size: 0.85rem; color: #90a4ae; margin-top: 0.25rem; }
        #chat-container { flex: 1; overflow-y: auto; padding: 2rem; display: flex; flex-direction: column; gap: 1rem; }
        .message { max-width: 75%; padding: 1rem 1.25rem; border-radius: 1rem; line-height: 1.6; white-space: pre-wrap; }
        .user-message { align-self: flex-end; background-color: #0f3460; color: #e0e0e0; border-bottom-right-radius: 0.25rem; }
        .assistant-message { align-self: flex-start; background-color: #16213e; color: #e0e0e0; border-bottom-left-radius: 0.25rem; border: 1px solid #0f3460; }
        .loading { align-self: flex-start; color: #90a4ae; font-style: italic; }
        #input-container { padding: 1rem 2rem; background-color: #16213e; border-top: 1px solid #0f3460; display: flex; gap: 0.75rem; }
        #user-input { flex: 1; padding: 0.75rem 1rem; border-radius: 0.5rem; border: 1px solid #0f3460; background-color: #1a1a2e; color: #e0e0e0; font-size: 1rem; outline: none; }
        #user-input:focus { border-color: #4fc3f7; }
        #send-btn { padding: 0.75rem 1.5rem; border-radius: 0.5rem; border: none; background-color: #4fc3f7; color: #1a1a2e; font-weight: bold; font-size: 1rem; cursor: pointer; }
        #send-btn:hover { background-color: #81d4fa; }
        #send-btn:disabled { background-color: #37474f; color: #90a4ae; cursor: not-allowed; }
    </style>
</head>

What does the CSS do?

The styles create a full-height dark layout with a fixed header at the top, a scrollable chat area in the middle, and an input bar pinned to the bottom. User messages float right with a blue background. Assistant messages float left with a subtle border.

  • Below the closing </head> tag, add the body HTML with the page structure and an empty script tag:
<body>
    <header>
        <h1>DSA Learning Assistant</h1>
        <p>Ask me anything about data structures and algorithms. I will explain step-by-step.</p>
    </header>
    <div id="chat-container"></div>
    <div id="input-container">
        <input type="text" id="user-input" placeholder="Ask about a data structure or algorithm..." autofocus>
        <button id="send-btn">Send</button>
    </div>
    <script>
    </script>
</body>
</html>
  • Save index.html and restart the Flask server by running python app.py again.
  • Open http://127.0.0.1:5000 in your browser.

You should see a dark-themed page with a header reading "DSA Learning Assistant", an empty chat area, and an input field with a Send button at the bottom. Clicking Send does nothing yet because the JavaScript is empty.

Seeing a blank page or an error?

  • If you see a TemplateNotFound error in the terminal, confirm the file is at templates/index.html (not just index.html in the root folder).
  • If the page looks unstyled, check for typos in the <style> block and make sure you saved the file.

help me debug why my Flask template is not rendering

  • Now add the JavaScript that powers the chat. Replace the empty <script> tag contents with the DOM references and utility functions:
    <script>
        const chatContainer = document.getElementById('chat-container');
        const userInput = document.getElementById('user-input');
        const sendBtn = document.getElementById('send-btn');

        function addMessage(text, isUser) {
            const div = document.createElement('div');
            div.className = `message ${isUser ? 'user-message' : 'assistant-message'}`;
            div.textContent = text;
            chatContainer.appendChild(div);
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }

        function showLoading() {
            const div = document.createElement('div');
            div.className = 'message loading';
            div.id = 'loading';
            div.textContent = 'Thinking...';
            chatContainer.appendChild(div);
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }

        function hideLoading() {
            const loading = document.getElementById('loading');
            if (loading) loading.remove();
        }

What do these functions do?

  • addMessage() creates a new chat bubble (styled as user or assistant) and scrolls the container to show it.
  • showLoading() displays a "Thinking..." indicator while waiting for the API response.
  • hideLoading() removes the indicator once the response arrives.
  • Directly below the hideLoading function, add the main sendMessage function and the event listeners:
        async function sendMessage() {
            const message = userInput.value.trim();
            if (!message) return;
            addMessage(message, true);
            userInput.value = '';
            sendBtn.disabled = true;
            showLoading();
            try {
                const response = await fetch('/chat', {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ message: message })
                });
                const data = await response.json();
                hideLoading();
                addMessage(data.response, false);
            } catch (error) {
                hideLoading();
                addMessage('Error: Could not reach the assistant. Please try again.', false);
            }
            sendBtn.disabled = false;
            userInput.focus();
        }

        sendBtn.addEventListener('click', sendMessage);
        userInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') sendMessage(); });
    </script>

What does sendMessage do?

When the user clicks Send or presses Enter, this function grabs the input text, displays it as a user message bubble, and sends a POST request to your /chat endpoint. When the JSON response arrives, it displays the assistant's answer as a new bubble.

The try/catch block handles network errors gracefully. The button is disabled during the request to prevent double-sends.

  • Save index.html.

✔️ Awesome, I've got everything!

Great. Make sure you saved both app.py and templates/index.html.

ⓧ I'd like to double check the full code

Both files are over 30 lines, so scroll up and confirm each section is present in order:

app.py should contain:

  • Imports (os, flask, genai, types).
  • app = Flask(__name__) and the client setup.
  • SYSTEM_INSTRUCTION with all 7 rules.
  • chat_session = None followed by the / and /chat routes.
  • if __name__ == '__main__': app.run(debug=True) at the bottom.

templates/index.html should contain:

  • DOCTYPE and head with the full <style> block (16 CSS rules).
  • Body with a <header>, #chat-container div, and #input-container div.
  • A <script> tag with addMessage, showLoading, hideLoading, sendMessage, and two event listeners.
Run and test the full app

Everything is wired up. Time to see your assistant come alive in the browser.

  • Stop the Flask server with Ctrl+C if it is still running, then restart it:
python app.py
  • Open http://127.0.0.1:5000 in your browser (or refresh if it is already open).

Before you type anything, what do you expect will happen when you ask a follow-up question like "give me a harder example" after an initial explanation?

  • Type Explain how a stack works and click Send (or press Enter).
  • Wait for the assistant's response to appear. You should see a structured explanation with Python code and complexity analysis.
  • Now type Give me a harder example and send it.

The assistant remembers your previous message and builds on its stack explanation. Conversation memory is working because the / route created a chat session and every /chat call goes through the same session.

Send button does nothing?

  • Open your browser's developer console (F12 or Cmd+Option+J on macOS) and check for JavaScript errors.
  • Confirm the <script> tag contains all four functions (addMessage, showLoading, hideLoading, sendMessage) and the two event listener lines at the bottom.
  • Make sure the Flask server is running in your terminal. The browser cannot reach /chat if the server is stopped.

help me debug why my chat messages are not sending

Your DSA tutor is now a real web app running in your browser. You went from a terminal script to a polished chat interface that you can use for extended study sessions. In the Secret Mission, you will push this further by adding a quiz mode that tests your understanding.

Secret mission

Add Quiz Mode

Your tutor explains things well, but real learning happens when you test yourself. Can you add a toggle that switches the assistant from explanation mode to quiz mode, where it asks YOU the questions?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on the Gemini free tier and your local machine. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Project files: assistant.py, app.py, and templates/index.html.
  • Installed Python packages: google-genai and Flask.
  • Gemini API key created at Google AI Studio.

Keep everything running

No action needed. Choose this if you are still actively building or want to keep using your DSA assistant.

  • Your code is all local. Run python app.py any time to start your assistant.
  • Your API key is free-tier and costs nothing to keep active.
  • Your GEMINI_API_KEY environment variable only lasts for your current terminal session. You will need to set it again if you open a new terminal.

Pause - I'll come back to this later

Stop the Flask dev server to free up your terminal. Your files and packages stay on your machine for next time.

  • If the Flask server is still running, press Ctrl+C in the terminal to stop it.
  • Your project files and installed packages remain on your machine.
  • When you come back, set GEMINI_API_KEY again in your terminal before running the app.

Delete - I don't want to use this again

Remove all project files, uninstall the packages, and delete your API key.

  • If the Flask server is still running, press Ctrl+C in the terminal to stop it.
  • Delete the project files by running:

macOS

rm assistant.py app.py
rm -rf templates/

Windows

del assistant.py app.py
rmdir /s /q templates
  • Uninstall the Python packages by running:
pip uninstall google-genai Flask -y

What about the environment variable?

The GEMINI_API_KEY environment variable only exists in your current terminal session. It disappears automatically when you close the terminal. No extra cleanup needed.

Keep your API key private

Your Gemini API key is free-tier but should not be shared publicly. Do not commit it to Git or paste it in public repositories.

Nice Work!

Nice Work!

You just built a fully functional AI learning assistant powered by Google Gemini that runs in your browser. From a raw API call to a polished chat interface with conversation memory, you now have a personal DSA tutor you can fire up any time.

Here's what you learned:

  • Built a working LLM-powered chat application using the Google GenAI SDK and Gemini 2.5 Flash, from first API call to multi-turn conversations.
  • Engineered system prompts that transform a general-purpose LLM into a focused, opinionated agent with consistent teaching behavior.
  • Implemented multi-turn conversation memory using the SDK's chat helper, enabling follow-up questions and progressive learning.
  • Built a full-stack Python web app with Flask that connects a frontend chat UI to an LLM backend via JSON API routes.
  • Secret Mission: Added dynamic behavior switching with a quiz mode that tests your understanding through targeted questions.

Ready to quiz yourself?