It all started on a random Saturday afternoon. I was playing around with the Google Gemini API, curious to see what these large language models were really capable of. I wasn’t trying to solve a grand problem or build a startup; I was just having fun and wanted to see if I could make it do something interesting.
My first thought was simple: “Could I get this thing to ask me questions?” I fed it a prompt for “JavaScript interview questions,” and the response was surprisingly good. That’s when the idea sparked. What if I could wrap a simple UI around this? What if I could build a little web app where an AI could interview me on any topic I wanted?
What began as a weekend experiment quickly spiraled into one of the most engaging and educational personal projects I’ve ever tackled. In this post, I’ll walk you through how my “just for fun” idea evolved into a full-blown AI Interviewer app.
The Big Idea
The concept was simple. I wanted a web app where I could:
Enter any technical topic (from “React Hooks” to “SQL Joins” to “Docker Networking”).
Have an AI ask me a series of relevant, high-quality questions on that topic.
Crucially, get a different set of questions every single time to keep me on my toes.
At the end, receive a detailed, AI-generated report card on my performance.
The final result is a sleek, dark-mode app that does exactly this, and I’m incredibly proud of how it turned out.
The Tech Deep Dive
I decided to build the entire application using JavaScript. This allowed me to move quickly and maintain a single language across the entire project.
Frontend: I went with plain, “vanilla” HTML, CSS, and JavaScript. This was a deliberate choice to keep the frontend light and to really focus on the core logic and UI/UX.
Backend: I chose Node.js with the Express.js framework. It’s fast, lightweight, and perfect for building the API that would act as the bridge between my frontend and the AI’s brain.
The AI: The magic comes from the Google Gemini API. I needed a powerful language model that could not only generate questions but also understand my nuanced instructions for creating the final feedback report.
Tuning the AI for Creativity
One of the first problems I hit was repetition. The AI would give me the same great questions for “JavaScript” every single time. The key to solving this was adjusting the AI’s temperature
—its "creativity" dial.
In my server.js
file, I configured the Gemini model with a high temperature to ensure it would generate unique responses for every request.
// Import the Google AI client
const { GoogleGenerativeAI } = require('@google/generative-ai');
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
// Define a generation config with a high temperature for unique responses
const generationConfig = {
temperature: 0.9,
};
// Initialize the model WITH the new generation configuration
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
generationConfig: generationConfig, // Pass our new config here
});
Setting temperature: 0.9
tells the model to take more risks and be more creative, which was the secret to getting fresh, non-repetitive questions every time.
Instructing the AI
I quickly learned that working with an LLM isn’t as simple as just asking a question. You have to become a “prompt engineer.” The real art is in crafting the perfect set of instructions to get the exact output you need.
For the final feedback report, I wanted a beautiful, interactive accordion. This meant I had to instruct the AI to format its entire response in a very specific HTML structure. My final prompt for the analysis looks something like this:
// A simplified look at the prompt sent to the AI
const prompt_text = `
You are a helpful and experienced tech interview coach.
Your entire response MUST be a single block of HTML content suitable for placing inside a <div>.
1. Begin with a "<div>" with the class "overall-summary".
2. After the summary, provide a series of accordion items using this exact HTML structure:
<div class="accordion-item">
<button class="accordion-header">...</button>
<div class="accordion-content">...</div>
</div>
IMPORTANT: Do not include any tags like <html>, <style>, or <script>.
Here is the interview data: ...
`;
Getting this prompt right took more trial and error than any other part of the project!
Safely Rendering AI-Generated HTML
My biggest struggle was handling the AI’s response on the frontend. Sometimes, the AI would “helpfully” wrap its perfect HTML inside Markdown code blocks, or even worse, include its own <style>
and <script>
tags! This would break my UI completely.
My initial attempts to clean this with simple string replacement were flaky. This led me to the real, industry-standard solution: using the browser’s built-in DOMParser
.
This was a game-changer. My final JavaScript code in the script.js finishInterview
function now safely parses and validates the response before it ever touches the live page.
// script.js
async function finishInterview() {
display.loadingText.textContent = 'Analyzing your performance...';
switchScreen('loading-screen');
try {
const response = await fetch(ANALYZE_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic: interviewTopic, qa_list: interviewData }),
});
if (!response.ok) throw new Error(`Server responded with status: ${response.status}`);
const result = await response.json();
// THIS IS THE FINAL, ROBUST PARSING AND VALIDATION LOGIC
if (result && result.analysis && typeof result.analysis === 'string') {
const cleanedHtml = result.analysis.replace(/^```
{% endraw %}
html\s*|
{% raw %}
```$/g, "").trim();
const parser = new DOMParser();
const doc = parser.parseFromString(cleanedHtml.trim(), 'text/html');
// Sanity Check: Does the AI response contain the HTML we actually want?
if (doc.querySelector('.accordion-item') || doc.querySelector('.overall-summary')) {
const desiredElements = doc.body.childNodes;
display.feedbackContent.innerHTML = ''; // Clear previous content
desiredElements.forEach((node) => {
display.feedbackContent.appendChild(node.cloneNode(true));
});
setupAccordion();
} else {
// This block runs if the AI sends back garbage (like just ```
{% endraw %}
html
{% raw %}
```)
throw new Error('The AI returned an empty or invalid response.');
}
} else {
throw new Error('Received invalid or empty analysis string from server.');
}
} catch (error) {
console.error('Analysis failed:', error);
display.feedbackContent.innerHTML = `<p class="error">Sorry, the AI was unable to provide feedback for this interview. Please try again with more detailed answers.</p>`;
}
This defensive approach ensures that no matter what the AI sends back, my application will never crash or display a broken UI.
Final Result
Final Thoughts
Building the AI Interviewer was an incredible learning experience. It taught me the practical realities of integrating with large language models, the importance of robust error handling, and the art of prompt engineering. If you’re a developer looking for a project that touches on every part of the modern tech stack, I can’t recommend building your own AI-powered tool enough.
You can check out the full source code on my GitHub here 👇:
https://github.com/praveen-sripati/ai-interviewer
Thanks for reading, and happy coding!
Want to see a random mix of weekend projects, half-baked ideas, and the occasional useful bit of code? Feel free to follow me on Twitter!
Follow @x
Top comments (0)