<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cyril Vitchenko</title>
    <description>The latest articles on DEV Community by Cyril Vitchenko (@vitchenkokir).</description>
    <link>https://dev.to/vitchenkokir</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4035378%2F2c65a043-838e-4269-9f92-49017e7c9aa3.png</url>
      <title>DEV Community: Cyril Vitchenko</title>
      <link>https://dev.to/vitchenkokir</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vitchenkokir"/>
    <language>en</language>
    <item>
      <title>I Built GrillKit: A Self-Hosted AI Interview Trainer That Actually Runs Interviews</title>
      <dc:creator>Cyril Vitchenko</dc:creator>
      <pubDate>Mon, 20 Jul 2026 05:46:46 +0000</pubDate>
      <link>https://dev.to/vitchenkokir/i-built-grillkit-a-self-hosted-ai-interview-trainer-that-actually-runs-interviews-5afb</link>
      <guid>https://dev.to/vitchenkokir/i-built-grillkit-a-self-hosted-ai-interview-trainer-that-actually-runs-interviews-5afb</guid>
      <description>&lt;p&gt;Ask ChatGPT for a system design question and it'll give you one. Answer it, and you'll get a reasonable reply back. Ask a follow-up, and it'll follow up. What you won't get is anything close to an actual interview: no score, no memory of what you already covered last week, no real signal on whether you're ready or just having a pleasant conversation about load balancers.&lt;/p&gt;

&lt;p&gt;That's the actual problem &lt;a href="https://github.com/GrillKit/grillkit" rel="noopener noreferrer"&gt;GrillKit&lt;/a&gt; tries to solve. It's an open-source, self-hosted technical interview trainer: point it at any OpenAI-compatible model, pick what you want to practice, and it runs an actual session — real questions from curated banks, real scoring, live coding against a real sandbox, and a history you can look back on afterward. I built it mainly for my own prep and kept going because the gap between "chatting about interview topics" and "practicing an interview" turned out to matter more than I expected.&lt;/p&gt;

&lt;p&gt;This post is less about showing off code and more about what it's actually like to use — and since it's open source, an invitation to help with the part that scales worst as a one-person project, which is mostly the question banks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why practicing alone in a chat window doesn't really work
&lt;/h2&gt;

&lt;p&gt;None of this is about the model being smart or dumb. It's about what a free-form chat can't give you no matter how good the model is.&lt;/p&gt;

&lt;p&gt;You end up being your own interviewer. There's no curriculum, so you ask about whatever comes to mind that day — which tends to be whatever you already feel solid on, not your actual weak spots. Close the tab and the session is gone, so there's no way to tell if you're improving or just remembering your best answers. A general assistant is also polite by default: give it a shaky answer and it'll often soften the feedback or move on unless you explicitly demand a grade, which a real interviewer won't do for you. And code doesn't run — you paste a snippet, the model reasons about it, but nothing executes, so the kind of bug that shows up the second you hit "run" just sails through untested.&lt;/p&gt;

&lt;p&gt;GrillKit is built around fixing each of those specifically. Questions come from curated tracks — Python, Database/SQL, System Design, Kafka, RabbitMQ, Docker, Kubernetes, Observability, Airflow — split by level, so you're pulling from an actual curriculum instead of improvising one. Every answer gets scored 1–5 against a rubric with up to two AI follow-ups, so a weak answer gets flagged instead of politely waved through. Sessions are saved, and you can mark any question "I already know this" so it stops coming up and you spend your time on what's still shaky — which, after a few sessions, feels genuinely different from grinding the same comfortable questions over and over. Coding tasks execute for real against a sandbox, so a bug shows up as a stack trace, not as something an AI quietly didn't notice. And there's an optional per-round timer if you want actual time pressure instead of unlimited thinking time.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick tour
&lt;/h2&gt;

&lt;p&gt;The home page is a dashboard of recent sessions — enough to see what you've done and jump into a new one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnokehp7u6d5gqxkgvztf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnokehp7u6d5gqxkgvztf.png" alt="GrillKit dashboard" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Setup walks you through session mode (theory only, coding only, or one then the other), tracks/levels/topics, how many questions or tasks, whether to exclude known questions, and the optional timer. During a theory section it's real-time Q&amp;amp;A: type an answer, dictate it, or record audio if your model accepts audio input. During a coding section you get a proper editor, a Run button against public tests, and Submit for the real evaluation. Once you finish, a results hub links out to full review pages for both sections — the whole conversation, the code you actually submitted, every score, still there whenever you want to look back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real scoring, real code
&lt;/h2&gt;

