DEV Community

Cover image for Turn Any Job Description Into a Mock Interview Pack With 70 Lines of Node.js
Karuha
Karuha

Posted on

Turn Any Job Description Into a Mock Interview Pack With 70 Lines of Node.js

Most interview prep fails because it starts from generic question lists. Start from the job description instead: extract role signals, turn them into technical and behavioral prompts, then rehearse the gaps. Below is a small dependency-free Node.js script that turns any JD into a focused mock interview pack.

If you are applying to developer roles, you have probably seen the same prep advice recycled everywhere: grind more LeetCode, memorize STAR, read system design notes, repeat.

That advice is not wrong. It is just too broad.

A frontend platform role, a backend payments role, and a data engineering role can all say “Software Engineer” while testing completely different things. The job description is the closest public artifact you have to the interview rubric. It contains repeated nouns, required technologies, soft-skill verbs, and business outcomes. Those signals are enough to build a better practice plan than a generic checklist.

Let’s build a tiny local tool for that.

What are we building?

A CLI that reads a job description from a .txt file and prints a JSON practice pack:

  • the most common role keywords
  • technical questions mapped to detected skills
  • behavioral questions mapped to role signals
  • risk areas you should prepare concrete examples for
  • a 7-day prep schedule

No API key. No package install. No model call. The point is not to “AI-generate perfect answers.” The point is to create a structured rehearsal surface from the exact role you are applying for.

Step 1: Save a job description

Create jd.txt:

Senior Frontend Engineer

We use React, TypeScript, GraphQL, Jest, accessibility, performance budgets,
design systems, and mentoring. You will own checkout flow reliability and
collaborate with product and design.
Enter fullscreen mode Exit fullscreen mode

Use a real JD when you run this. Keep the responsibilities, requirements, and nice-to-have sections. Delete salary, benefits, and boilerplate company text unless it contains role-specific language.

Step 2: Create the script

Save this as interview-pack.js:

#!/usr/bin/env node
const fs = require('node:fs');

const inputPath = process.argv[2];

if (!inputPath) {
  console.error('Usage: node interview-pack.js <job-description.txt>');
  process.exit(1);
}

const text = fs.readFileSync(inputPath, 'utf8');

const STOP = new Set(
  'a an and are as at be by for from has have in into is it of on or our the this to we with you your will'.split(' ')
);

const SKILL_MAP = [
  [
    'react',
    [
      'Explain a time you reduced unnecessary React re-renders.',
      'Build a controlled input and explain state ownership.'
    ]
  ],
  [
    'typescript',
    [
      'When would you use a discriminated union instead of optional fields?',
      'How do you model API errors without losing type safety?'
    ]
  ],
  [
    'graphql',
    [
      'How would you debug an over-fetching GraphQL query?',
      'Design a pagination strategy for a GraphQL list.'
    ]
  ],
  [
    'performance',
    [
      'What metrics would you check before optimizing a slow page?',
      'Walk through your first 15 minutes debugging a p95 latency spike.'
    ]
  ],
  [
    'accessibility',
    [
      'How do you test keyboard navigation in a custom component?',
      'What ARIA usage have you removed rather than added?'
    ]
  ],
  [
    'jest',
    [
      'What makes a test valuable instead of brittle?',
      'How would you test a debounced search box?'
    ]
  ],
  [
    'design system',
    [
      'How do you introduce a breaking component API change?',
      'What belongs in a design system and what should stay app-specific?'
    ]
  ]
];

const GENERIC_TECH = [
  'Describe the most production-like bug you fixed in this area.',
  'What trade-off would you make if delivery speed and reliability conflict?',
  'How would you explain this technical decision to a non-engineer?'
];

const BEHAVIORAL = [
  ['ownership', 'Tell me about a time you owned a project with unclear requirements.'],
  ['collaborate', 'Tell me about a disagreement with product, design, or another engineer.'],
  ['mentor', 'Tell me about a time you helped another engineer level up.'],
  ['reliability', 'Tell me about a production incident or near miss you learned from.'],
  ['lead', 'Tell me about a time you led without formal authority.']
];

