๐Ÿ’ฌ Comments

Python Comments โ€” The Complete Guide

Everything from the basic # symbol to docstring formats, linter directives, and the judgment calls that separate genuinely useful comments from clutter that actively hurts a codebase.

๐Ÿ“…

Last Updated

March 2026

โฑ๏ธ

Read Time

40 min

๐ŸŽฏ

Level

Beginner to Intermediate

What Is a Comment in Python?

A comment is a piece of text inside your source code that the Python interpreter completely ignores when the program runs. It exists purely for humans โ€” the person reading the code later, which is very often you, six months from now, having forgotten exactly why a particular line does what it does. Comments don't affect execution speed, don't produce output, and don't change program behaviour in any way. Their only job is to make code easier to understand, maintain, and trust.

This sounds almost too simple to deserve an entire page, and mechanically it is simple โ€” Python's comment syntax can be summarised in a single sentence. But the actual skill of commenting well is surprisingly deep. Ask ten experienced developers what makes a good comment, and you'll get ten slightly different answers shaped by hard-won experience: comments that saved them during an outage at 2 a.m., and comments that actively misled them during one. This guide covers both halves of that picture โ€” the mechanics of Python's comment and docstring syntax, and the judgment behind when a comment genuinely earns its place in the code.

It's also worth setting expectations early: comments are not a substitute for clear code. A confusingly written function surrounded by paragraphs of explanation is still a confusingly written function โ€” the comments just make that fact slightly less painful to discover. The best-commented codebases tend to also be the most clearly written ones, because the same instinct that makes someone write a helpful comment also makes them choose a clearer variable name or break a tangled function into smaller pieces in the first place.

Why Comments Exist โ€” The Problem They Actually Solve

Source code answers the question what does this do extremely well โ€” arguably better than any English explanation could, since the code is the literal, executable truth. What code structurally cannot answer, no matter how cleanly it's written, is why a particular approach was chosen over the alternatives, what constraint forced a workaround, or what a future maintainer needs to know before changing something that looks simple but isn't.

Consider a line that looks entirely arbitrary in isolation: timeout = 4.5. The code tells you the timeout is four and a half seconds. It cannot tell you that this specific number came from a support ticket after the payment gateway's 95th-percentile response time was measured at 4.2 seconds under load, and 4.5 was chosen with a small buffer. Without a comment carrying that context, a future developer โ€” quite reasonably โ€” might 'clean up' the number to a rounder 5.0 or 4.0, silently reintroducing a bug that was fixed months earlier for a reason nobody documented. This is the actual problem comments solve: they preserve context that the code's structure has no way of representing on its own.

๐Ÿ Pythoncontext_preserving_comment.py
# Payment gateway p95 latency measured at 4.2s under load (see ticket PAY-1042).
# Timeout set slightly above that to avoid false failures during traffic spikes.
timeout = 4.5

Single-Line Comments โ€” The Hash Symbol in Detail

Python uses the hash symbol # to mark a single-line comment. Everything from the # to the end of that physical line is ignored by the interpreter โ€” it can sit on its own line, or follow actual code on the same line. There is exactly one comment character in the entire language, which is a large part of why Python's comment story is so easy to learn compared to languages offering several overlapping comment mechanisms.

๐Ÿ Pythonsingle_line_comments.py
# This is a full-line comment explaining the next block
tax_rate = 0.18

price = 500
total = price * (1 + tax_rate)  # inline comment: adds GST to the base price

print(total)

Both styles shown above are valid: a standalone (block) comment on its own line, usually explaining the block of code that follows, and an inline (trailing) comment that follows actual code on the same line. PEP 8 gives specific guidance for spacing around both. A block comment should start with a # followed by a single space, and should be indented to the same level as the code it describes. An inline comment should be separated from the code by at least two spaces, and the # itself should be followed by a single space before the comment text begins.

๐Ÿ Pythonpep8_comment_spacing.py
# Correct: block comment, one space after #, matches code indentation
if is_valid:
    process(order)  # correct: two spaces before #, one space after

#Incorrect: no space after the hash symbol
if is_valid:
    process(order) #incorrect: only one space before #, none after