&lt;p&gt;Two things make this feel like practice instead of a quiz. Scoring is structured: every answer gets a 1–5 score and specific feedback, checked against a rubric pulled from the question bank rather than freestyled, so the same answer gets held to the same standard whether it's your first attempt of the day or your fifth. And coding tasks genuinely execute — there's a real sandbox behind the editor (&lt;a href="https://github.com/judge0/judge0" rel="noopener noreferrer"&gt;Judge0&lt;/a&gt;, running in its own optional set of containers), so if your fix still throws on an empty list, you find out from a stack trace. Tasks with a clean input/output contract get checked directly against test cases; open-ended ones like bug hunts get graded by the AI against a short checklist of what a correct fix needs to do, since "find the bug" doesn't have one canonical answer to diff against.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2yl21cpjadhoinweqj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl2yl21cpjadhoinweqj6.png" alt="Coding interview session with Monaco editor" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-hosted, and you pick the model
&lt;/h2&gt;

&lt;p&gt;Everything stays on your machine — the database, your session history, your provider API key, any downloaded voice models. The only thing that leaves is whatever text you send to whichever model you've configured, and that's entirely your call: OpenAI, a local Ollama model, vLLM, anything that speaks the same API. Practically, that means you're not paying a subscription on top of your model costs, and you're not locked into one provider if you'd rather run something local and free. Voice practice, if you turn it on, is fully offline too — Whisper for dictating or recording answers, Piper for reading questions aloud, both downloaded once and then run entirely locally. Saying an answer out loud is a different skill than typing it, and it's worth being able to practice that way without an audio file going anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Help fill in the question banks
&lt;/h2&gt;

&lt;p&gt;The part of GrillKit that scales worst as a one-person project is content, not code. Coverage right now is decent for Python, Database/SQL, System Design, Kafka, RabbitMQ, Docker, Kubernetes, Observability, and Airflow — but that's nowhere near everything people actually get asked. There's no Go, no Java, no frontend track, nothing ML-specific, and plenty of gaps even inside the tracks that exist.&lt;/p&gt;

&lt;p&gt;Adding a question doesn't touch any Python. It's a YAML file under &lt;code&gt;data/questions/{track}/{level}/{category}.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Data&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Structures"&lt;/span&gt;
&lt;span class="na"&gt;track&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python"&lt;/span&gt;
&lt;span class="na"&gt;level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;junior"&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lists,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tuples,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dicts,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sets"&lt;/span&gt;

&lt;span class="na"&gt;questions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ds-001"&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;knowledge"&lt;/span&gt;
    &lt;span class="na"&gt;difficulty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tuple"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;question&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;difference&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;between&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;list&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tuple?"&lt;/span&gt;
      &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="na"&gt;expected_points&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lists&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mutable,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tuples&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tuples&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;can&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;used&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;as&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dict&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;keys,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lists&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;cannot"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;category&lt;/code&gt;/&lt;code&gt;track&lt;/code&gt;/&lt;code&gt;level&lt;/code&gt; say where it lives, &lt;code&gt;difficulty&lt;/code&gt; runs 1 to 5, and &lt;code&gt;expected_points&lt;/code&gt; are the rubric bullets the AI checks your answer against when it scores you — you don't need to write follow-ups yourself, those get generated on the fly. Coding tasks work the same way, one folder over in &lt;code&gt;data/coding/&lt;/code&gt;. If there's a track you wish existed or a category you know is thin, that's honestly the most useful contribution right now, more than a code PR would be. &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; has the full guidelines; the short version is match the existing schema, keep the wording clear, and open a PR.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/GrillKit/grillkit.git
&lt;span class="nb"&gt;cd &lt;/span&gt;grillkit
docker compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;http://localhost:8000&lt;/code&gt;, add a model on &lt;code&gt;/config&lt;/code&gt;, and start a session from &lt;code&gt;/setup&lt;/code&gt;. Add &lt;code&gt;--profile coding&lt;/code&gt; to the compose command if you want live coding tasks.&lt;/p&gt;

&lt;p&gt;It's Apache-2.0. If something's broken, confusing, or just missing your interview topic, open an issue on the &lt;a href="https://github.com/GrillKit/grillkit" rel="noopener noreferrer"&gt;repo&lt;/a&gt; — and if you send a PR with a new question bank, even better.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>career</category>
    </item>
  </channel>
</rss>
