Chapter 112 — GenAI Interview: Hands-On Coding Tasks (with Solutions)

📖 What to Expect

Practical GenAI interviews often ask you to write code live: call an LLM API, stream a response, get structured JSON, chunk text, or build a mini RAG. Below are common tasks in TypeScript (Node.js) and Python, with solutions you can adapt. Always mention API keys come from environment variables, never hard-coded.


1. Call an LLM API (Node.js / TypeScript)

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();   // reads ANTHROPIC_API_KEY from the environment

export async function summarise(text: string): Promise<string> {
  const res = await client.messages.create({
    model: "claude-sonnet-5-5",
    max_tokens: 300,
    system: "You summarise text in 3 bullet points, simple English.",
    messages: [{ role: "user", content: text }],
  });
  return res.content.find((b) => b.type === "text")?.text ?? "";
}

2. Same in Python

python
import anthropic

client = anthropic.Anthropic()   # uses ANTHROPIC_API_KEY

def summarise(text: str) -> str:
    res = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=300,
        system="You summarise text in 3 bullet points, simple English.",
        messages=[{"role": "user", "content": text}],
    )
    return "".join(b.text for b in res.content if b.type == "text")

3. Streaming endpoint with Express (Server-Sent Events)

import express from "express";
import Anthropic from "@anthropic-ai/sdk";

const app = express();
const client = new Anthropic();
app.use(express.json());

app.post("/api/chat/stream", async (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");
  res.flushHeaders();

  const stream = client.messages.stream({
    model: "claude-sonnet-5-5",
    max_tokens: 1024,
    messages: req.body.messages,            // validate in real code
  });

  req.on("close", () => stream.abort());    // stop paying if the user leaves

  stream.on("text", (delta) => res.write(`data: ${JSON.stringify({ delta })}\n\n`));
  stream.on("error", () => { res.write(`event: error\ndata: {}\n\n`); res.end(); });
  await stream.finalMessage().catch(() => {});
  res.write("event: done\ndata: {}\n\n");
  res.end();
});

4. React client that reads the stream

import { useState } from "react";

export function Chat() {
  const [answer, setAnswer] = useState("");
  const [loading, setLoading] = useState(false);

  async function ask(question: string) {
    setAnswer("");
    setLoading(true);
    const res = await fetch("/api/chat/stream", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages: [{ role: "user", content: question }] }),
    });
    const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
    let buffer = "";
    while (true) {
      const { value, done } = await reader.read();
      if (done) break;
      buffer += value;
      const events = buffer.split("\n\n");
      buffer = events.pop() ?? "";                 // keep incomplete event
      for (const evt of events) {
        const line = evt.split("\n").find((l) => l.startsWith("data: "));
        if (!line || evt.startsWith("event:")) continue;
        const { delta } = JSON.parse(line.slice(6));
        if (delta) setAnswer((a) => a + delta);
      }
    }
    setLoading(false);
  }

  return (
    <div>
      <button onClick={() => ask("Explain RAG in 3 lines")} disabled={loading}>Ask</button>
      <p style={{ whiteSpace: "pre-wrap" }}>{answer}</p>
    </div>
  );
}

5. Structured JSON output with validation and retry

interface Ticket { category: "billing" | "technical" | "account" | "other"; urgency: "low" | "medium" | "high"; summary: string }

function isTicket(v: any): v is Ticket {
  return (
    v && ["billing", "technical", "account", "other"].includes(v.category) &&
    ["low", "medium", "high"].includes(v.urgency) && typeof v.summary === "string"
  );
}

export async function classifyTicket(text: string): Promise<Ticket> {
  const res = await client.messages.create({
    model: "claude-haiku-5-5",                          // small, fast model is enough
    max_tokens: 300,
    tools: [{
      name: "save_ticket",
      description: "Save the classified support ticket.",
      input_schema: {
        type: "object",
        properties: {
          category: { type: "string", enum: ["billing", "technical", "account", "other"] },
          urgency: { type: "string", enum: ["low", "medium", "high"] },
          summary: { type: "string", description: "One sentence" },
        },
        required: ["category", "urgency", "summary"],
      },
    }],
    tool_choice: { type: "tool", name: "save_ticket" },   // force structured output
    messages: [{ role: "user", content: `<ticket>${text}</ticket>` }],
  });
  const block = res.content.find((b) => b.type === "tool_use");
  if (!block || !isTicket(block.input)) throw new Error("Invalid classification");
  return block.input;
}

6. Chunk text with overlap (Python)

