Code Comments & Docstrings — Interview Questions¶
Category: Documentation — docstrings as a generated, versioned API contract, the doc-generation toolchain, and the brief comment philosophy that frames them.
Conceptual and coding questions, graded junior → professional, plus trick and behavioral questions. (Inline-comment style is owned by Clean Code → Comments; questions here focus on docstrings, generators, and contracts.)
Table of Contents¶
- Junior Questions
- Middle Questions
- Senior Questions
- Professional Questions
- Coding Tasks
- Trick Questions
- Behavioral Questions
- Tips for Answering
Junior Questions¶
J1. What's the difference between a comment and a docstring?¶
Answer: A comment is free text for someone reading the source; it explains a tricky line or a why, and is never extracted. A docstring (doc comment) is structured documentation attached to a declaration, written for someone calling the code, and extracted by a generator into the API reference, IDE tooltips, etc. A comment is read in place; a docstring is republished.
J2. State the comment philosophy in one line.¶
Answer: "Comment why, not what" — the code already shows what it does; a comment earns its place by explaining the reason. And prefer self-documenting code (a good name) over a comment that explains a bad one.
J3. What does a docstring document?¶
Answer: The contract — the things a caller can't see from the signature: a one-line summary, each parameter (meaning, units, valid range), the return value, what errors it raises and when, an example, and caveats (thread-safety, nullability, side effects, ownership). Not the implementation.
J4. What is the single most common docstring mistake?¶
Answer: Restating the signature — amount: the amount. It adds nothing the caller couldn't read from the function header, so it's pure noise that buries the contract facts that do matter.
J5. Name a docstring convention and its generator for Python, Java, and Go.¶
Answer: Python → PEP 257 docstrings ("""...""" as first statement), generated by Sphinx. Java → Javadoc (/** */ with @param/@return/@throws), generated by the javadoc tool. Go → the godoc convention (// full sentences above the declaration, starting with the identifier name), published on pkg.go.dev.
J6. What is a doc generator?¶
Answer: A tool that reads source code plus docstrings and emits a published API reference (HTML site, IDE tooltips, man pages). Examples: Sphinx, Javadoc, JSDoc/TypeDoc, godoc, rustdoc, Doxygen, DocFX.
J7. What's special about the Python and Go docstring placement?¶
Answer: Python: the docstring must be a string literal that is the first statement in the module/function/class — a # comment doesn't count. Go: the doc comment is a // comment placed immediately above the declaration with no blank line, written as a full sentence starting with the identifier name (// Withdraw debits ...); godoc renders it verbatim.
J8. What is a doc-test?¶
Answer: An example inside a docstring that the test runner executes, so it can't silently go stale. Python's doctest and Rust's doc-tests are the canonical examples — a stale example fails CI.
J9. Should you document a trivial getter like get_name()?¶
Answer: Generally no. """Return the name.""" adds nothing the signature doesn't already say — it's noise on the reference site. Document the public/non-obvious surface; leave the trivial alone.
J10. Where does inline-comment style (when to leave one, how to phrase it) belong?¶
Answer: In Clean Code → Comments. This topic covers docstrings and doc generation; it intentionally defers comment style to that topic.
Middle Questions¶
M1. Why document the contract instead of the implementation?¶
Answer: The contract is what the caller depends on and what you must not break; the implementation is free to change. Documenting the implementation duplicates the code (so it rots on every refactor) and omits the guarantees callers actually need. "Uses quicksort" is fragile; "sorts ascending, not stable" is a contract.
M2. Name the three Python docstring styles and when you'd pick each.¶
Answer: Google (Args:/Returns:/Raises:) — most readable, default for general code. NumPy (dashed-underline sections) — scientific/long-param APIs (pandas, SciPy). reStructuredText (:param: field lists) — native Sphinx, most powerful cross-references. The key is consistency: a generator configured for one mis-renders another (Sphinx's napoleon plugin parses Google/NumPy).
M3. In TypeScript, what should @param document?¶
Answer: The meaning/semantics the type can't express — "stable opaque id, never the email, used as the cache key." Not the type ({string}) — TypeScript already enforces that, so repeating it is redundant noise.
M4. Where should documentation effort be high vs. near-zero?¶
Answer: High on the public/exported surface (strangers depend on it; it's in the published reference) and on subtle module front-pages. Near-zero on trivial getters and obvious helpers (the signature is the documentation). A missing public contract hurts every caller; noise on trivia hurts every reader of the reference forever.
M5. What does Sphinx autodoc do, and what's napoleon?¶
Answer: autodoc pulls docstrings directly from the source to build reference pages (so you don't hand-write the reference). napoleon is the extension that lets Sphinx parse Google and NumPy docstring styles, not just native reST.
M6. Why is "100% docstring coverage" a misleading goal?¶
Answer: Coverage counts docstrings, not contracts. A codebase can hit 100% with """Return x.""" on every getter while its load-bearing functions have weak contracts. The meaningful target is coverage of the public surface with real contracts (errors, units, guarantees).
M7. What's the most-omitted part of a contract?¶
Answer: Errors — what exceptions/error values the function raises and under what conditions. Callers need it to write correct error handling, and it's the field people skip most.
Senior Questions¶
S1. Why is changing a docstring sometimes a breaking change?¶
Answer: A docstring is a contract, and callers build on its documented promises. Removing or weakening a guarantee — "thread-safe", "preserves insertion order", "never returns null" — breaks every caller who relied on it, even if the code is byte-identical. Under semver it's a major change, not a "comment tweak." Corollary (Hyrum's Law): with enough callers, every documented behavior is depended upon — so only document the guarantees you're willing to maintain.
S2. Why "document the contract, not the implementation" at the system level?¶
Answer: Every implementation detail you write into a docstring becomes a behavior you've promised. "Uses a binary search internally" lets callers depend on / time against it, so you can't change the algorithm without a contract change. Document guarantees (O(log n), ordering, errors); keep mechanics out so you stay free to refactor.
S3. What makes doc-tests valuable, and what are their costs?¶
Answer: They collapse the example and the code into one artifact that can't diverge without failing the build — the cure for example rot, and they double as regression tests for the documented behavior (Rust even checks them at compile time). Costs: they run in the test suite (slow/flaky ones destabilize the build), they can't easily cover side effects/I/O without polluting the example, and illustrative-but-unrunnable snippets (no_run / +SKIP) can still rot.
S4. Where do generated API docs fit in the Diátaxis model?¶
Answer: They are the reference mode — information-oriented, complete, austere, structured for lookup. They are not tutorials (learning), how-to guides (tasks), or explanation (understanding). The senior implication: don't force a docstring to teach — cramming onboarding narrative into the reference bloats it and still fails to teach. Keep the reference austere and link to hand-written guides.
S5. Compare two doc generators on the axes that matter.¶
Answer: Key axes: (1) how much they infer vs. require — rustdoc and TypeDoc read the type system so you write less and rot less; JSDoc requires everything in comments. (2) Do they execute examples — rustdoc (always) and Sphinx (with doctest) verify examples; others render unverified prose. (3) Convention vs. configuration — godoc is pure convention (scales across an org cheaply); Sphinx/Doxygen are powerful but heavyweight. The "right" choice depends on whether you value verified examples, type-driven inference, or low coordination cost.
S6. What's the single highest-leverage doc-toolchain setting?¶
Answer: Warnings-as-errors (sphinx-build -W, javadoc -Xwerror, RUSTDOCFLAGS="-D warnings"). Without it the generator silently ships broken pages — dead cross-references, malformed tags, "module not found." It turns "broken reference" from a problem a user discovers into a build failure that blocks merge. Pair with link-checking and doc-tests in CI.
Professional Questions¶
P1. How do you review a docstring?¶
Answer: Treat a docstring change as a contract change. Check: every public symbol has a contract (summary/params-with-units/return/errors-and-when); it documents the contract not the implementation; it's truthful against this diff (a behavior change with an untouched docstring is a blocker); a weakened guarantee is flagged as a breaking change; and it's not noise. The single most-skipped field to demand: errors.
P2. What does a doc-generation CI pipeline look like?¶
Answer: On every PR: build with warnings-as-errors → execute doc-tests → link-check (internal + external) → coverage gate on the public boundary → on merge/release, publish a versioned site (v2.3 docs ≠ latest). Non-negotiables: warnings-as-errors (no silently-broken pages), doc-tests as a required check (examples can't rot), and versioned publishing (users behind HEAD see correct docs).
P3. How do you add docstrings to a large legacy codebase?¶
Answer: Don't boil the ocean. Document on touch (Boy Scout Rule) when you modify a function, with context loaded. Prioritize the public boundary and the functions people keep asking about. Verify the real contract (read code + tests) before writing it — documenting a guessed contract manufactures a lie. Baseline-and-ratchet the coverage gate ("no new public symbol without a docstring") instead of a big-bang gate. Never auto-generate docstrings from signatures — that's negative-value noise at scale.
P4. Why turn a prose example into a doc-test?¶
Answer: A prose example is unverified — it drifts the moment the API changes, and users copy-paste broken snippets (a top doc-bug source). A doc-test is executed in CI, so the next signature change breaks the build, not the user. It converts "examples that will rot" into "examples that can't."
P5. What's wrong with gating on raw docstring-coverage percentage?¶
Answer: It rewards trivial docstrings ("""Return x.""") and even script/LLM-generated signature echoes — a team can hit 100% with zero real contracts (a real failure mode). Gate the public boundary (missing_docs on pub, interrogate on the public API), and review for the contract fields (errors, units, guarantees), not the percentage.
P6. A teammate says "good code is self-documenting, so we don't need docstrings." Respond.¶
Answer: Half right. Self-documenting code handles the what — clean names mean you don't comment the obvious. But it can't carry the contract: a caller shouldn't have to read your function body to learn what exceptions you raise, what units a parameter is in, or whether you're thread-safe. You want both — clean names and a documented contract on the public surface — not one instead of the other.
Coding Tasks¶
C1. Fix this signature-restating docstring (Python).¶
Before:
After:
def transfer(src: str, dst: str, amount: Decimal) -> None:
"""Move money between two accounts, atomically.
Both accounts are locked for the duration; the debit and credit either
both commit or both roll back. `amount` is in the source account's
currency and must be positive.
Raises:
InsufficientFunds: if `src` lacks the balance (nothing is moved).
CurrencyMismatch: if `src` and `dst` hold different currencies.
"""
State the reasoning: document the facts a caller can't infer from the signature — atomicity, locking, units, sign rule, and exactly what errors fire and what happens on failure.
C2. Convert documentation of the implementation into a contract (Python).¶
Before:
After:
def dedupe(items: list[T]) -> list[T]:
"""Return items with duplicates removed, preserving first-seen order.
Elements must be hashable; the input is not mutated. O(n) expected.
"""
The "after" promises the guarantees callers rely on (order preserved, input untouched, hashable required) and frees you to change the algorithm later — the "before" pinned you to a set and lost the ordering promise.
C3. Write a doc-test that catches a bug (Python).¶
def slugify(title: str) -> str:
"""Return a URL slug for a title.
>>> slugify("Hello, World!")
'hello-world'
>>> slugify(" Trailing Spaces ")
'trailing-spaces'
"""
return "-".join(title.lower().split())
Running python -m doctest fails the first case ('hello,-world!' != 'hello-world') — the example exposes that punctuation isn't stripped. The doctest forces the docstring's promise and the code to agree.
C4. Write a Go doc comment that follows the convention.¶
Before (won't attach / reads wrong):
After:
// Withdraw debits amount from the account identified by accountID and returns
// the new balance. amount must be positive. It returns ErrAccountNotFound if
// the account does not exist, and ErrInsufficientFunds if amount exceeds the
// available balance.
func Withdraw(accountID string, amount Money) (Money, error) { ... }
The convention: full sentence, starts with the identifier Withdraw, immediately above the func with no blank line; errors documented in prose (Go returns them as values).
C5. Spot what makes this doc-test fragile and fix it (Rust).¶
Before — example does real I/O, so it can't run in CI:
/// ```
/// let cfg = mycrate::load_config("/etc/app/config.toml")?; // needs a real file
/// assert_eq!(cfg.port, 8080);
/// ```
After — keep it shown but not executed, or make it self-contained:
/// ```no_run
/// let cfg = mycrate::load_config("/etc/app/config.toml")?;
/// # Ok::<(), mycrate::Error>(())
/// ```
///
/// For a runnable example, parse from a string instead:
/// ```
/// let cfg = mycrate::Config::from_str("port = 8080").unwrap();
/// assert_eq!(cfg.port, 8080);
/// ```
State the trade-off: no_run shows the I/O usage without executing it (so it can still rot), while the from_str example is fully runnable and can't rot — prefer a runnable example for the canonical case.
Trick Questions¶
T1. "A docstring is just a comment at the top of a function." True?¶
False. A comment is free text for source readers, never extracted. A docstring is structured, follows a tool-readable convention, is extracted by a generator into the API reference and IDE tooltips, and is part of your public contract. (And in Python it must be a string literal, not a # comment, to count at all.)
T2. "More docstrings = better documentation." Agree?¶
No. Docstrings on trivial getters ("""Return the name.""") are noise that buries the contracts that matter, and 100% coverage is a vanity metric you can hit with signature-echoing junk (negative value). Document the public surface with real contracts; leave trivia undocumented.
T3. Does editing a docstring ever break callers if the code is unchanged?¶
Yes. Removing or weakening a documented guarantee (thread-safety, ordering, non-null) breaks callers who relied on the promise, even with byte-identical code. It's a breaking change under semver, governed like a signature change.
T4. "Document how the function works so maintainers understand it." Right place?¶
Mostly no. The docstring documents the contract for callers — implementation narrative both rots on refactor and over-promises mechanics you'd then have to preserve. A non-obvious why of the implementation belongs in an inline comment for maintainers (style → Clean Code → Comments), not in the docstring.
T5. "We have great names, so we don't need docstrings." Correct?¶
Half-true and dangerous. Good names handle the what; they cannot express the contract — errors raised, units, valid ranges, thread-safety, what happens on failure. A caller shouldn't read your body to learn you throw InsufficientFunds. You need clean names and a documented contract on the public surface.
T6. "Examples in docs always help." Any catch?¶
Yes — an unverified example is a future lie. When the API changes and the prose example doesn't, users copy-paste broken code and trust is destroyed. Use doc-tests (Python doctest, Rust doc-tests) so examples run in CI and a stale one fails the build instead of the user.
Behavioral Questions¶
B1. Tell me about a time documentation (or its absence) caused a bug.¶
Sample: "A cache class was documented 'thread-safe'. A refactor swapped in a plain HashMap behind an optimization and left the docstring untouched. Three teams kept calling it concurrently on the documented promise — we got intermittent corruption and a week-long investigation, because everyone 'knew' it was thread-safe; the docs said so. I restored synchronization and added a rule that concurrency claims in docstrings require a matching test. The lesson I quote: a docstring is a contract, and silently invalidating it is a breaking change that fails silently and late."
B2. How do you handle a teammate's PR with no docstrings on a new public API?¶
Sample: "I treat it as a missing contract, not a style nit. I ask for the standard fields — summary, params with units, return, and especially what it raises and when — and point to our convention so it's policy, not my opinion. For their internal helpers I explicitly say 'no docstring needed here', so the bar reads as 'document the boundary', not 'document everything'."
B3. How do you keep an API reference truthful across a large team over years?¶
Sample: "Make truth structural, not heroic. Doc generation runs in CI with warnings-as-errors and link-checking; examples are doc-tests that fail the build when they rot; the coverage gate is on the public boundary, baseline-and-ratcheted so legacy code doesn't block work. In review, a behavior change with an untouched docstring is a blocker. And we publish versioned docs so users behind HEAD see correct contracts."
B4. Describe pushing back on a documentation metric you thought was wrong.¶
Sample: "Leadership wanted '100% docstring coverage' as a quality gate. A team hit it by auto-generating signature-echoing docstrings — 100% green, zero real contracts, an enormous useless reference. I made the case to change the gate to 'public symbols need a hand-written contract' and to review for errors/units/guarantees. Coverage counts docstrings; it doesn't count whether they're true or useful."
B5. When did you decide not to document something?¶
Sample: "On a legacy module, a teammate proposed a sprint to add docstrings to every function. I argued against it: most were trivial internals, and we'd produce hundreds of 'Return x' lines of noise plus the risk of guessing contracts wrong. Instead we documented on touch — Boy Scout Rule — prioritized the public API and the functions people kept asking about, and verified the real contract before writing it. The active surface got documented through normal work; the trivia stayed blank, correctly."
Tips for Answering¶
- Lead with the comment-vs-docstring distinction (audience + extracted), and that this topic owns docstrings, deferring comment style to Clean Code → Comments.
- Say "document the contract, not the implementation" — and why: documented mechanics become promises you must keep.
- Always mention errors as the most-omitted contract field; add units and ranges.
- Frame a docstring as a versioned contract — weakening a guarantee is a breaking change (cite Hyrum's Law if pushed).
- Name doc-tests as the cure for example rot, and that rustdoc/
doctestrun them in CI. - Place generated docs in Diátaxis's reference mode — don't make a docstring teach.
- For toolchain, name warnings-as-errors + doc-tests + versioned publishing as the CI non-negotiables; for metrics, "gate the public boundary, not raw percentage."
← Professional · Documentation · Roadmap
In this topic
- interview