These spacing rules might feel pedantic in isolation, but they matter more than they seem once a codebase is shared across a team. Consistent spacing is exactly the kind of detail that automated formatters and linters check for, precisely because human reviewers shouldn't have to spend attention on it during a code review โ€” it should simply be uniform everywhere, freeing reviewers to focus on what the comment actually says rather than how it's spaced.

It's also worth knowing exactly where the # loses its special meaning: inside a string literal. A hash symbol that appears within quotes is just a regular character with no comment behaviour at all, since the interpreter is inside a string context at that point and isn't scanning for comment markers.

๐Ÿ Pythonhash_inside_string.py
message = "Use #hashtags to categorise your post"  # this trailing part IS a comment
print(message)
# Output: Use #hashtags to categorise your post
# The # inside the quotes is just a character in the string, not a comment marker

Multi-Line Comments โ€” There's No Dedicated Symbol

Unlike languages such as Java, C++, or CSS, which offer a dedicated /* ... */ block comment syntax that can span any number of lines with a single opening and closing marker, Python has no true multi-line comment symbol baked into its grammar. If you want to comment out several consecutive lines, the officially supported approach is simply to repeat the # symbol on every line that needs to be commented.

๐Ÿ Pythonrepeated_hash_comments.py
# Step 1: read the input file
# Step 2: parse each line into a dictionary
# Step 3: filter out any incomplete records
# Step 4: write the cleaned data to a new file

data = load_file("records.csv")

In practice, many developers instead reach for a triple-quoted string โ€” """ ... """ or ''' ... ''' โ€” spanning multiple lines, to achieve a similar visual effect with a single opening and closing marker, much like /* */ in other languages. Technically, this is not a comment at all; it's a string literal that Python parses, evaluates, and then discards, since it isn't assigned to a variable, passed to a function, or used anywhere else. Because it's discarded harmlessly as an unused expression statement, it behaves like a comment in practice, which is why it's so widely used for this purpose despite not being the 'official' comment mechanism.

๐Ÿ Pythontriple_quote_as_comment.py
"""
This section handles user authentication.
It checks the submitted credentials against the
database and returns a session token on success.
"""

def authenticate(username, password):
    pass

One caveat worth knowing: a triple-quoted string used this way is not entirely free of cost โ€” unlike a genuine # comment, which is stripped during the parsing/tokenising stage and never becomes part of the executable bytecode at all, a standalone string expression still gets compiled into a bytecode instruction that creates the string object and then immediately discards it. In virtually every real-world program, this overhead is completely unmeasurable, so it's not a practical concern โ€” but it explains why the Python community generally treats this technique as a documentation convention borrowed for comment-like purposes, rather than a strict, interpreter-recognised substitute for a real comment.

For quickly commenting out a block of code temporarily โ€” say, while debugging โ€” most code editors offer a keyboard shortcut (commonly Ctrl+/ or Cmd+/) that prefixes every selected line with # automatically and removes it again on a second press. This remains the more idiomatic approach for that specific task, since it's unambiguous, easily reversible, and doesn't risk accidentally leaving a stray triple-quoted string sitting in the middle of a function where it could be mistaken for an actual docstring.

ApproachTruly a Comment?Reversible Easily?Best Use Case
Repeated # on each lineYes โ€” stripped during parsingYes, via editor comment toggleExplaining or disabling a specific block of code
Triple-quoted string (not first statement)No โ€” a discarded expressionManual โ€” must remove the quotesLonger free-form notes, quick prototyping notes
Triple-quoted string as first statementNo โ€” becomes a docstring, kept at runtimeN/A โ€” intentional documentation, not meant to be removedFormal documentation of a module, class, or function

Special Comment Lines Python Actually Reads

There's an interesting exception to the rule that comments are purely for humans: a small number of specific comment-shaped lines are actually inspected by the Python interpreter or by surrounding tooling, even though they're still written using the ordinary # syntax. The two most common are the shebang line and the encoding declaration.

๐Ÿ Pythonshebang_and_encoding.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

print("Runs correctly on both Linux and macOS when executed directly")