function normalize(value) {
  return value.toLowerCase().replace(/[^a-z0-9+#\s-]/g, ' ');
}

function keywords(input, limit = 12) {
  const counts = new Map();

  for (const token of normalize(input).split(/\s+/)) {
    if (token.length < 3 || STOP.has(token)) continue;
    counts.set(token, (counts.get(token) || 0) + 1);
  }

  return [...counts.entries()]
    .sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
    .slice(0, limit)
    .map(([word]) => word);
}

function matchingSkills(input) {
  const lower = normalize(input);
  return SKILL_MAP.filter(([skill]) => lower.includes(skill));
}

function makePack(input) {
  const keys = keywords(input);
  const skills = matchingSkills(input);

  const technicalQuestions = skills.flatMap(([skill, questions]) =>
    questions.map((question) => ({ skill, question }))
  );

  const behavioralQuestions = BEHAVIORAL
    .filter(([signal]) => normalize(input).includes(signal))
    .map(([signal, question]) => ({ signal, question }));

  return {
    keywords: keys,
    technicalQuestions: technicalQuestions.length
      ? technicalQuestions
      : GENERIC_TECH.map((question) => ({ skill: keys[0] || 'role', question })),
    behavioralQuestions: behavioralQuestions.length
      ? behavioralQuestions
      : BEHAVIORAL.slice(0, 3).map(([signal, question]) => ({ signal, question })),
    riskAreas: keys.slice(0, 5).map(
      (keyword) => `Prepare a concrete story or project detail for "${keyword}".`
    ),
    sevenDayPlan: [
      'Day 1: rewrite the JD into 5 role outcomes.',
      'Day 2: answer every technical question out loud, no notes.',
      'Day 3: implement one small exercise related to the top skill.',
      'Day 4: build 4 STAR stories mapped to the JD signals.',
      'Day 5: run a 45-minute mock interview and record it.',
      'Day 6: fix the two weakest answers, not all answers.',
      'Day 7: rehearse setup, questions for interviewer, and opening pitch.'
    ]
  };
}

console.log(JSON.stringify(makePack(text), null, 2));
Enter fullscreen mode Exit fullscreen mode

Run it:

node interview-pack.js jd.txt
Enter fullscreen mode Exit fullscreen mode

You should see output like this:

{
  "keywords": [
    "accessibility",
    "budgets",
    "checkout",
    "collaborate",
    "design",
    "engineer",
    "flow",
    "frontend",
    "graphql",
    "jest",
    "mentoring",
    "performance"
  ],
  "technicalQuestions": [
    {
      "skill": "react",
      "question": "Explain a time you reduced unnecessary React re-renders."
    },
    {
      "skill": "typescript",
      "question": "When would you use a discriminated union instead of optional fields?"
    }
  ],
  "behavioralQuestions": [
    {
      "signal": "collaborate",
      "question": "Tell me about a disagreement with product, design, or another engineer."
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Your full output will include more questions and the 7-day plan.

Why this works better than a generic list

A generic interview list optimizes for coverage. A JD-derived pack optimizes for relevance.

If a role repeats “reliability,” “checkout,” “payments,” and “incident response,” you should not spend the same amount of time on abstract frontend trivia as someone applying to a design-system role. You need production stories, failure-mode thinking, and clear examples of trade-offs under pressure.

If a role mentions “mentoring,” “cross-functional,” and “roadmap,” you should prepare behavioral stories about influence, disagreement, and ambiguity. A technically strong answer can still miss the rubric if the role is really asking whether you can operate at senior scope.

The keyword list is not magic. It is a forcing function. It makes you ask: “Can I tell a real story about this word?”

How to use the pack without sounding scripted

Do not write perfect paragraphs for every question. That is how candidates start sounding like they are reading from a document.

Use this format instead:

Prep item What to write What to rehearse
Technical question 3 bullets: context, trade-off, result Explain it out loud in 90 seconds
Behavioral question STAR outline, not full prose Tell it twice: structured, then conversational
Risk area One project or bug that proves it A follow-up question you hope they ask
7-day plan One deliverable per day Stop when the deliverable is done

The “structured, then conversational” part matters. Structured practice makes the story coherent. Conversational practice keeps you human.

A better technical exercise

For developer interviews, add one more field to each technical question: a tiny build task.

Example:

const buildTasks = {
  react: 'Build a controlled search box with loading, empty, and error states.',
  typescript: 'Model a success/error API response as a discriminated union.',
  graphql: 'Sketch a cursor pagination response and explain the edge cases.',
  performance: 'Measure and reduce one unnecessary render in a small demo.',
  accessibility: 'Make a custom dropdown keyboard navigable.'
};
Enter fullscreen mode Exit fullscreen mode

Interviewers rarely only test whether you know a definition. They test whether you can move between code, trade-offs, and explanation. A good prep session should do the same:

  1. Build something small.
  2. Explain the decisions out loud.
  3. Ask yourself what breaks at scale.
  4. Turn the answer into a shorter version.

That last step is underrated. In a live interview, a correct 6-minute answer can feel worse than a clear 90-second answer with room for follow-up.

Where AI fits, and where it does not

This script intentionally avoids calling an AI API because the first pass should be deterministic. You want to see the raw signals in the JD before a model smooths them out.

After that, AI can be useful in three narrow ways:

  • ask follow-up questions for the weakest items
  • convert a rough STAR outline into clearer spoken English
  • run a timed mock interview so you practice under pressure

Do not use AI to invent experience. Interviewers notice when a story has no operational detail: no constraints, no trade-off, no metric, no human friction. Use AI to pressure-test your real material, not replace it.

If you want a dedicated tool for the mock-interview step, aceround.app — AI interview assistant is one option. I would still run the local JD pass first; the better your input material is, the better any practice tool becomes.

How to extend the script

A few useful extensions:

  • Add skill packs for your stack: Python, Go, Java, SQL, cloud, data engineering.
  • Add company-level signals: “startup,” “enterprise,” “regulated,” “global,” “on-call.”
  • Export Markdown instead of JSON so you can keep the pack in your notes.
  • Add a scoring pass after mock practice: clarity, specificity, trade-off, result.

Keep it small. The goal is not to build an applicant tracking system. The goal is to walk into the interview with sharper reps than “I read 200 questions last night.”

FAQ

Should I paste the whole job description?

Yes, but remove generic company boilerplate. The responsibilities and requirements sections are the most useful. If the same word appears in both sections, treat it as high priority.

Is keyword matching too simple?

It is simple by design. For interview prep, explainability beats cleverness. If the script says “reliability” is a risk area, you can immediately decide whether you have a story for it. A black-box score is less useful.

What if the JD is vague?

Use the generic questions, then add signals from the recruiter screen. Vague JDs are common. After the first call, update jd.txt with the recruiter’s language and rerun the script.

How long should each answer be?

Aim for 60-120 seconds for most behavioral and technical explanation answers. Senior system design answers can run longer, but even there your opening should be short and structured.

Should I use this for every application?

Use it for roles you actually care about. For low-priority applications, it is enough to scan the keywords and update your story bank.

Sources and disclosure

Further reading: the Node.js file system documentation for fs.readFileSync, DEV Community editor guidance for post metadata, and the DEV Community Code of Conduct for disclosure expectations.

Disclosure: this post was drafted with AI assistance, then edited and checked by a human. The code path above was run locally with Node.js before publishing.

Top comments (0)