I’ve onboarded 200+ developers over the years. The ones who succeed fastest? They’re not necessarily the best coders - they’re the ones who can navigate bad documentation and still get productive.
But here’s the thing: it doesn’t have to be this way. After watching countless talented developers struggle with terrible docs (and writing my fair share of bad ones), I’ve learned what actually works.
Why Most Dev Docs Fail
Let’s be honest - most documentation is written by the person who built the thing, for someone who already understands the thing. It’s the classic curse of knowledge, and it kills productivity.
I see the same patterns everywhere:
The “Just Look at the Code” Problem
“The API is self-documenting” - no, it’s not. Your variable names might make sense to you, but they don’t explain why you chose that particular approach or what happens when things go wrong.
Missing the Mental Model
Here’s a real example from a codebase I inherited:
|
|
That’s it. No explanation of what setup
does, why you need it, or what start
actually starts. Compare that to:
|
|
Same commands, but now I understand what’s happening and what to expect.
The “Happy Path Only” Trap
Most docs assume everything works perfectly. But in reality, developers spend 60% of their time debugging. Yet documentation rarely addresses the “what if” scenarios.
The Three Types of Documentation That Actually Work
After years of trial and error, I’ve found that effective developer documentation needs exactly three things:
1. Getting Started (The First 30 Minutes)
This is your make-or-break moment. A developer should go from zero to “I see something working” in under 30 minutes. Not production-ready - just proof that the system works.
Here’s what I include in every getting started guide:
Before You Start
- Node.js 18+ (we use features from 18.12)
- Docker Desktop (for the local database)
- Git (obviously)
Quick Start
Clone and install dependencies
1 2 3
git clone [repo-url] cd [project-name] npm install
Start the local environment
1 2 3 4 5
# This creates a PostgreSQL container and runs migrations npm run dev:setup # Start the development server npm run dev
Verify it’s working
- Open http://localhost:3000
- You should see “Welcome to [Project Name]”
- Check http://localhost:3000/health - should return 200 OK
What You Just Built
You now have:
- API server running on port 3000
- PostgreSQL database with sample data
- Hot reload enabled for development
Next: Common Development Tasks
Notice what this does:
- Sets clear expectations (30 minutes, not production-ready)
- Explains what each command actually does
- Provides verification steps
- Connects to what comes next
2. Reference Documentation (Daily Lookups)
This is where most teams go wrong. They either skip reference docs entirely or make them so verbose that finding anything takes forever.
Good reference docs are scannable and searchable. Here’s my format:
Here’s my reference doc format:
POST /api/users
Creates a new user account.
Request:
|
|
Success Response (201):
|
|
Error Responses:
400
- Invalid email format or password too short409
- Email already exists500
- Server error
Notes:
- Password is hashed with bcrypt before storage
- Email verification sent automatically
- User starts with ‘pending’ status until verified
The key is answering the questions developers actually have:
- What format do I send?
- What do I get back?
- What can go wrong?
- Any gotchas I should know about?
3. Troubleshooting (When Things Break)
This is the documentation that saves time and frustration. I organize it by symptoms, not by system components.
The examples below are for local development, but the same principles apply to production - you just need more elaborate monitoring, structured logging, and proper alerting systems. That’s a topic for another blog post entirely.
Here’s how I organize troubleshooting docs:
“Connection refused” on localhost:3000
Symptoms:
curl localhost:3000
returns connection refused- Browser shows “This site can’t be reached”
Likely causes:
- Dev server not running - Run
npm run dev
- Port already in use - Kill the process:
lsof -ti:3000 | xargs kill
- Environment variables missing - Copy
.env.example
to.env
Still broken? Check the server logs: npm run dev:logs
Database migrations fail
Symptoms:
npm run migrate
returns error- App crashes with “relation does not exist”
Fix:
|
|
If that doesn’t work: The database container might be corrupted. Reset everything:
|
|
Notice the pattern:
- Start with symptoms (what the developer is experiencing)
- List likely causes in order of probability
- Provide specific commands to fix it
- Include an escalation path
For production issues, you’d document things like “API returning 500 errors” with specific log patterns to look for:
|
|
But the documentation approach stays the same - symptoms first, then fixes. The key difference is that production troubleshooting requires good logging infrastructure and monitoring to surface these symptoms in the first place. Documenting how to read your logs and where to find them is just as important as documenting the fixes themselves.
Writing for the Frustrated Developer
The best documentation acknowledges that developers are usually frustrated when they’re reading it. They’re stuck, they’re under pressure, and they just want to get unstuck.
Use the Second Person
Write “you” not “the developer” or “one should.” It’s more direct and feels like you’re talking to them.
Lead with the Fix
Don’t bury the solution after three paragraphs of explanation. Give them the fix first, then explain why it works.
Admit When Things Are Weird
I have a section in one of my codebases called “Yeah, This Is Weird But…” where I explain architectural decisions that don’t make obvious sense. Developers appreciate the honesty.
For example, I have a section like this:
Yeah, This Is Weird But…
Why we have two different User models
You’ll notice UserProfile
in the API and User
in the database layer. This seems redundant, but here’s why:
The API model excludes sensitive fields (password hash, internal flags) and includes computed fields (full name, avatar URL). The database model is just raw data.
Yes, it means more code. But it prevents accidentally exposing sensitive data and makes the API cleaner.
Simple Tools and Workflows
You don’t need fancy documentation platforms. Some of my best documentation is just markdown files in the repo.
Documentation-Driven Development
Before I build a feature, I write the documentation for it. Not full docs - just the getting started section. It forces me to think about the developer experience early.
Test Your Docs Like Code
I have a simple script that runs through the getting started guide in a clean Docker container. When new developers join the team, I watch them go through the setup process - if they get stuck, I update the docs immediately.
|
|
Get Feedback from Actual Users
The only way to know if your documentation works is to watch someone use it. I do this during code reviews - when someone asks a question that should be answered in the docs, I fix the docs instead of just answering the question.
The Documentation That Gets Read
Good documentation isn’t about being comprehensive - it’s about being useful at the exact moment someone needs it.
The best compliment I ever got on documentation was from a junior developer who said: “I never had to ask anyone for help setting up the project.” That’s the goal.
Your documentation should make the person reading it feel smart, not stupid. It should acknowledge their frustration and give them a clear path forward. And it should assume they’re capable but missing context, not incapable of understanding.
Most importantly, it should be written by someone who remembers what it’s like to be confused.
What’s the worst documentation you’ve ever encountered? I’m always collecting examples of what not to do. Share your horror stories - misery loves company.