The shebang line at the very top of a file, starting with #!, tells a Unix-like operating system which interpreter to use when the script is executed directly from the command line (for example, running ./script.py instead of python3 script.py). Python itself ignores this line as an ordinary comment when the file is run through the interpreter explicitly โ€” it's the operating system's shell that reads and acts on it before Python ever sees the file.

The encoding declaration, written as # -*- coding: utf-8 -*-, historically told older versions of Python which character encoding to use when reading the source file โ€” necessary before Python 3 made UTF-8 the default. Python 3 already assumes UTF-8 by default, so this line is largely a legacy convention today, though some tools and older codebases still include it for clarity or backward compatibility with Python 2. In both cases, these lines look like ordinary comments and are ignored by Python's own execution logic, but their specific position (the first or second line of the file) and exact format give them meaning to other tools reading the file.

Docstrings โ€” When a Triple-Quoted String Becomes Documentation

There's one very specific situation where a triple-quoted string stops being a discarded throwaway and becomes something Python actually keeps around at runtime: when it appears as the very first statement inside a module, function, class, or method. In that position, it's called a docstring (short for 'documentation string'), and it's stored on the object itself, accessible through the special __doc__ attribute.

๐Ÿ Pythondocstring_examples.py
def calculate_bmi(weight_kg, height_m):
    """
    Calculate Body Mass Index (BMI).

    Args:
        weight_kg (float): weight in kilograms
        height_m (float): height in metres

    Returns:
        float: the calculated BMI value
    """
    return weight_kg / (height_m ** 2)

print(calculate_bmi.__doc__)
help(calculate_bmi)

This is exactly what the built-in help() function displays when you inspect an object, and it's what documentation-generation tools like Sphinx, MkDocs, and pydoc use to automatically build reference documentation directly from your source code, without you writing a separate document by hand that inevitably drifts out of sync with the code over time. Because docstrings carry this extra runtime and tooling significance, they're held to a noticeably higher standard than ordinary comments โ€” a good docstring reliably describes what a function does, what parameters it expects (including their types), what it returns, and, where relevant, what exceptions it might raise.

