AI Interview Simulator with Voice
Build a voice-powered AI interview coach with Redis score tracking.
Introduction
30 Second Summary
Interview prep is one of those things everyone knows they should do. But reading questions off a list never feels like the real thing. Real interviews demand you think on your feet and communicate clearly under pressure.
In this project, you will build a terminal-based AI interview simulator that listens to you speak, evaluates your answers against a structured rubric, and tracks your improvement over time using cloud-based state management.
What You'll Build
Picture this: you run a command, pick your target role, and a question appears. You speak your answer out loud into your microphone. Seconds later, a scorecard breaks down exactly where you were strong and where you fell short, with evidence pulled directly from your own words.
By the end of this project, you'll have:
- A working voice-powered interview loop where you speak answers into your microphone, get real-time transcription, and have your recordings saved as WAV files for self-review.
- Rubric-based AI scoring powered by Gemini that evaluates your answers on three dimensions (technical accuracy, completeness, communication clarity) with specific cited feedback.
- Redis-backed session persistence that stores every interview score so you can track which dimensions are improving across sessions.
- Secret Mission: Multi-round adaptive interviews that adjust difficulty based on your performance history stored in Redis.
Are there any prerequisites?
You'll need basic Python and REST API familiarity. You'll also need a Google account for the free Gemini API key and an Upstash account for a free Redis database. No credit card is required for either service.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters for your interview preparation.
Set Up Your Environment
Before you can build an AI interview simulator, you need the right tools and cloud services in place. This step gets your Python project ready with the AI, voice, and database libraries it depends on.
You will also connect to two free cloud services: Google's Gemini API for AI-powered evaluation, and Upstash Redis for storing your interview scores across sessions.
In this step, get ready to:
- Create a Python virtual environment and install all project dependencies.
- Connect to the Gemini API with your own API key.
- Set up a free Upstash Redis database for session tracking.
Create your project and install dependencies
The google-genai SDK requires Python 3.10 or higher. Let's start by creating a dedicated project folder and virtual environment.
- Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal, and pressing Enter.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder, move into it, and set up a virtual environment by running these commands:
mkdir ai-interview-simulator
cd ai-interview-simulator
python3 -m venv venv
What is a virtual environment?
A virtual environment is an isolated Python installation inside your project folder. It keeps this project's packages separate from your system Python, so nothing conflicts.
- Activate the virtual environment by running:
macOS
source venv/bin/activate
Windows
venv\Scripts\activate
You should see (venv) appear at the start of your terminal prompt. This confirms the virtual environment is active.
- Confirm your Python version meets the minimum requirement by running:
python --version
You should see Python 3.10 or higher in the output.
Seeing a version below 3.10?
The google-genai SDK requires Python 3.10+. If your version is lower, download and install the latest Python from python.org/downloads.
On macOS, the system python3 may point to an older version even after installing a new one. Use python3.12 (or whichever version you installed) when creating the venv: python3.12 -m venv venv.
Help me upgrade Python to 3.10+ for this project.
- Install the four project dependencies by running:
pip install google-genai redis "SpeechRecognition[audio]" python-dotenv
You should see output ending with a Successfully installed message listing the packages.
What are these packages?
- google-genai is Google's official SDK for calling the Gemini API.
- redis is the Python client for Redis databases.
- SpeechRecognition[audio] provides speech-to-text transcription with bundled audio support (includes PyAudio on Windows).
- python-dotenv loads environment variables from a .env file so your API keys stay out of your code.
Installation failed?
If you see an error about PyAudio on macOS, you may need to install PortAudio first: brew install portaudio, then retry the pip install command.
On Windows, the [audio] extra bundles everything needed. If you still see errors, make sure your virtual environment is active (look for (venv) in your prompt).
Help me fix my pip install errors for this project.
- Create a requirements.txt file to document your dependencies. Open your project folder in VS Code by running:
code .
- In VS Code, click File in the top menu bar, then New File.
- Name the file requirements.txt and paste in the following content:
google-genai>=2.0.0
redis>=5.0.0
SpeechRecognition[audio]>=3.10.0
python-dotenv>=1.0.0
What does this file do?
The requirements.txt file records the exact packages your project needs. Anyone (or any deployment system) can recreate your environment by running pip install -r requirements.txt.
- Save requirements.txt with Cmd+S (macOS) or Ctrl+S (Windows).
- Verify all three libraries import correctly by running these commands in your terminal:
python -c "from google import genai; print('Gemini SDK ready')"
python -c "import redis; print('Redis client ready')"
python -c "import speech_recognition as sr; print('SpeechRecognition ready')"
Each command should print its ready message without errors.
✔️ All three printed their ready messages
Your dependencies are installed and importing correctly.
ⓧ I see an import error
Make sure your virtual environment is active (you see (venv) in your prompt). If not, re-run the activation command from the platform tabs above, then try the imports again.
If your venv is active but imports fail, re-run the pip install command to ensure packages installed in the correct environment.
✔️ Awesome, I've got everything!
Great. Make sure you have saved requirements.txt.
ⓧ I'd like to double check the full code
google-genai>=2.0.0
redis>=5.0.0
SpeechRecognition[audio]>=3.10.0
python-dotenv>=1.0.0
Get your Gemini API key
Google AI Studio lets you create free API keys for the Gemini API. The free tier has generous token limits, so this project costs nothing to run.
- Open your browser and go to aistudio.google.com/app/apikey.
- Sign in with your Google account if prompted.
- Click Create API key.
- If prompted to select a project, choose the default project or create a new one.
- Copy the generated API key. You will only see the full key once, so copy it now.
Why is this free?
The Gemini API free tier provides free input and output tokens with no billing required. For a personal interview practice tool, you will stay well within the free limits.
Can't create an API key?
If you see a permission error, try using a personal Google account rather than a work or school account. Organization-managed accounts may restrict API key creation.
You can also try creating the key directly from the Google Cloud Console under APIs & Services then Credentials.
Help me create a Gemini API key.
- In VS Code, create a new file called .env in your project root (File then New File).
- Paste the following into .env, replacing your-api-key-here with the key you just copied:
GEMINI_API_KEY=your-api-key-here
- Save .env with Cmd+S (macOS) or Ctrl+S (Windows).
- Test that your Gemini connection works by running this command in your terminal:
python -c "
from google import genai
from dotenv import load_dotenv
import os
load_dotenv()
client = genai.Client(api_key=os.getenv('GEMINI_API_KEY'))
response = client.models.generate_content(model='gemini-2.0-flash', contents='Say hello in one word')
print(response.text)
"
What do you think you will see in the terminal?
You should see a single-word greeting (like Hello) printed to the terminal. This confirms your API key works and Gemini is responding.
Seeing an authentication error?
Double-check that your .env file is saved in the same folder where your terminal is running (the ai-interview-simulator folder).
Make sure there are no extra spaces around the = sign or quotes around the key value in .env.
Help me debug my Gemini API connection.
Set up Upstash Redis
Upstash provides a free cloud Redis database that works with standard Redis clients. You will use it to store interview scores so they persist across sessions.
- Open your browser and go to console.upstash.com.
- Sign up for a free account (no credit card required) or sign in if you already have one.
- From the Redis tab, click + Create Database in the upper right corner.
- Enter a name for your database (for example, interview-simulator).
- Select the region closest to you for best performance.
- Click Next and select the Free plan.
- Once created, your database details page appears. Find the connection string labeled with the rediss:// prefix and copy it.
What does the Redis URL look like?
The URL follows this format: rediss://default:password@endpoint:port. The rediss:// prefix (with double s) means the connection uses TLS encryption.
- Switch back to VS Code and open your .env file.
- Add a second line with your Redis URL, so the file now looks like this:
GEMINI_API_KEY=your-api-key-here
REDIS_URL=rediss://default:your-password@your-endpoint.upstash.io:6379
- Replace the REDIS_URL value with the actual connection string you copied from Upstash.
- Save .env.
- Test your Redis connection by running this command in your terminal:
python -c "
import redis
from dotenv import load_dotenv
import os
load_dotenv()
client = redis.from_url(os.getenv('REDIS_URL'), decode_responses=True)
client.set('test_key', 'hello from interview simulator')
print(client.get('test_key'))
client.delete('test_key')
print('Redis connection successful!')
"
Before you hit Enter, what do you expect to see printed?
You should see two lines: hello from interview simulator followed by Redis connection successful!. This proves your code can write to and read from the Upstash database.
Connection refused or timed out?
Make sure you copied the full Redis URL including the rediss:// prefix (double s for TLS). A single-s redis:// URL will fail because Upstash requires encrypted connections.
Check that there are no extra spaces or line breaks in the URL inside your .env file.
Help me fix my Redis connection.
✔️ Awesome, I've got everything!
Great. Make sure you have saved your .env file with both keys.
ⓧ I'd like to double check the full code
GEMINI_API_KEY=your-api-key-here
REDIS_URL=rediss://default:your-password@your-endpoint.upstash.io:6379
google-genai>=2.0.0
redis>=5.0.0
SpeechRecognition[audio]>=3.10.0
python-dotenv>=1.0.0
Your environment is fully configured. Next up, you will build the core AI interview engine that generates questions and scores your answers with a structured rubric.
Build the AI Interview Engine
Before layering in voice or session tracking, you need the core interview loop working end-to-end. That means Gemini can generate a question, receive your typed answer, and score it against a structured rubric.
This step builds the entire text-based evaluation pipeline. By the end, you will run a single command and get back a formatted scorecard with per-dimension scores and cited feedback.
In this step, get ready to:
- Create the AI interviewer module with question generation and answer evaluation.
- Build the main interview loop with a formatted scorecard display.
- Harden the evaluation output for consistent JSON parsing.
Create the AI interviewer module
The interviewer module handles two responsibilities: generating role-specific questions and evaluating your answers with a structured rubric. You will split these into two functions inside a single file.
- Create a new file called ai_interviewer.py in your project folder.
- Add the imports, client setup, and question generation function by pasting this code:
import os
import json
from google import genai
from google.genai import types
from dotenv import load_dotenv
load_dotenv()
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
MODEL = "gemini-2.0-flash"
def generate_question(role: str, topic: str = "") -> str:
prompt = f"""You are a senior technical interviewer at a top tech company.
Generate ONE interview question for a {role} candidate.
{"Focus area: " + topic if topic else "Pick a relevant technical topic."}
Requirements:
- The question should be specific and require a detailed answer (not yes/no)
- It should test both technical knowledge and ability to communicate clearly
- Difficulty: Medium
Return ONLY the interview question, nothing else."""
response = client.models.generate_content(
model=MODEL,
contents=prompt
)
return response.text.strip()
What does this code do?
- load_dotenv() reads your .env file so the API key is available as an environment variable.
- genai.Client(api_key=...) creates a Gemini client authenticated with your key.
- generate_question() sends a prompt to Gemini requesting a single interview question tailored to the given role and optional topic.
- Save ai_interviewer.py.
- Verify the module loads and Gemini responds by running:
python -c "from ai_interviewer import generate_question; print(generate_question('Software Engineer'))"
You should see an interview question printed in your terminal.
Not seeing a question?
- Check that your .env file contains a valid GEMINI_API_KEY value (not the placeholder text).
- Make sure your virtual environment is active. You should see its name in parentheses in your terminal prompt.
- If you see ModuleNotFoundError, re-run pip install google-genai python-dotenv.
help me debug why my Gemini API call is not returning a question
Now add the evaluation function that scores answers on three dimensions.
- In ai_interviewer.py, add the following function below generate_question:
def evaluate_answer(question: str, answer: str) -> dict:
prompt = f"""You are an expert interview evaluator. Score the following interview answer on exactly 3 dimensions.
INTERVIEW QUESTION:
{question}
CANDIDATE'S ANSWER:
{answer}
Score each dimension from 1-10 and provide specific feedback citing parts of the answer.
Return ONLY valid JSON in this exact format:
{{
"technical_accuracy": {{
"score": <1-10>,
"feedback": "<specific feedback citing what was correct/incorrect>"
}},
"completeness": {{
"score": <1-10>,
"feedback": "<what was covered well and what was missing>"
}},
"communication": {{
"score": <1-10>,
"feedback": "<how clearly and structured the answer was delivered>"
}},
"overall_score": <average of 3 scores rounded to 1 decimal>,
"summary": "<2-3 sentence overall assessment>"
}}"""
response = client.models.generate_content(
model=MODEL,
contents=prompt
)
return json.loads(response.text.strip())
What is the LLM-as-a-Judge pattern?
Instead of writing scoring logic yourself, you instruct the LLM to act as a structured evaluator. The prompt defines the exact rubric dimensions, scoring range, and output format.
By requiring the AI to cite specific parts of your answer in its feedback, you get evidence-based scoring rather than vague praise.
- Save ai_interviewer.py.
Build the interview loop
With the AI module ready, you need a main script that ties everything together into an interactive loop.
- Create a new file called main.py in your project folder.
- Add the import and helper functions by pasting this code:
from ai_interviewer import generate_question, evaluate_answer
def display_scorecard(scores: dict):
# Print a formatted scorecard with dimension scores and feedback
print("\n" + "=" * 50)
print(" INTERVIEW SCORECARD")
print("=" * 50)
for dimension in ["technical_accuracy", "completeness", "communication"]:
data = scores.get(dimension, {})
score = data.get("score", 0)
feedback = data.get("feedback", "")
bar = "#" * score + "." * (10 - score)
print(f"\n {dimension.replace('_', ' ').title()}: [{bar}] {score}/10")
print(f" {feedback}")
print(f"\n Overall Score: {scores.get('overall_score', 0)}/10")
print(f"\n Summary: {scores.get('summary', '')}")
print("=" * 50)
def get_answer_typed() -> tuple:
# Collect multi-line typed input until user presses Enter on an empty line
print("\nType your answer (press Enter twice to submit):")
lines = []
while True:
line = input()
if line == "":
if lines:
break
lines.append(line)
return "\n".join(lines), ""
What do these helpers do?
- display_scorecard() prints a visual bar chart for each rubric dimension along with the AI's cited feedback.
- get_answer_typed() collects multi-line input. The learner presses Enter on a blank line to submit. It returns a tuple so you can swap in voice input later without changing the caller.
- Below get_answer_typed, add the main entry point function:
def main():
print("\n" + "=" * 50)
print(" AI INTERVIEW SIMULATOR")
print(" Practice like it's the real thing.")
print("=" * 50)
print("\nSelect your target role:")
print("[1] Software Engineer (SWE)")
print("[2] AI/ML Engineer")
role_choice = input("Select: ").strip()
role = "AI/ML Engineer" if role_choice == "2" else "Software Engineer"
print(f"\n--- Interview for {role} ---")
print("\n[Generating your interview question...]\n")
question = generate_question(role)
print(f"INTERVIEWER: {question}\n")
answer, wav_file = get_answer_typed()
if not answer:
print("\nNo answer received. Exiting.")
return
print("\n[Evaluating your answer...]\n")
try:
scores = evaluate_answer(question, answer)
display_scorecard(scores)
except Exception as e:
print(f"\n[Evaluation error: {e}]")
print("[The AI response may not have been valid JSON. Try again.]")
print("\n[Another round? Run the program again!]")
if __name__ == "__main__":
main()
What does main() do?
- Presents a role menu (SWE or AI/ML) and generates a tailored question.
- Collects your typed answer, then sends both the question and answer to evaluate_answer() for scoring.
- The try/except block catches JSON parsing failures. Gemini sometimes wraps its response in markdown code fences, which breaks json.loads().
- Save main.py.
- Before you run this, what do you think will happen if Gemini wraps its JSON response in ```json markdown fences?
- Run the interview loop:
python main.py
- Select a role (press 1 for SWE), read the question, then type a short answer and press Enter twice to submit.
If the scorecard appears with scores and feedback, the pipeline is working end-to-end. If you see an [Evaluation error: ...] message instead, that is expected. Gemini often wraps JSON in markdown fences, and json.loads() cannot parse them.
Harden the JSON parsing
Gemini frequently wraps JSON responses in markdown code fences like ```json ... ```. You need to strip those fences and lower the temperature for more deterministic output.
- In ai_interviewer.py, find the last two lines of the evaluate_answer function:
response = client.models.generate_content(
model=MODEL,
contents=prompt
)
return json.loads(response.text.strip())
- Replace those lines with the following, which adds a temperature config and strips markdown fences before parsing:
response = client.models.generate_content(
model=MODEL,
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.3
)
)
response_text = response.text.strip()
if response_text.startswith("```json"):
response_text = response_text[7:]
if response_text.startswith("```"):
response_text = response_text[3:]
if response_text.endswith("```"):
response_text = response_text[:-3]
return json.loads(response_text.strip())
Why lower the temperature?
Temperature controls how random the model's output is. A value of 0.3 makes Gemini stick closer to the most likely tokens, producing more consistent JSON structure across runs.
The stripping logic handles three cases: fences starting with ```json, plain ``` fences, and trailing ``` closers.
- Save ai_interviewer.py.
- Run the interview again:
python main.py
This time, the scorecard should display reliably with scores for all three dimensions and cited feedback from your answer.
Still seeing a JSON error?
- Check that your if response_text.startswith lines are inside the evaluate_answer function (indented with 4 spaces).
- Make sure you removed the old return json.loads(response.text.strip()) line. The new return uses response_text (the cleaned variable).
- Run the script a second time. Gemini occasionally returns unexpected formats, but with temperature at 0.3 it should be consistent.
help me fix JSON parsing errors in my evaluate_answer function
✔️ Awesome, I've got everything!
Great. Double check you have saved both ai_interviewer.py and main.py.
ⓧ I'd like to double check the full code
Here is the complete ai_interviewer.py:
import os
import json
from google import genai
from google.genai import types
from dotenv import load_dotenv
load_dotenv()
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
MODEL = "gemini-2.0-flash"
def generate_question(role: str, topic: str = "") -> str:
prompt = f"""You are a senior technical interviewer at a top tech company.
Generate ONE interview question for a {role} candidate.
{"Focus area: " + topic if topic else "Pick a relevant technical topic."}
Requirements:
- The question should be specific and require a detailed answer (not yes/no)
- It should test both technical knowledge and ability to communicate clearly
- Difficulty: Medium
Return ONLY the interview question, nothing else."""
response = client.models.generate_content(
model=MODEL,
contents=prompt
)
return response.text.strip()
And the evaluate_answer function (directly below generate_question in the same file):
def evaluate_answer(question: str, answer: str) -> dict:
prompt = f"""You are an expert interview evaluator. Score the following interview answer on exactly 3 dimensions.
INTERVIEW QUESTION:
{question}
CANDIDATE'S ANSWER:
{answer}
Score each dimension from 1-10 and provide specific feedback citing parts of the answer.
Return ONLY valid JSON in this exact format:
{{
"technical_accuracy": {{
"score": <1-10>,
"feedback": "<specific feedback citing what was correct/incorrect>"
}},
"completeness": {{
"score": <1-10>,
"feedback": "<what was covered well and what was missing>"
}},
"communication": {{
"score": <1-10>,
"feedback": "<how clearly and structured the answer was delivered>"
}},
"overall_score": <average of 3 scores rounded to 1 decimal>,
"summary": "<2-3 sentence overall assessment>"
}}"""
response = client.models.generate_content(
model=MODEL,
contents=prompt,
config=types.GenerateContentConfig(
temperature=0.3
)
)
response_text = response.text.strip()
if response_text.startswith("```json"):
response_text = response_text[7:]
if response_text.startswith("```"):
response_text = response_text[3:]
if response_text.endswith("```"):
response_text = response_text[:-3]
return json.loads(response_text.strip())
Here is the complete main.py:
from ai_interviewer import generate_question, evaluate_answer
def display_scorecard(scores: dict):
print("\n" + "=" * 50)
print(" INTERVIEW SCORECARD")
print("=" * 50)
for dimension in ["technical_accuracy", "completeness", "communication"]:
data = scores.get(dimension, {})
score = data.get("score", 0)
feedback = data.get("feedback", "")
bar = "#" * score + "." * (10 - score)
print(f"\n {dimension.replace('_', ' ').title()}: [{bar}] {score}/10")
print(f" {feedback}")
print(f"\n Overall Score: {scores.get('overall_score', 0)}/10")
print(f"\n Summary: {scores.get('summary', '')}")
print("=" * 50)
And the get_answer_typed and main functions:
def get_answer_typed() -> tuple:
print("\nType your answer (press Enter twice to submit):")
lines = []
while True:
line = input()
if line == "":
if lines:
break
lines.append(line)
return "\n".join(lines), ""
def main():
print("\n" + "=" * 50)
print(" AI INTERVIEW SIMULATOR")
print(" Practice like it's the real thing.")
print("=" * 50)
print("\nSelect your target role:")
print("[1] Software Engineer (SWE)")
print("[2] AI/ML Engineer")
role_choice = input("Select: ").strip()
role = "AI/ML Engineer" if role_choice == "2" else "Software Engineer"
print(f"\n--- Interview for {role} ---")
print("\n[Generating your interview question...]\n")
question = generate_question(role)
print(f"INTERVIEWER: {question}\n")
answer, wav_file = get_answer_typed()
if not answer:
print("\nNo answer received. Exiting.")
return
print("\n[Evaluating your answer...]\n")
try:
scores = evaluate_answer(question, answer)
display_scorecard(scores)
except Exception as e:
print(f"\n[Evaluation error: {e}]")
print("[The AI response may not have been valid JSON. Try again.]")
print("\n[Another round? Run the program again!]")
if __name__ == "__main__":
main()
Your AI interview engine is working. Next up, you will swap out the keyboard for your microphone so you can practice speaking your answers out loud.
Add Voice Input and Audio Recording
Typing answers into a terminal is nothing like a real interview. In a real interview, you articulate your thoughts under time pressure while managing tone, pace, and filler words.
In this step, you will build a voice recording module that captures your spoken answer through your microphone, saves it as a WAV file you can replay later, and transcribes it to text for AI evaluation. This is where the simulator starts feeling like genuine interview practice.
In this step, get ready to:
- Create a voice recording module that captures microphone audio and saves WAV files.
- Add speech-to-text transcription using the built-in Google Web Speech API.
- Wire voice input into the interview loop so you can speak your answers.
Build the audio recording module
The SpeechRecognition library provides a unified interface for accessing your microphone and converting audio to text. You installed it in Step 1 as part of your project dependencies.
- Create a new file called voice_recorder.py in your project folder.
- Add the imports, recording directory constant, and helper functions by pasting this code:
import os
import wave
import datetime
import speech_recognition as sr
RECORDINGS_DIR = "recordings"
def ensure_recordings_dir():
if not os.path.exists(RECORDINGS_DIR):
os.makedirs(RECORDINGS_DIR)
def save_audio_wav(audio_data: sr.AudioData) -> str:
"""Save AudioData as a WAV file. Returns the filepath."""
ensure_recordings_dir()
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"interview_{timestamp}.wav"
filepath = os.path.join(RECORDINGS_DIR, filename)
wav_data = audio_data.get_wav_data()
with open(filepath, "wb") as f:
f.write(wav_data)
print(f"[Audio saved: {filepath}]")
return filepath
What does this code do?
- RECORDINGS_DIR sets a consistent folder name where all your interview audio files will live.
- ensure_recordings_dir() creates the recordings/ folder if it does not already exist.
- save_audio_wav() converts the raw audio data into a timestamped WAV file so you can replay your answers later and hear how you actually sound.
- Now add the record_answer() function below save_audio_wav() in the same file:
def record_answer(time_limit: int = 60) -> tuple:
"""Record audio from microphone. Returns (audio_data, wav_filepath) or (None, None) on failure."""
recognizer = sr.Recognizer()
try:
with sr.Microphone() as source:
print("\n[Adjusting for ambient noise... stay quiet for 1 second]")
recognizer.adjust_for_ambient_noise(source, duration=1)
print("[Ready! Speak your answer now. (silence will end recording)]")
print(f"[Time limit: {time_limit} seconds]\n")
audio_data = recognizer.listen(source, timeout=10, phrase_time_limit=time_limit)
print("[Recording complete!]")
wav_filepath = save_audio_wav(audio_data)
return audio_data, wav_filepath
except sr.WaitTimeoutError:
print("[No speech detected within timeout. Try again.]")
return None, None
except OSError as e:
print(f"[Microphone error: {e}]")
print("[Make sure your microphone is connected and not in use by another app.]")
return None, None
What does this code do?
- sr.Recognizer() creates the speech recognition engine that handles audio processing.
- adjust_for_ambient_noise() listens for 1 second of silence to calibrate the noise threshold so background hum does not trigger false recordings.
- recognizer.listen() records until it detects silence (end of your answer), with a 10-second timeout if you never start speaking and a 60-second phrase_time_limit maximum.
- The function returns a tuple of the raw audio data (for transcription) and the saved WAV filepath (for your records).
- Save voice_recorder.py.
- Verify your microphone is detected by running this command:
python -c "import speech_recognition as sr; print(sr.Microphone.list_microphone_names())"
You should see a list of available microphone names printed in your terminal. If you see at least one entry, your microphone hardware is ready.
Seeing an empty list or an error?
- Make sure your microphone is plugged in and not muted at the OS level.
- On Windows, check that your microphone is enabled in Sound Settings under Input.
- If you see No Default Input Device Available, your system has no active microphone selected.
Still stuck? Help me fix my microphone detection in SpeechRecognition
Add speech-to-text transcription
The SpeechRecognition library includes a built-in Google Web Speech API that requires no API key for personal use. It converts your recorded audio into text that the AI can evaluate.
- Add the transcribe_audio() function at the bottom of voice_recorder.py:
def transcribe_audio(audio_data: sr.AudioData) -> str:
"""Transcribe audio using Google Web Speech API. Returns transcribed text or empty string."""
recognizer = sr.Recognizer()
try:
text = recognizer.recognize_google(audio_data)
return text
except sr.UnknownValueError:
print("[Could not understand the audio. Please try again.]")
return ""
except sr.RequestError as e:
print(f"[Speech recognition service error: {e}]")
return ""
What does this code do?
- recognize_google() sends your audio to the Google Web Speech API and returns the transcribed text.
- UnknownValueError catches cases where the audio was too quiet or garbled to understand.
- RequestError catches network failures so the program does not crash if you lose internet mid-recording.
- Save voice_recorder.py.
- Test the full recording and transcription pipeline by running this command:
python -c "from voice_recorder import record_answer, transcribe_audio; a, p = record_answer(5); print('Transcription:', transcribe_audio(a)) if a else print('No audio captured')"
When you run this, say a short phrase like "hello world" into your microphone. You should see your spoken words printed as text in the terminal, and a new WAV file should appear in the recordings/ folder.
Recording times out or transcription is empty?
- Speak within 10 seconds of seeing the "Ready" message. The timeout=10 parameter means it gives up if you do not start speaking within 10 seconds.
- Speak clearly and at normal volume. Very quiet speech may not register above the ambient noise threshold.
- Confirm you have an active internet connection. The Google Web Speech API requires network access.
Need more help? Help me troubleshoot my voice recording and transcription
✔️ Awesome, I've got everything!
Great. Double check you have saved voice_recorder.py with all three functions: record_answer(), save_audio_wav(), and transcribe_audio().
ⓧ I'd like to double check the full code
Here is the complete voice_recorder.py for comparison. Scroll up and confirm each function is present in this order: imports, ensure_recordings_dir(), save_audio_wav(), record_answer(), transcribe_audio().
Wire voice input into the interview loop
Your recording and transcription module is ready. Now you need to connect it to the existing interview flow in main.py so the learner can choose between speaking and typing.
- Open main.py and find the import line at the top of the file:
from ai_interviewer import generate_question, evaluate_answer
- Add this import line directly below it:
from voice_recorder import record_answer, transcribe_audio
- Next, add the get_answer_voice() function between display_scorecard() and get_answer_typed():
def get_answer_voice() -> tuple:
audio_data, wav_file = record_answer(time_limit=60)
if audio_data is None:
return "", ""
print("\n[Transcribing your answer...]")
text = transcribe_audio(audio_data)
if not text:
print("[Transcription failed. Would you like to type your answer instead? (y/n)]")
choice = input("> ").strip().lower()
if choice == "y":
text = input("\nType your answer: ")
return text, wav_file or ""
return "", wav_file or ""
print(f"\n[Transcription]: {text}")
return text, wav_file or ""
What does this function do?
- It calls record_answer() to capture your speech and save the WAV file.
- If recording succeeds, it passes the audio to transcribe_audio() for conversion to text.
- If transcription fails, it offers a keyboard fallback so you are never stuck with no way to answer.
- Save main.py and verify the import works by running:
python -c "from main import get_answer_voice; print('Voice function ready')"
You should see Voice function ready printed with no import errors.
Seeing an ImportError?
- Check that voice_recorder.py is saved in the same folder as main.py.
- Verify the import line reads exactly from voice_recorder import record_answer, transcribe_audio.
Still stuck? Help me fix my import error in main.py
Now you need to update the main() function to let the user choose between voice and keyboard input.
- In main.py, find this line inside main():
role = "AI/ML Engineer" if role_choice == "2" else "Software Engineer"
- Add the input mode selection menu directly below that line, so the next lines read:
print("\nInput mode:")
print("[1] Voice (speak your answer)")
print("[2] Keyboard (type your answer)")
mode = input("Select: ").strip()
use_voice = mode != "2"
- Now find the line that calls the typed input function:
answer, wav_file = get_answer_typed()
- Replace it with this conditional block so voice is used when selected:
if use_voice:
answer, wav_file = get_answer_voice()
else:
answer, wav_file = get_answer_typed()
Why default to voice?
The line use_voice = mode != "2" means any input besides "2" (including just pressing Enter) defaults to voice mode. The whole point of this simulator is to practice speaking, so voice is the encouraged path.
- Save main.py.
Before you run the full interview, what do you think will happen when you speak your answer? Will you see the transcription before or after the AI evaluation?
- Run the interview simulator:
python main.py
- Select a role (1 or 2).
- Select 1 for Voice input mode.
- When the interview question appears, wait for the "Ready" message, then speak your answer clearly for 15 to 30 seconds.
- Stop speaking and wait for silence detection to end the recording.
You should see your transcribed text printed in the terminal, followed by the full AI scorecard with scores for technical accuracy, completeness, and communication. A WAV file should also appear in the recordings/ folder.
- Confirm the WAV file was saved by running:
ls recordings/
You should see a file named something like interview_20260727_143022.wav (with your current date and time).
Recording works but transcription is empty?
- Ensure you have an active internet connection. The Google Web Speech API sends audio to Google's servers for processing.
- Try speaking more clearly and closer to your microphone. Background noise can cause the API to return empty results.
- If voice consistently fails, select option 2 for keyboard mode. The AI evaluation works identically with typed input.
Need help? Help me debug my voice interview flow
✔️ Awesome, I've got everything!
Great. Double check you have saved main.py with the new import, the get_answer_voice() function, the input mode menu, and the voice/keyboard conditional.
ⓧ I'd like to double check the full code
Here is the complete updated main.py at this point. Scroll through and confirm each section matches:
from ai_interviewer import generate_question, evaluate_answer
from voice_recorder import record_answer, transcribe_audio
def display_scorecard(scores: dict):
print("\n" + "=" * 50)
print(" INTERVIEW SCORECARD")
print("=" * 50)
for dimension in ["technical_accuracy", "completeness", "communication"]:
data = scores.get(dimension, {})
score = data.get("score", 0)
feedback = data.get("feedback", "")
bar = "#" * score + "." * (10 - score)
print(f"\n {dimension.replace('_', ' ').title()}: [{bar}] {score}/10")
print(f" {feedback}")
print(f"\n Overall Score: {scores.get('overall_score', 0)}/10")
print(f"\n Summary: {scores.get('summary', '')}")
print("=" * 50)
def get_answer_voice() -> tuple:
audio_data, wav_file = record_answer(time_limit=60)
if audio_data is None:
return "", ""
print("\n[Transcribing your answer...]")
text = transcribe_audio(audio_data)
if not text:
print("[Transcription failed. Would you like to type your answer instead? (y/n)]")
choice = input("> ").strip().lower()
if choice == "y":
text = input("\nType your answer: ")
return text, wav_file or ""
return "", wav_file or ""
print(f"\n[Transcription]: {text}")
return text, wav_file or ""
def get_answer_typed() -> tuple:
print("\nType your answer (press Enter twice to submit):")
lines = []
while True:
line = input()
if line == "":
if lines:
break
lines.append(line)
return "\n".join(lines), ""
def main():
print("\n" + "=" * 50)
print(" AI INTERVIEW SIMULATOR")
print(" Practice like it's the real thing.")
print("=" * 50)
print("\nSelect your target role:")
print("[1] Software Engineer (SWE)")
print("[2] AI/ML Engineer")
role_choice = input("Select: ").strip()
role = "AI/ML Engineer" if role_choice == "2" else "Software Engineer"
print("\nInput mode:")
print("[1] Voice (speak your answer)")
print("[2] Keyboard (type your answer)")
mode = input("Select: ").strip()
use_voice = mode != "2"
print(f"\n--- Interview for {role} ---")
print("\n[Generating your interview question...]\n")
question = generate_question(role)
print(f"INTERVIEWER: {question}\n")
if use_voice:
answer, wav_file = get_answer_voice()
else:
answer, wav_file = get_answer_typed()
if not answer:
print("\nNo answer received. Exiting.")
return
print("\n[Evaluating your answer...]\n")
try:
scores = evaluate_answer(question, answer)
display_scorecard(scores)
except Exception as e:
print(f"\n[Evaluation error: {e}]")
print("[The AI response may not have been valid JSON. Try again.]")
print("\n[Another round? Run the program again!]")
if __name__ == "__main__":
main()
You now have a working voice-powered interview simulator. You can speak your answers, hear yourself back via the saved WAV files, and get the same structured AI feedback. Next up, you will add Redis-backed persistence so your scores survive between sessions and you can track your improvement over time.
Track Progress with Redis
Your interview simulator works end-to-end. You can speak an answer, get a scorecard, and review your recordings. But close the terminal and everything vanishes.
You have no way to know whether you are improving, which dimensions you struggle with, or how your communication compares to last week. In this step, you will connect your simulator to Upstash Redis so that every session persists across restarts and you can track your progress over time.
In this step, get ready to:
- Create a session store module that saves and retrieves interview results from Redis.
- Add performance analytics that compute average scores, trends, and your weakest dimension.
- Wire the session store into the interview loop so results persist and a progress dashboard appears at startup.
Create session_store.py with Redis connection and session storage
This new module handles all communication with your Upstash Redis database. It needs three capabilities: connecting to Redis, saving a session, and retrieving past sessions.
- Create a new file called session_store.py in your project folder and add the following imports and connection function:
import os
import json
import datetime
import redis
from dotenv import load_dotenv
load_dotenv()
def get_redis_client():
redis_url = os.getenv("REDIS_URL")
if not redis_url:
print("[Warning: REDIS_URL not set. Session tracking disabled.]")
return None
try:
client = redis.from_url(redis_url, decode_responses=True)
client.ping()
return client
except redis.ConnectionError as e:
print(f"[Redis connection failed: {e}. Session tracking disabled.]")
return None
What does this code do?
get_redis_client() reads your REDIS_URL from the .env file, connects using the rediss:// TLS scheme Upstash requires, and pings the server to confirm connectivity.
If the URL is missing or the connection fails, it prints a warning and returns None so the rest of the app still runs without crashing.
- Save session_store.py and test the Redis connection by running this command:
python -c "from session_store import get_redis_client; c = get_redis_client(); print('Connected!' if c else 'Failed')"
You should see Connected! printed in your terminal.
Seeing a connection error?
- Confirm your REDIS_URL in .env starts with rediss:// (double s for TLS). Upstash requires TLS connections.
- Make sure you copied the full URL from the Upstash console including the port number at the end.
- Check that your virtual environment is active and redis is installed.
Why can't I connect to my Upstash Redis database?
- Add the save_session() function below get_redis_client() in session_store.py:
def save_session(question: str, answer: str, scores: dict, wav_file: str = ""):
client = get_redis_client()
if not client:
return None
session_id = datetime.datetime.now().strftime("session_%Y%m%d_%H%M%S")
session_data = {
"timestamp": datetime.datetime.now().isoformat(),
"question": question,
"answer": answer,
"scores": json.dumps(scores),
"wav_file": wav_file,
"overall_score": scores.get("overall_score", 0)
}
client.hset(session_id, mapping=session_data)
client.rpush("session_list", session_id)
return session_id
What does this code do?
Each session gets a unique ID based on the current timestamp (like session_20260727_143022). The session data is stored as a Redis hash with fields for the question, answer, scores, and WAV file path.
The session ID is also appended to a session_list Redis list. This gives you an ordered index of all sessions you can iterate through later.
- Add the get_history() function below save_session() in session_store.py:
def get_history() -> list:
client = get_redis_client()
if not client:
return []
session_ids = client.lrange("session_list", 0, -1)
sessions = []
for session_id in session_ids:
data = client.hgetall(session_id)
if data:
data["id"] = session_id
if "scores" in data:
data["scores"] = json.loads(data["scores"])
sessions.append(data)
return sessions
What does this code do?
get_history() reads all session IDs from the session_list Redis list, then fetches the full hash data for each one. It deserializes the scores from JSON back into a Python dictionary so calling code can work with them directly.
- Save session_store.py and verify the save-and-retrieve cycle works by running:
python -c "from session_store import save_session, get_history; save_session('test q', 'test a', {'technical_accuracy': {'score': 7, 'feedback': 'ok'}, 'completeness': {'score': 6, 'feedback': 'ok'}, 'communication': {'score': 8, 'feedback': 'ok'}, 'overall_score': 7.0, 'summary': 'test'}); print(len(get_history()), 'session(s) found')"
You should see 1 session(s) found (or more if you run it multiple times).
Seeing 0 sessions found?
- Make sure save_session() uses client.hset(session_id, mapping=session_data) not client.set(). Redis hashes require hset.
- Check that the client.rpush("session_list", session_id) line is present. Without it, get_history() has no list to iterate through.
Why is my Redis session data not persisting?
Add performance analytics
Storing raw sessions is useful, but you need aggregated insights to know whether you are improving. This function computes averages, trends, and identifies your weakest dimension.
- Add the first part of get_performance_summary() below get_history() in session_store.py:
def get_performance_summary() -> dict:
sessions = get_history()
if not sessions:
return {"message": "No sessions yet. Complete an interview to see your progress!"}
technical_scores = []
completeness_scores = []
communication_scores = []
for session in sessions:
scores = session.get("scores", {})
if "technical_accuracy" in scores:
technical_scores.append(scores["technical_accuracy"]["score"])
if "completeness" in scores:
completeness_scores.append(scores["completeness"]["score"])
if "communication" in scores:
communication_scores.append(scores["communication"]["score"])
What does this code do?
This first section of get_performance_summary() collects all individual dimension scores from your session history into separate lists. Each list holds one score per session, giving you the raw data to compute averages and detect trends.
- Continue the function by adding the helper functions and summary construction directly below the loop you just wrote (still inside get_performance_summary):
def avg(lst):
return round(sum(lst) / len(lst), 1) if lst else 0
def trend(lst):
if len(lst) < 2:
return "not enough data"
recent = avg(lst[-3:])
older = avg(lst[:-3]) if len(lst) > 3 else avg(lst[:1])
if recent > older:
return "improving"
elif recent < older:
return "declining"
return "stable"
summary = {
"total_sessions": len(sessions),
"technical_accuracy": {"average": avg(technical_scores), "trend": trend(technical_scores)},
"completeness": {"average": avg(completeness_scores), "trend": trend(completeness_scores)},
"communication": {"average": avg(communication_scores), "trend": trend(communication_scores)},
}
all_avgs = [
("technical_accuracy", summary["technical_accuracy"]["average"]),
("completeness", summary["completeness"]["average"]),
("communication", summary["communication"]["average"]),
]
summary["weakest_dimension"] = min(all_avgs, key=lambda x: x[1])[0] if all_avgs else "none"
return summary
What does this code do?
avg() computes a rounded mean. trend() compares your recent scores (last 3 sessions) against older scores to report whether you are improving, declining, or stable.
The final summary dictionary bundles averages, trends, and your weakest dimension into one object your dashboard can display.
- Save session_store.py and confirm the full module loads by running:
python -c "from session_store import get_performance_summary; print(get_performance_summary())"
You should see a dictionary with your test session's scores and trend data (or the data from earlier if you ran multiple tests).
✔️ Awesome, I've got everything!
Great. Make sure you have saved session_store.py before continuing.
ⓧ I'd like to double check the full code
Your session_store.py should contain these sections in order. Scroll up and confirm each one is present:
- Imports (os, json, datetime, redis, dotenv) and load_dotenv() call.
- get_redis_client() function that reads REDIS_URL, connects with from_url, pings, and handles errors.
- save_session() function that creates a timestamped session ID, builds a data dict, calls hset and rpush.
- get_history() function that reads session_list, fetches each hash, and deserializes scores.
- get_performance_summary() function with score collection loop, avg() and trend() helpers, summary construction, and weakest dimension.
Wire Redis into the interview loop
The session store is ready. Now you need to connect it to main.py so that results save automatically and a progress dashboard appears when you launch the program.
- In main.py, find the import lines at the top of the file:
from ai_interviewer import generate_question, evaluate_answer
from voice_recorder import record_answer, transcribe_audio
- Replace them with these three imports that add the session store functions:
from ai_interviewer import generate_question, evaluate_answer
from voice_recorder import record_answer, transcribe_audio
from session_store import save_session, get_history, get_performance_summary
- Add the display_progress() function below display_scorecard() in main.py:
def display_progress():
summary = get_performance_summary()
if "message" in summary:
print(f"\n{summary['message']}")
return
print("\n" + "=" * 50)
print(" PERFORMANCE DASHBOARD")
print("=" * 50)
print(f" Total Sessions: {summary['total_sessions']}")
print(f"\n Technical Accuracy: {summary['technical_accuracy']['average']}/10 ({summary['technical_accuracy']['trend']})")
print(f" Completeness: {summary['completeness']['average']}/10 ({summary['completeness']['trend']})")
print(f" Communication: {summary['communication']['average']}/10 ({summary['communication']['trend']})")
print(f"\n Focus area: {summary['weakest_dimension'].replace('_', ' ').title()}")
print("=" * 50)
What does this code do?
display_progress() calls your session store's analytics function and renders a formatted dashboard showing averages, trends, and which dimension needs the most work. If no sessions exist yet, it prints a friendly message instead of crashing.
- Save main.py and verify the import chain works by running:
python -c "from main import display_progress; display_progress()"
You should see your test session's performance data printed as a dashboard (or the data from the test you ran earlier).
Now you need to add a startup menu to main() so the learner can choose between starting an interview or viewing their progress.
- In main.py, find the main() function. Locate this line inside it:
print("\nSelect your target role:")
- Insert the following menu code directly ABOVE that line (after the four header print lines, before role selection):
print("\n[1] Start Interview")
print("[2] View Progress")
print("[3] Exit")
choice = input("\nSelect: ").strip()
if choice == "2":
display_progress()
input("\nPress Enter to continue...")
main()
return
elif choice == "3":
print("\nGood luck with your interviews!")
return
elif choice != "1":
print("Invalid choice.")
main()
return
What does this code do?
This adds a 3-option menu when the program starts. Option 2 shows the performance dashboard then recursively calls main() to return to the menu. Option 3 exits cleanly. Any other input besides 1 also loops back.
Only when the user selects 1 does execution continue to the existing role selection and interview logic below.
The last change is adding save_session() so scores persist after each evaluation.
- In the main() function, find the try block near the bottom. It currently looks like this:
try:
scores = evaluate_answer(question, answer)
display_scorecard(scores)
except Exception as e:
print(f"\n[Evaluation error: {e}]")
print("[The AI response may not have been valid JSON. Try again.]")
print("\n[Another round? Run the program again!]")
- Replace that entire try/except block and the print below it with this version that saves to Redis:
try:
scores = evaluate_answer(question, answer)
display_scorecard(scores)
save_session(question, answer, scores, wav_file)
print("\n[Session saved to Redis!]")
except Exception as e:
print(f"\n[Evaluation error: {e}]")
print("[The AI response may not have been valid JSON. Try again.]")
print("\n[Another round? Run the program again!]")
What changed?
Two lines were added inside the try block: save_session() persists the question, answer, scores, and WAV filepath to Redis. The confirmation print tells you the save succeeded.
- Save main.py.
✔️ Awesome, I've got everything!
Great. Make sure main.py is saved before running the final test.
ⓧ I'd like to double check the full code
Your main.py should contain these sections in order. Scroll up and confirm each one is present:
- Three import lines: ai_interviewer, voice_recorder, and session_store.
- display_scorecard() function (unchanged from before).
- display_progress() function calling get_performance_summary() and printing the dashboard.
- get_answer_voice() and get_answer_typed() functions (unchanged).
- main() with header prints, 3-option menu, menu routing, role selection, input mode, question generation, answer collection, evaluation with save_session() in the try block.
- if __name__ == "__main__": main() at the bottom.
Before you run the full test, what do you think will happen if you run the program, complete an interview, exit, then run it again and choose View Progress?
- Run your first interview session with Redis persistence by running:
python main.py
- Select 1 to start an interview.
- Pick a role and input mode.
- Answer the question (voice or keyboard).
- Confirm you see the scorecard followed by [Session saved to Redis!].
- Run the program a second time:
python main.py
- This time, select 2 to view your progress.
You should see the Performance Dashboard with your scores from the first session, including averages per dimension and a focus area recommendation. If you complete a second interview and check progress again, both sessions appear and trends begin to calculate.
Not seeing your session in View Progress?
- Check that [Session saved to Redis!] printed after your scorecard. If it did not, the save_session() call might be outside the try block.
- Verify session_store.py imports load_dotenv and calls load_dotenv() at module level. Without it, REDIS_URL will be None.
- If you see a connection warning, confirm your Upstash database is still active at console.upstash.com.
Why is my Redis session history empty even though I completed an interview?
Your interview simulator now remembers everything. Scores persist across sessions, trends reveal your trajectory, and you always know where to focus next. In the Secret Mission, you will take this further by building multi-round adaptive interviews that use your Redis history to dynamically adjust question difficulty.
Secret mission
Add Multi-Round Adaptive Interviews
Real interviews have multiple rounds that get harder as you go. Can you upgrade your simulator to run a 3-round adaptive interview that reads your Redis history, targets your weakest dimension, and adjusts difficulty between rounds?
Clean Up Your Resources
Clean Up Your Resources
This project runs on free tiers for both Upstash Redis and the Gemini API. There are no ongoing costs, so keeping everything running will not charge you anything.
Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Upstash Redis database (free tier, no cost).
- Google AI Studio API key (free tier, no billing required).
- Local project files (Python scripts, virtual environment, and recorded WAV files).
Keep everything running
No action needed. Choose this if you plan to keep practising interviews or want to revisit the project later.
- Both Upstash Redis and Google AI Studio are on free tiers with no idle charges.
- Your Redis data persists indefinitely. Free tier databases are archived after a minimum of 30 days of inactivity, but your data is backed up and restorable.
- Your local project files and WAV recordings remain on your Desktop.
Pause - I'll come back to this later
Shut down any running processes to free up memory. Your data stays safe in Redis and your files stay on disk.
- No action needed for Upstash Redis or Google AI Studio. Free tiers have no idle cost.
- If your virtual environment is activated, deactivate it by running:
- Your WAV recordings in the recordings/ folder remain on disk for self-review whenever you return.
deactivate
Delete - I don't want to use this again
Remove all project resources and cloud data to start fresh.
Delete your Upstash Redis database
- Go to console.upstash.com and sign in.
- Select your Redis database from the list.
- Scroll to the bottom of the Details page.
- Click Delete this database and confirm when prompted.
Delete your Google AI Studio API key
- Go to aistudio.google.com/app/apikey and sign in.
- Find the API key you created for this project in the keys list.
- Click the delete option next to your key and confirm the deletion.
Delete your local project files
Remove the project folder from your Desktop. This deletes all Python files, the virtual environment, your .env file, and any saved WAV recordings.
macOS
- Open your terminal and run:
cd ~/Desktop
ls
- Find your project folder name in the output, then delete it by running:
rm -rf your-project-folder-name
Windows
- Open PowerShell and run:
cd ~\Desktop
ls
- Find your project folder name in the output, then delete it by running:
Remove-Item -Recurse -Force your-project-folder-name
Nice Work!
Nice Work!
You just built a personal AI interview coach that listens to you speak, scores your answers against a structured rubric, and tracks your improvement over time.
You've learned how to:
- Built a complete AI interview simulator using the Gemini API with structured rubric-based evaluation (the LLM-as-a-Judge pattern) that scores answers on technical accuracy, completeness, and communication clarity.
- Integrated real-time speech recognition with the SpeechRecognition library and PyAudio for microphone capture, saving interview answers as WAV files for self-review.
- Added Redis-backed state management via Upstash to persist scores and track improvement across sessions with a performance dashboard.
- Secret Mission: Created adaptive multi-round interviews that dynamically adjust difficulty based on your historical performance data stored in Redis.
Ready to quiz yourself?