python
def chunk_text(text: str, chunk_size: int = 800, overlap: int = 100) -> list[str]:
    """Split by paragraphs, then pack paragraphs into chunks of ~chunk_size characters."""
    paragraphs = [p.strip() for p in text.split("\n\n") if p.strip()]
    chunks, current = [], ""
    for p in paragraphs:
        if len(current) + len(p) + 2 <= chunk_size:
            current = f"{current}\n\n{p}" if current else p
        else:
            if current:
                chunks.append(current)
            tail = current[-overlap:] if current else ""   # carry overlap into next chunk
            current = f"{tail}\n\n{p}" if tail else p
            while len(current) > chunk_size:               # very long paragraph → hard split
                chunks.append(current[:chunk_size])
                current = current[chunk_size - overlap:]
    if current:
        chunks.append(current)
    return chunks

Mention: production code usually counts tokens (tokenizer) instead of characters and splits by headings.

7. Cosine similarity search over embeddings (Python, NumPy)

python
import numpy as np

def top_k(query_vec: np.ndarray, doc_vecs: np.ndarray, k: int = 5) -> list[int]:
    q = query_vec / np.linalg.norm(query_vec)
    d = doc_vecs / np.linalg.norm(doc_vecs, axis=1, keepdims=True)
    scores = d @ q
    return list(np.argsort(-scores)[:k])

For large data, use a vector database instead of brute force.

8. Mini RAG in Python (in-memory)

python
import anthropic
import numpy as np

client = anthropic.Anthropic()

# uses chunk_text (task 6) and top_k (task 7) from above

class MiniRAG:
    def __init__(self, embed_fn):
        self.embed = embed_fn            # function: list[str] -> np.ndarray (n, dim)
        self.chunks: list[str] = []
        self.vectors = None

    def add_documents(self, docs: list[str]):
        for doc in docs:
            self.chunks.extend(chunk_text(doc))
        self.vectors = self.embed(self.chunks)

    def ask(self, question: str, k: int = 4) -> str:
        q = self.embed([question])[0]
        ids = top_k(q, self.vectors, k)
        context = "\n\n".join(f"[{i}] {self.chunks[i]}" for i in ids)
        res = client.messages.create(
            model="claude-sonnet-5-5",
            max_tokens=500,
            system="Answer only from the context. Cite chunk numbers like [3]. "
                   "If the answer is not in the context, say you don't know.",
            messages=[{"role": "user", "content": f"<context>\n{context}\n</context>\n\nQuestion: {question}"}],
        )
        return "".join(b.text for b in res.content if b.type == "text")

9. Retry with exponential backoff for rate limits (TypeScript)

async function withRetry<T>(fn: () => Promise<T>, retries = 4): Promise<T> {
  for (let attempt = 0; ; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const status = err?.status;
      const retryable = status === 429 || (status >= 500 && status < 600);
      if (!retryable || attempt >= retries) throw err;
      const delay = Math.min(30_000, 500 * 2 ** attempt) * (0.5 + Math.random());   // jitter
      await new Promise((r) => setTimeout(r, delay));
    }
  }
}

(Official SDKs already retry some errors automatically — mention that and configure maxRetries.)

10. Count tokens / estimate cost before sending

const count = await client.messages.countTokens({
  model: "claude-sonnet-5-5",
  messages: [{ role: "user", content: longText }],
});
const estimatedInputCost = (count.input_tokens / 1_000_000) * INPUT_PRICE_PER_MILLION;

11. Simple evaluation script (Python)

python
import json

def evaluate(test_file: str, answer_fn, judge_fn) -> float:
    """test_file: JSONL with {"question": ..., "expected": ...}."""
    results = []
    with open(test_file) as f:
        for line in f:
            case = json.loads(line)
            answer = answer_fn(case["question"])
            verdict = judge_fn(case["question"], case["expected"], answer)   # returns True/False
            results.append({**case, "answer": answer, "pass": verdict})
    score = sum(r["pass"] for r in results) / len(results)
    print(f"Pass rate: {score:.1%} ({len(results)} cases)")
    for r in results:
        if not r["pass"]:
            print("FAIL:", r["question"])
    return score

12. Conversation memory: keep recent turns, summarise older ones

async function buildHistory(messages: Anthropic.MessageParam[], keepLast = 8) {
  if (messages.length <= keepLast) return messages;
  const older = messages.slice(0, -keepLast);
  const summary = await summarise(older.map((m) => `${m.role}: ${JSON.stringify(m.content)}`).join("\n"));
  return [
    { role: "user" as const, content: `Summary of earlier conversation:\n${summary}` },
    { role: "assistant" as const, content: "Understood." },
    ...messages.slice(-keepLast),
  ];
}

🧠 What Interviewers Look For

  • Clean API usage with keys from environment variables.
  • Error handling: timeouts, retries, invalid outputs.
  • Validation of model output before using it.
  • Security awareness: permissions in code, untrusted input in tags.
  • Thinking about cost and latency (model choice, streaming, caching).

← Scenario & Design Questions | Index | Next: Coding Question Tracker →