FeatureRegular Comment (#)Docstring (""" """)
Kept at runtime?No โ€” stripped entirely during parsingYes โ€” stored in __doc__
Accessible via help()?NoYes
Valid positionAnywhere in the codeMust be the first statement in a module/function/class/method
Typical useExplaining a specific line or short blockDocumenting the purpose, parameters, and return value of a whole unit
Used by documentation tools?NoYes (e.g. Sphinx, pydoc, MkDocs)
Quoting convention (PEP 257)Not applicableTriple double quotes preferred

Docstrings at Every Level โ€” Module, Class, and Function

Docstrings aren't limited to individual functions โ€” they can, and generally should, appear at every organisational level of a Python codebase: the module itself, each class, and each method or function within it. Each level serves a slightly different purpose in the overall documentation picture.

๐Ÿ Pythonlayered_docstrings.py
"""
orders.py

Handles order creation, validation, and price calculation
for the checkout module.
"""

class Order:
    """Represents a single customer order and its line items."""

    def __init__(self, customer_id):
        """Initialise a new, empty order for the given customer."""
        self.customer_id = customer_id
        self.items = []

    def add_item(self, product, quantity):
        """
        Add a product to the order.

        Args:
            product (str): the product name or SKU
            quantity (int): number of units, must be positive
        """
        self.items.append((product, quantity))
  • โ–ถ

    Module-level docstring โ€” placed at the very top of the .py file, before any imports. It describes the overall purpose of the file and is what appears first when someone runs help(module_name).

  • โ–ถ

    Class-level docstring โ€” placed immediately after the class definition line. It describes what the class represents conceptually, independent of any single method.

  • โ–ถ

    Method/function-level docstring โ€” placed immediately after each def line. It describes that specific behaviour: what it does, what it takes in, and what it returns.

This layered approach mirrors how a reader naturally explores unfamiliar code: they start by skimming the module docstring to understand the file's overall purpose, then look at class docstrings to understand the major concepts involved, and finally drill into individual method docstrings only once they need to understand a specific piece of behaviour in detail.

Docstring Formats โ€” Google Style, NumPy Style, and reStructuredText

PEP 257 defines the basic conventions for docstrings โ€” how they should be quoted and structured at a high level โ€” but it deliberately doesn't mandate one single format for describing parameters and return values in detail. Over time, the Python community converged on a small number of widely recognised formats, and most documentation-generation tools can parse any of them. Knowing the differences helps you read documentation from different projects and choose a consistent style for your own.

๐Ÿ Pythongoogle_style_docstring.py
def convert_currency(amount, rate):
    """Convert an amount from one currency to another.

    Args:
        amount (float): the amount in the source currency.
        rate (float): the conversion rate to apply.

    Returns:
        float: the converted amount in the target currency.

    Raises:
        ValueError: if amount or rate is negative.
    """
    if amount < 0 or rate < 0:
        raise ValueError("amount and rate must be non-negative")
    return amount * rate
๐Ÿ Pythonnumpy_style_docstring.py
def convert_currency(amount, rate):
    """
    Convert an amount from one currency to another.

    Parameters
    ----------
    amount : float
        The amount in the source currency.
    rate : float
        The conversion rate to apply.

    Returns
    -------
    float
        The converted amount in the target currency.
    """
    return amount * rate
FormatStyleCommon InReadability as Plain Text
Google StyleArgs:/Returns:/Raises: headers, minimal indentationGeneral Python projects, Google's own style guideVery readable even without rendering tools
NumPy StyleParameters/Returns headers with underlined sectionsScientific computing (NumPy, SciPy, pandas)More verbose, but very clear for complex signatures
reStructuredText (reST):param name: / :returns: field markersSphinx-based documentation, older Python projectsLess readable unrendered; excellent once built into HTML docs

None of these formats is objectively 'correct' โ€” the right choice generally comes down to whatever convention a project or team has already adopted. What matters far more than which format you pick is picking one and applying it consistently across a codebase, since documentation tools rely on that consistency to parse and render docstrings correctly, and human readers benefit from not having to mentally switch between formats file to file.

Why Comment at All? What Good Comments Actually Do

It's easy to think of comments as optional decoration, something you add if you have time left over at the end of writing a function. In reality, comments serve a handful of specific, practical purposes that code by itself cannot cover, no matter how cleanly it's written or how well the variables are named.

๐Ÿง 
Explaining 'Why', Not 'What'

Code already shows what happens. A good comment explains the reasoning the code alone can't express โ€” a business rule, a constraint, or a non-obvious tradeoff behind a decision.

โš ๏ธ
Flagging Known Limitations

A comment can honestly note that a piece of code is a temporary workaround, or that it doesn't yet handle a particular edge case, saving the next reader from wrongly assuming it's fully robust.

๐Ÿ”—
Linking to Context

Referencing a ticket number, a specification, or a discussion thread lets a future reader trace back to the original reasoning without that history living entirely in someone's memory or a chat log nobody can search.

๐Ÿšง
Marking TODOs Honestly

A clearly labelled # TODO: comment signals unfinished work honestly, rather than leaving a silent gap in functionality that looks complete but isn't.

๐Ÿ“–
Orienting a Reader in a Large File

A short comment introducing a large block โ€” 'the section below handles retry logic' โ€” helps someone skim a long file without reading every line in painstaking detail.

๐Ÿงช
Documenting Assumptions

Noting an assumption the code relies on, such as an input always being pre-validated upstream, prevents a future change from silently breaking that assumption elsewhere.

๐Ÿ”ฌ
Explaining Non-Obvious Algorithms

When code implements a specific known algorithm or formula, a short comment naming it (and ideally linking a source) saves a reader from reverse-engineering the math from scratch.

๐Ÿ›ก๏ธ
Warning About Fragile Code

A comment can flag that a piece of code is order-dependent or interacts unexpectedly with another part of the system, protecting against a well-intentioned but breaking refactor later.

Comments That Actively Hurt โ€” Common Mistakes

Not every comment is an improvement. Poorly written comments can add noise, go stale, or actively mislead a reader โ€” and a wrong comment is often worse than no comment at all, because a reader instinctively trusts what a comment says without necessarily re-verifying it against the actual code beneath it.

  • โ–ถ

    Restating the obvious โ€” a comment like # increment counter by one directly above counter += 1 adds nothing the code doesn't already say clearly by itself. It also adds a second thing that has to be kept in sync if the line ever changes.

  • โ–ถ

    Leaving stale comments after code changes โ€” a comment describing old behaviour that nobody updated when the underlying logic changed is actively misleading, and arguably worse than having no comment, since it actively points a reader in the wrong direction with false confidence.

  • โ–ถ

    Commenting out large blocks of dead code indefinitely โ€” code that's commented out 'just in case' tends to accumulate for years, cluttering the file and making it harder to tell what's actually in use. Version control systems like Git already preserve old code permanently and searchably, so deleting it outright is almost always the better choice.

  • โ–ถ

    Over-explaining trivial code โ€” a wall of comments above three lines of straightforward code slows a reader down rather than helping them, and often suggests the code itself might need to be simplified instead of documented around.

  • โ–ถ

    Using comments to apologise instead of fixing the problem โ€” a comment reading # I know this is ugly, sorry is a strong signal that the code should probably be rewritten, not documented around and left as-is.

  • โ–ถ

    Writing comments that contradict the code โ€” perhaps the single most dangerous case: a comment claiming a function 'always returns a positive number' sitting above code that can clearly return a negative value under certain inputs. A reader trusting the comment over the code can introduce serious bugs downstream.

  • โ–ถ

    Commenting in a way that duplicates version history โ€” a comment block listing every past change ('// changed by Amit on 3 March, changed again by Priya on 10 March...') duplicates exactly what a commit history already tracks reliably, and inevitably falls out of date.

Self-Documenting Code โ€” Reducing the Need for Comments in the First Place

A subtle but important idea in professional Python development is that the best comment is often the one you don't need to write, because the code communicates its intent clearly enough on its own. This is usually called writing self-documenting code, and it's less about avoiding comments entirely and more about not reaching for a comment as a first response to unclear code.

๐Ÿ Pythonbefore_after_self_documenting.py
# Before: a cryptic line rescued only by a comment
d = (t - 32) * 5 / 9  # convert Fahrenheit to Celsius

# After: the code itself explains what's happening,
# with no comment required at all
def fahrenheit_to_celsius(temperature_f):
    return (temperature_f - 32) * 5 / 9

celsius = fahrenheit_to_celsius(98.6)

Descriptive variable and function names, breaking a long expression into smaller named steps, and extracting a confusing block into a well-named helper function all reduce the burden that would otherwise fall entirely on comments to explain. This doesn't mean comments become unnecessary โ€” the why behind a decision still can't be expressed through naming alone โ€” but it does mean fewer comments are needed purely to explain what the code is doing, freeing the comments that remain to focus on the information that actually can't be expressed any other way.

TODO, FIXME, and Other Comment Conventions

Many teams adopt a small set of conventional tags inside comments to flag specific situations consistently across a codebase. These aren't enforced by Python itself โ€” they're just a widely recognised convention โ€” but most editors and IDEs recognise them automatically and can list every occurrence across an entire project, which makes them genuinely useful in team settings rather than purely decorative.

๐Ÿ Pythoncomment_tags.py
# TODO: add input validation before this reaches production
def process_order(order):
    pass

# FIXME: this breaks when quantity is zero โ€” division by zero below
def average_price(total, quantity):
    return total / quantity

# NOTE: this function assumes dates are already in UTC
def schedule_reminder(date):
    pass

# HACK: bypassing the cache here to work around a stale-data bug (see PERF-88)
def get_latest_price(product_id):
    pass

# XXX: this whole module needs a proper rewrite once the new API ships
def legacy_import(data):
    pass
  • โ–ถ

    TODO โ€” marks planned but not-yet-implemented work; the most universally recognised of these tags

  • โ–ถ

    FIXME โ€” flags a known bug or broken behaviour that needs correcting, distinct from a TODO because something is actively wrong, not just incomplete

  • โ–ถ

    NOTE โ€” highlights an important assumption or piece of context worth knowing, without implying any action is required

  • โ–ถ

    HACK โ€” admits a workaround that solves the immediate problem but isn't the ideal long-term solution, often paired with a reference to why it exists

  • โ–ถ

    XXX โ€” an older, more general-purpose convention signalling 'this needs serious attention,' often used for larger structural concerns rather than a single line

A practical detail worth adopting: pairing these tags with a ticket number, an issue link, or at minimum a short reason, as shown in the HACK example above. A bare # TODO with no further context, left in a codebase for years, tends to become invisible โ€” everyone scrolls past it without ever addressing it, precisely because it carries no urgency or traceable owner. A TODO linked to a tracked issue is far more likely to actually get resolved.

Commenting Differently for Different Audiences

How much and what kind of commenting is appropriate changes noticeably depending on who the code is written for. Treating every codebase with an identical commenting style, regardless of audience, tends to either under-explain production code or over-explain internal tooling that only the original author will ever touch.

ContextTypical Comment DensityWhat to Prioritise
Internal team codebaseModerateExplain non-obvious decisions and business rules; trust teammates to read straightforward code unaided
Public library / open-source packageHigh, with formal docstringsEvery public function needs a clear docstring covering parameters, return values, and exceptions โ€” external users can't ask you directly
Tutorial or educational codeHigh, explanatoryComment even fairly obvious steps, since the reader is actively learning the language, not just the specific logic
Quick personal scriptsLowComment only genuinely non-obvious or reused logic; heavy commenting on a throwaway script has little payoff
Production code others will maintain long-termModerate to high on critical pathsFocus comments on edge cases, failure modes, and anything a change could silently break

This is also exactly why the code examples throughout educational resources โ€” including this page โ€” tend to be more heavily commented than what you'd typically see in a mature production codebase. The extra explanation is appropriate for a reader encountering the concept for the first time, even though the same density of comments would feel excessive to an experienced developer reading equivalent code in a professional setting.

Comments in the Age of Version Control

Before tools like Git became universal, commenting out old code and leaving it in place, along with a changelog comment block at the top of a file, was a genuinely practical way to preserve history โ€” there was often no other reliable record of what a file used to look like. That justification has almost entirely disappeared today. Git (or any modern version control system) already preserves every previous version of every line, searchably, with the author, timestamp, and commit message attached automatically.

๐Ÿ Pythonavoid_this_pattern.py
def calculate_shipping(weight, distance):
    # Old implementation, kept just in case:
    # base_rate = weight * 0.5
    # return base_rate + (distance * 0.1)
    #
    # Updated 2024-11-02 by Aman: switched to tiered pricing
    if weight <= 5:
        return distance * 0.08
    return distance * 0.12

In a project tracked with Git, the old implementation above is already fully preserved in the commit history โ€” running git log -p or git blame on that file reveals exactly what changed, when, by whom, and typically why, if commit messages are written well. Leaving the old code commented out in the file itself adds visual clutter without adding any information that isn't already captured more reliably elsewhere. The stronger practice is to delete replaced code outright and rely on version control for history, reserving in-code comments for context that's relevant to understanding the current state of the code, not its past.

Comments That Talk to Tools โ€” Linter and Type-Checker Directives

Beyond human-readable notes, Python's tooling ecosystem has adopted a specific pattern where a specially formatted comment is read by an external tool โ€” a linter or type checker โ€” to change how that tool behaves on a particular line, even though Python itself still treats the line as an ordinary, ignored comment.

๐Ÿ Pythonlinter_directives.py
import os  # noqa: F401  (imported for side effects, flake8 would otherwise flag it as unused)

result = some_legacy_function()  # type: ignore  (third-party stub is outdated)

value = eval(user_expression)  # nosec  (input is sanitised upstream, safe in this context)
  • โ–ถ

    # noqa โ€” recognised by flake8 and similar linters, tells the tool to skip flagging a warning on that specific line, optionally naming the exact rule code to suppress

  • โ–ถ

    # type: ignore โ€” recognised by mypy and other type checkers, suppresses a type-checking error on that line, typically used when a third-party library's type stubs are incomplete or incorrect

  • โ–ถ

    # nosec โ€” recognised by security linters like Bandit, suppresses a security warning on a line the developer has manually reviewed and judged safe

  • โ–ถ

    # pragma: no cover โ€” recognised by coverage.py, excludes a specific line or block from code coverage reporting, typically for defensive code that's genuinely unreachable in normal testing

These directive comments occupy an interesting middle ground: syntactically, they're indistinguishable from an ordinary comment as far as the Python interpreter is concerned, but semantically, a separate tool parses their exact text and acts on it. Because of this, they should always be used deliberately and sparingly, ideally with a short explanation of why the suppression is justified โ€” a bare # noqa with no reasoning, scattered across a file, is a strong signal that warnings are being silenced out of convenience rather than genuine, reviewed necessity.

Comments vs Type Hints โ€” Overlapping but Different Tools

Before type hints became widespread, it was common to see comments used purely to document a parameter's expected type โ€” something like # price: float above a function. Modern Python offers a more precise, tool-checked alternative: type hints, written directly in the function signature using a colon and, for return values, an arrow.

๐Ÿ Pythoncomment_vs_type_hint.py
# Older style: type documented only via comment, unverified by any tool
def apply_discount(price, percent):
    # price: float, percent: float, returns: float
    return price * (1 - percent / 100)

# Modern style: type hints, checkable by tools like mypy
def apply_discount(price: float, percent: float) -> float:
    """Apply a percentage discount to a price."""
    return price * (1 - percent / 100)

The key distinction is that a comment describing a type is just text โ€” nothing verifies it matches reality, and it can silently drift out of date the same way any other comment can. A type hint, by contrast, can be checked automatically by tools like mypy, catching a genuine mismatch (someone passing a string where a float was expected) before the code ever runs. This doesn't make comments obsolete for describing behaviour โ€” a docstring explaining what a discount percentage represents still adds value a bare type hint can't โ€” but for the narrow job of documenting a parameter's type specifically, type hints are now the more reliable, tool-verified choice.

Python Comment Syntax vs Other Languages

Comment syntax is one of the more visible surface differences between languages, and it's a useful reference if you move between Python and other ecosystems regularly.

LanguageSingle-Line CommentMulti-Line / Block Comment
Python# commentNo dedicated syntax โ€” repeated # or a triple-quoted string
Java// comment/* comment */
JavaScript / TypeScript// comment/* comment */
C / C++// comment/* comment */
HTMLNot applicable line-by-line<!-- comment -->
CSSNot applicable line-by-line/* comment */
SQL-- comment/* comment */ (in most dialects)
Bash / Shell# commentNo dedicated syntax โ€” repeated # or a here-doc trick
Ruby# comment=begin ... =end

Python's decision to skip a dedicated block-comment symbol is consistent with its broader minimalism โ€” rather than adding another piece of syntax for a relatively narrow use case, it lets the single # symbol handle every comment scenario, with triple-quoted strings available as an unofficial but widely accepted alternative for longer blocks. Notably, Python shares its single-line comment character with Bash and shell scripting, which is a small but genuine convenience for developers who frequently move between writing Python scripts and the shell scripts that often surround them in a typical deployment pipeline.

A Fully Worked Example โ€” Before and After

To bring every idea on this page together, here's a single function shown twice: first with no comments and unclear naming, and then rewritten with self-documenting code plus comments that add genuine, non-obvious value rather than restating what's already visible.

๐Ÿ Pythonbefore_no_comments.py
def calc(o):
    t = 0
    for i in o:
        t += i[1] * i[2]
    if t > 5000:
        t = t * 0.9
    return t
๐Ÿ Pythonafter_well_documented.py
def calculate_order_total(line_items):
    """
    Calculate the total cost of an order, applying a bulk discount
    when the pre-discount total exceeds the threshold.

    Args:
        line_items (list[tuple]): each item is
            (product_name, unit_price, quantity).

    Returns:
        float: the final total after any applicable discount.
    """
    # Bulk discount threshold and rate are set by the Q1 2026 pricing policy (PRICE-204)
    BULK_DISCOUNT_THRESHOLD = 5000
    BULK_DISCOUNT_RATE = 0.10

    subtotal = 0
    for product_name, unit_price, quantity in line_items:
        subtotal += unit_price * quantity

    if subtotal > BULK_DISCOUNT_THRESHOLD:
        subtotal *= (1 - BULK_DISCOUNT_RATE)

    return subtotal

Notice what changed beyond just adding comments: the function and parameter names became self-explanatory, the magic numbers 5000 and 0.9 became named constants, and the docstring covers the contract of the function (inputs, outputs) while the single inline comment covers the one thing that genuinely can't be inferred from the code alone โ€” why those specific numbers were chosen. This is the pattern worth internalising: let clear code and a solid docstring handle the mechanics, and reserve inline comments for the handful of details that only exist outside the code itself.

Practice This Code โ€” Live Editor

Syntax Pitfalls Involving Comments and Docstrings

While comments are conceptually simple, a small number of syntax mistakes around them show up often enough to be worth flagging explicitly.

MistakeWhat HappensFix
Unterminated triple-quoted string used as a commentSyntaxError: EOF in multi-line string โ€” Python keeps scanning for the closing """ past the intended commentAlways make sure the closing triple quote is present and matches the opening one exactly
Placing a docstring after other code in a functionIt's treated as an ordinary, discarded string expression, not stored in __doc__A docstring must be the very first statement โ€” move it directly under the def line
Forgetting the space after #Not a syntax error, but flagged by most linters and inconsistent with PEP 8Always write # comment, never #comment
Using # inside an f-string's expression part without escaping contextCan cause confusion since # inside {} is not a comment marker there eitherKeep comments outside of string literals and f-string expressions entirely
Nesting triple quotes of the same type inside a docstringSyntaxError, since Python can't tell where the outer string actually endsUse the other quote style (' vs ") for any triple-quoted text that appears inside the docstring's body

Interview Questions on Python Comments

Practice Questions โ€” Test Your Understanding

1. Write a single-line comment above a variable assignment explaining why a discount rate of 0.1 was chosen.

Easy

2. What will print(my_function.__doc__) output if my_function has no docstring defined?

Easy

3. Is the following code block a comment, in the strict technical sense? '"""This explains the section below"""' placed on its own, not assigned to anything.

Medium

4. A teammate leaves the comment '# TODO: handle negative numbers' above a function that already handles negative numbers correctly. What does this suggest, and what should be done?

Medium

5. Rewrite this function so the code is self-documenting enough that the comment becomes unnecessary: '# convert kilometers to miles\nx = k * 0.621371'

Medium

6. What is wrong with this docstring placement, and how would you fix it? 'def greet(name):\n message = f"Hello {name}"\n """Return a greeting for the given name."""\n return message'

Hard

7. Explain why a comment like '# noqa' with no further explanation is considered weaker practice than '# noqa: F401 โ€” imported for its side effects on module load'.

Hard

8. Why does Python raise a SyntaxError for an unterminated triple-quoted comment, but not for a missing closing single # on a line comment?

Hard

Conclusion โ€” Comment With Intent

Python's comment syntax itself is about as simple as it gets โ€” a single # symbol covers the vast majority of everyday cases, with triple-quoted strings and docstrings filling in the gaps for longer explanations and formal, tool-readable documentation. The real skill isn't the syntax; it's judgment โ€” knowing when a comment genuinely helps a future reader, and when the code would be better served by simply being clearer on its own.

As a rule of thumb worth internalising early: comment the reasoning, not the mechanics โ€” explain why a decision was made, not what a line of code already obviously does. Reach for a clearer name or a smaller, better-named function before reaching for an explanatory comment, and reserve the comments you do write for the handful of things that genuinely can't live inside the code itself: business context, known limitations, and links back to the discussions or tickets that shaped a decision.

Get into the habit of writing a docstring for every function you define from the very start, even a one-line description for something simple. It costs almost nothing in the moment and pays for itself repeatedly later โ€” both for others reading your code, and for you, returning to it after enough time has passed that your own reasoning has quietly slipped out of memory.

Frequently Asked Questions (FAQ)