Every experienced engineer has been on both sides of a bug report. You either get a perfect, self-contained snippet that reproduces the bug in 2 seconds — or you get a wall of text saying "my app crashes sometimes, here's the stack trace, what's wrong?" The first gets fixed. The second goes into the backlog and dies.
The difference is a minimal reproducible example (MRE). It's not just a nice-to-have; it's the single most effective debugging tool you can learn. But writing a good MRE is harder than it looks. Most people skip the "minimal" part and dump their entire project. Others strip so much that the bug disappears. Here's how to find the sweet spot.
What Makes an MRE 'Good'?
A good MRE has three properties: it's minimal, it's complete, and it's verifiable. Minimal means you've removed everything that isn't strictly necessary to trigger the bug. Complete means you haven't left out any critical step — the code runs as-is. Verifiable means I can run it and see the bug myself without guessing what you meant.
The canonical Stack Overflow MCVE guide is a great starting point, but I want to go deeper. Here are the non-obvious rules I've learned from years of debugging production incidents.
Rule 1: Eliminate All External Dependencies
If your bug only happens with a specific database, network service, or file, replace it with a local equivalent. For example, if the bug is a race condition when reading from S3, write a local file server that simulates the latency. If the bug is in a SQL query against PostgreSQL, switch to SQLite with the same schema. The goal is to make the MRE runnable on any machine with zero setup beyond installing dependencies.
I once spent three days debugging a Node.js memory leak that only occurred in production under high load. After creating an MRE that used a simple event loop timer instead of the actual message queue, I found the leak in 30 minutes. The external system was a red herring.
Rule 2: Start from Scratch — Don't Trim Your Project
A common mistake is taking your existing project and deleting files until the bug reproduces. You'll end up with a Frankenstein's monster of half-removed code. Instead, start a new directory. Write the smallest possible program that does the thing that breaks. Then add back pieces one by one until the bug appears.
This inversion of approach forces you to understand the actual root cause. If you can't reproduce the bug from scratch, you don't understand it well enough to file a report.
If you can't reproduce the bug in a fresh project, your bug report should say: 'I tried to create an MRE but the bug only appears in my full project. Here's what I think is relevant...' That honesty is more useful than a half-baked MRE.
A War Story: The Case of the Vanishing Response
Last year, a teammate filed a bug: 'The API sometimes returns an empty response for user profile requests. Happens randomly, maybe once every 100 requests. Node.js, Express, MongoDB.' The ticket had a stack trace pointing to an async middleware but no MRE.
I asked for an MRE. Three days later, he sent me a zip of the entire project — 15,000 lines of code. I spent an hour trying to reproduce it locally but couldn't. The bug was intermittent even in his environment.
Frustrated, I built my own MRE: a simple Express server with a single route that queries an in-memory array (simulating MongoDB) and returns the result. I added a setTimeout to simulate async behavior. The bug reproduced on the first try. The issue was a missing return statement in an async function — the route handler continued executing after sending the response, causing a race condition where the next request would sometimes get an empty body.
The fix was one line: `return res.json(data)`. But the MRE I built in 10 minutes isolated the problem in a way that a 15,000-line project never could.
const express = require('express');
const app = express();
// Simulating async DB call with delay
function getUser(id) {
return new Promise(resolve => {
setTimeout(() => {
resolve({ id, name: 'Alice' });
}, 50);
});
}
// Bug: missing 'return' before res.json
app.get('/user/:id', async (req, res) => {
const user = await getUser(req.params.id);
// The bug: no return, execution continues
res.json(user);
// This line runs after response is sent, modifying something else
console.log('After response');
});
app.listen(3000, () => console.log('Server on 3000'));Rule 3: Include Exact Steps and Expected vs. Actual
Your MRE should come with a README that says: 'Run `node index.js`, then send a GET to http://localhost:3000/user/1. Expected: JSON with user. Actual: empty response on every 3rd request.' Without that, the person reading your bug has to guess what you were doing. Be explicit.
I also recommend including the output of `node --version` and the relevant dependency versions. In Node.js, a bug that appears in v18 might not appear in v20. Save everyone time by stating your environment upfront.
Never assume the reader will infer the bug from the code. State the expected behavior and the actual behavior in plain English. If the bug is intermittent, describe the pattern (e.g., 'every 3rd request', 'after the server has been running for 10 minutes').
Rule 4: Make It Runnable with One Command
The best MREs are a single file or a small directory that you can clone and run with `npm install && npm start` (or `pip install -r requirements.txt && python main.py`). No manual database setup, no environment variables, no external services. If you can't provide that, provide a Dockerfile that sets it all up.
I use GitHub Gists for single-file MREs or public repos for multi-file ones. For live coding environments, StackBlitz and CodeSandbox are excellent because they run in the browser — no setup at all.
What Not to Do
- arrow_rightDon't paste 500 lines of code and say 'the bug is somewhere in here'.
- arrow_rightDon't include your entire package.json with 40 dependencies when only 2 are needed.
- arrow_rightDon't omit the error message — include the full stack trace, not just the first line.
- arrow_rightDon't say 'it works on my machine' without providing the exact OS, runtime version, and any relevant config.
- arrow_rightDon't file a bug report without attempting an MRE first. Even if you fail, the attempt will clarify your thinking.
The Side Effect: You'll Debug Your Own Bugs Faster
I can't count how many times I started writing an MRE only to find the bug before I finished. The process of isolating the minimal case forces you to question every assumption. You might discover that the bug only happens when a certain HTTP header is present, or only when the system clock is in a specific timezone.
Writing an MRE is the closest thing we have to a scientific method for debugging. It's a hypothesis: 'I think this code causes this behavior.' Then you test it in a controlled environment. If you can't reproduce it, your hypothesis is wrong. That's progress.
of bugs are found during the creation of an MRE, before the report is even filed (anecdotal, but experience-backed)
Putting It All Together
Next time you encounter a bug, before you paste a stack trace into Slack or open an issue, spend 15 minutes building an MRE. Start from scratch. Strip away everything except the core behavior. Include a README with reproduction steps. Make it runnable with a single command.
You might solve it yourself. Or you'll give the person on the other end the best possible chance to help you. Either way, you've leveled up your debugging skills. And that's the art of the minimal reproducible example.
Frequently asked questions
What's the difference between a minimal reproducible example and a minimal, complete, verifiable example (MCVE)?
They're essentially the same concept. MCVE is the term Stack Overflow uses, while MRE is more common in open-source issue trackers. Both emphasize minimal, complete, and verifiable — meaning you can copy-paste it, run it, and see the bug immediately without any extra setup.
How do I make an MRE when the bug depends on a specific database or external service?
Replace the dependency with a mock or a simpler equivalent. For example, if your bug involves PostgreSQL, swap it for SQLite with the same schema. If the bug is in the network layer, use a local HTTP server. The goal is to eliminate the external dependency while preserving the trigger.
My MRE is still long. How short does it need to be?
There's no hard limit, but aim for the smallest amount of code that still reproduces the bug. If you can cut a file in half and the bug still appears, cut it. Often you'll end up with 10-50 lines of code. If it's more than a couple hundred lines, you haven't gone far enough.
Should I include dependency files like package.json or requirements.txt?
Yes, but only if they're essential. A common mistake is including a full project with dozens of dependencies. Instead, create a fresh directory with only the dependencies needed to trigger the bug. For Node.js, that's often just the one library causing the issue.