Technical Writing: Documenting Code and APIs
Technical writing is the practice of creating documentation that explains how software works, how to use it, and how to integrate with it. For developers, this means writing clear, accurate, and maintainable documentation for code, libraries, and APIs. Good documentation is often the difference between a widely adopted project and one that is ignored, because no one can figure out how to use it.
What Is Technical Writing for Code and APIs?
At its core, technical writing for software involves translating complex technical concepts into language that a specific audience can understand. When documenting code, this includes inline comments, function descriptions, and architectural overviews. When documenting APIs, it includes endpoint references, authentication guides, request and response examples, and error handling instructions. The goal is always the same: reduce the friction a reader experiences when trying to accomplish a task.
Unlike marketing copy or prose, technical documentation must be precise. Ambiguity leads to bugs, wasted time, and frustrated users. A well-documented function tells the reader exactly what inputs it expects, what it returns, what errors it can throw, and what side effects it may have.
Why Documentation Matters
- Onboarding speed: New team members can become productive faster when code is documented.
- API adoption: External developers are far more likely to integrate with an API that has clear examples and references.
- Reduced support burden: Good documentation answers common questions before they are asked, reducing tickets and emails.
- Future-proofing: You will forget why you wrote a piece of code six months from now. Documentation helps your future self.
- Code quality: The act of writing documentation often reveals design flaws, because if you cannot explain a function simply, it may be doing too much.
How to Document Code
The most effective code documentation happens at multiple levels. At the file level, a brief header explains the purpose of the module. At the function level, a docstring describes inputs, outputs, and behavior. Inline comments explain why something is done, not what is being done, since the code itself already shows the what.
Most languages support standardized documentation comment formats that can be parsed by tools to generate reference docs automatically. In Python, this is docstrings processed by tools like Sphinx. In JavaScript, JSDoc is the standard. In Java, Javadoc is built into the language.
Here is an example of a well-documented Python function using a Google-style docstring:
def calculate_discount(price: float, discount_percent: float, min_price: float = 0.0) -> float:
"""Apply a percentage discount to a price, enforcing a minimum floor.
This function computes the discounted price by subtracting the discount
amount from the original price. If the result falls below the specified
minimum price, the minimum price is returned instead.
Args:
price: The original price before discount. Must be non-negative.
discount_percent: The discount as a percentage between 0 and 100.
min_price: The lowest allowable price. Defaults to 0.0.
Returns:
The final price after applying the discount, guaranteed to be
greater than or equal to min_price.
Raises:
ValueError: If price is negative or discount_percent is outside
the range 0 to 100.
Example:
>>> calculate_discount(100.0, 20.0)
80.0
>>> calculate_discount(100.0, 90.0, min_price=50.0)
50.0
"""
if price < 0:
raise ValueError("price must be non-negative")
if not 0 <= discount_percent <= 100:
raise ValueError("discount_percent must be between 0 and 100")
discounted = price - (price * discount_percent / 100)
return max(discounted, min_price)
Notice how the docstring covers every aspect a caller would need: the purpose, the arguments, the return value, possible exceptions, and a concrete usage example. A developer reading this never has to look at the implementation to understand how to use the function.
For JavaScript, the equivalent using JSDoc looks like this:
/**
* Formats a date as a human-readable string.
*
* @param {Date|string} date - The date to format. Accepts a Date object
* or an ISO 8601 string.
* @param {string} [locale="en-US"] - The BCP 47 locale tag for formatting.
* @returns {string} The formatted date string, e.g. "January 5, 2024".
* @throws {TypeError} If the input cannot be parsed as a valid date.
*
* @example
* formatDate("2024-01-05", "en-US");
* // returns "January 5, 2024"
*/
function formatDate(date, locale = "en-US") {
const parsed = new Date(date);
if (isNaN(parsed.getTime())) {
throw new TypeError("Invalid date input");
}
return parsed.toLocaleDateString(locale, {
year: "numeric",
month: "long",
day: "numeric",
});
}
How to Document APIs
API documentation goes beyond individual functions. It must explain the system as a whole: how to authenticate, what resources are available, how to make requests, and what responses to expect. The best API docs combine a narrative getting-started guide with a structured reference for each endpoint.
A strong API reference entry for a single endpoint typically includes the HTTP method, the path, a description, required and optional parameters, authentication requirements, example requests, example responses, and possible error codes. The OpenAPI Specification (formerly Swagger) is the industry standard for describing REST APIs in a machine-readable format, and tools like Redoc and Swagger UI can render it into interactive documentation.
Here is a minimal OpenAPI 3.0 document describing a single endpoint:
openapi: 3.0.3
info:
title: Tasks API
version: 1.0.0
description: A simple API for managing tasks.
paths:
/tasks/{taskId}:
get:
summary: Retrieve a single task
description: Returns the task matching the provided ID.
operationId: getTaskById
parameters:
- name: taskId
in: path
required: true
schema:
type: string
description: The unique identifier of the task.
responses:
"200":
description: The task was found and returned.
content:
application/json:
schema:
$ref: "#/components/schemas/Task"
"404":
description: No task exists with the given ID.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
components:
schemas:
Task:
type: object
required: [id, title, completed]
properties:
id:
type: string
example: "task_123"
title:
type: string
example: "Write API documentation"
completed:
type: boolean
example: false
Error:
type: object
required: [code, message]
properties:
code:
type: string
example: "not_found"
message:
type: string
example: "Task not found."
Even if you do not use OpenAPI, your written endpoint documentation should follow a similar structure. Here is how the same endpoint might look in plain Markdown:
## Get a Task
`GET /tasks/{taskId}`
Retrieves a single task by its unique identifier.
### Path Parameters
| Name | Type | Required | Description |
|---------|--------|----------|------------------------------------|
| taskId | string | Yes | The unique ID of the task to fetch.|
### Example Request
curl -X GET https://api.example.com/v1/tasks/task_123 \
-H "Authorization: Bearer YOUR_TOKEN"
### Example Response (200 OK)
{
"id": "task_123",
"title": "Write API documentation",
"completed": false
}
### Errors
| Status | Code | Meaning |
|--------|------------|------------------------------------------|
| 404 | not_found | No task exists with the given ID. |
| 401 | unauthorized | The authentication token is missing or invalid. |
Best Practices
- Write for the reader, not the author. Assume the reader knows nothing about your internal architecture. Explain what they need to know to accomplish their goal.
- Keep examples copy-pasteable. Code examples should work as written. Avoid placeholder values without clearly marking them, and test your examples against the current version of your code.
- Document the why, not just the what. Code shows what happens; comments and docs should explain the reasoning behind decisions.
- Keep docs next to the code. Storing documentation in the same repository as the code makes it easier to keep them in sync. Many teams enforce documentation updates as part of code review.
- Use consistent formatting. Pick a docstring style or API description format and stick with it across the entire project. Consistency makes documentation easier to scan and trust.
- Include error cases. Documenting only the happy path misleads users. Always describe what can go wrong and how to handle it.
- Treat documentation as code. Version it, review it, and test it. Tools like doctests in Python or documentation linters can catch broken examples automatically.
- Avoid outdated docs. Schedule regular reviews and use automated checks where possible. Stale documentation can be worse than no documentation because it actively misleads.
Conclusion
Technical writing is a core engineering skill, not an afterthought. By documenting code with clear docstrings and building API references that cover parameters, examples, and errors, you make your software accessible to other developers and to your future self. The investment pays off in faster onboarding, fewer support questions, broader adoption, and more maintainable codebases. Treat documentation with the same care you give to your production code, and your projects will be measurably easier to use and maintain.