Comments and Documentation
A comment is text in your source file that the Ruby interpreter ignores when it runs your program. Comments exist purely for humans: to explain why code does something, to document how a method should be used, and to leave notes for your future self or teammates. Ruby gives you a few different ways to write comments, and a loose but widely followed convention for turning ordinary comments into generated documentation.
Overview / How Comments Work
When Ruby parses a file, its lexer strips comments out before anything is turned into executable code. A single-line comment starts with a # character and runs to the end of that physical line — everything after the # is discarded, whether it’s a full line of its own or trailing text after real code. The # only starts a comment outside of a string; a # inside "a string like #this" is just a character, not a comment marker, because the parser is already inside a string literal at that point.
Ruby also has a multi-line form using =begin and =end. Everything between a line that starts with =begin and a line that starts with =end is ignored, no matter how many lines it spans. The catch is that =begin and =end must each be the very first thing on their line — no leading spaces, no leading code. Because of that rigidity, and because most editors and style guides (including the popular RuboCop linter, via its Style/BlockComments check) discourage it, =begin/=end is rare in real Ruby code. Idiomatic Ruby almost always stacks several single-line # comments instead.
Unlike some languages, Ruby has no special doc-comment syntax that the parser itself treats differently from a normal comment (there is no /** ... */ equivalent). Instead, documentation is a convention: you write ordinary # comments directly above a class, module, or method definition, and a separate tool reads your source file and extracts those comments to build documentation. Ruby ships with RDoc, and the community also widely uses the YARD gem, which adds structured tags like @param and @return that describe a method’s inputs and output. Whether or not you ever run one of these generators, writing a comment directly above a method definition is the idiomatic way to document it — other developers (and IDEs) expect to find documentation there.
There is also a special category called magic comments. These look exactly like ordinary # comments, but Ruby’s parser specifically recognizes them and changes how it reads the file. The most common one in modern Ruby is # frozen_string_literal: true, which tells Ruby to treat every string literal in the file as frozen (immutable) by default, catching accidental mutation and improving performance since Ruby doesn’t need to allocate a fresh string object for every literal. Magic comments must appear at the very top of the file, on the first line, or on the second line if the first line is a shebang (#!/usr/bin/env ruby) telling the operating system which interpreter to run the script with. A magic comment placed anywhere else in the file is just a regular, inert comment.
Syntax
# a single-line comment runs to the end of the line
=begin
a multi-line comment
spans as many lines as you like
=end
# frozen_string_literal: true
# ^ a "magic comment" -- must appear on the first line (or right
# after a shebang line) to actually change parser behavior
| Form | Example | Purpose |
|---|---|---|
| Single-line | # explains the next line |
Most common form; explain intent above code or trailing on a line |
| Multi-line | =begin ... =end |
Long blocks of text; rarely used, must start in column 1 |
| Documentation comment | # Returns the sum of a and b. above def |
Read by RDoc/YARD to generate API docs |
| Magic comment | # frozen_string_literal: true |
Changes how the parser/interpreter treats the file |
| Shebang | #!/usr/bin/env ruby |
Tells the OS which interpreter runs the script (Unix-like systems) |
Examples
Example 1: Basic comments
# This script calculates the area of a rectangle.
# Author: A. Student
def rectangle_area(width, height)
# Multiply width by height to get the area
width * height
end
puts rectangle_area(4, 5) # prints 20
Output:
20
The comments here explain what the file does and clarify a line of code, but they contribute nothing to what actually runs. Ruby’s lexer discards every #-prefixed comment before evaluating the file, so the program behaves exactly as if the comments weren’t there — it prints 20, the result of 4 * 5.
Example 2: Multi-line comments and a documentation comment
=begin
This is a multi-line comment.
Ruby ignores everything between =begin and =end.
It is rarely used in practice; most Rubyists prefer
stacking single-line comments with #.
=end
# Returns the factorial of +n+.
#
# @param n [Integer] a non-negative integer
# @return [Integer] the factorial of n
def factorial(n)
return 1 if n <= 1
n * factorial(n - 1)
end
puts factorial(5)
Output:
120
The =begin/=end block is skipped entirely. The comment directly above factorial is a documentation comment written in YARD style: the plain-English first line is the summary, and the @param/@return tags describe the method's contract. A tool like YARD would read this comment and generate an HTML page for factorial, but Ruby itself just treats it as text to discard. The method call itself computes 5 * 4 * 3 * 2 * 1, so 120 is printed.
Example 3: A magic comment in action
# frozen_string_literal: true
def shout(text)
text.upcase
end
puts shout("hello")
begin
name = "Ruby"
name << " rocks"
rescue FrozenError => e
puts "Error: #{e.message}"
end
Output:
HELLO
Error: can't modify frozen String: "Ruby"
Because # frozen_string_literal: true sits on the very first line, Ruby's parser treats every string literal in this file as frozen. shout("hello") still works fine because upcase (without a bang) returns a brand-new string rather than mutating the original, printing HELLO. But name << " rocks" tries to mutate the frozen string literal "Ruby" in place, which raises a FrozenError that gets caught and reported. This is exactly why the magic comment is useful: it turns accidental in-place mutation of what should be a fixed piece of text into an immediate, loud error instead of a subtle bug.
How It Works Step by Step
Ruby's lexer processes source text before the parser builds an abstract syntax tree from it. When the lexer meets an unescaped # outside of a string or symbol literal, it enters comment mode and discards every character up to (but not including) the newline. When it meets a line that is exactly =begin, it discards everything, including newlines, until it hits a matching =end line. Magic comments are a special case handled even earlier: Ruby scans the first line or two of a file specifically looking for recognized key: value patterns like frozen_string_literal: true or encoding: UTF-8, and if found, adjusts how the rest of the file is compiled before normal parsing continues. Documentation generators like RDoc and YARD run as a completely separate pass: they don't execute your code at all, they just read the source file as text, find comment blocks that sit directly above class, module, and method definitions, and turn them into structured documentation pages.
Common Mistakes
Mistake 1: A comment that lies about what the code does
# Returns the sum of a and b
def calculate(a, b)
a * b
end
puts calculate(3, 4)
Output:
12
This comment says "sum" but the code actually multiplies. Comments are never checked by the interpreter, so nothing stops them from going stale as code changes — and a misleading comment is worse than no comment at all, because a reader trusts it over the code. Always update (or delete) a comment the moment the code it describes changes.
# Returns the product of a and b
def calculate(a, b)
a * b
end
puts calculate(3, 4)
Output:
12
Mistake 2: Leaving dead code commented out instead of deleting it
def total_price(items)
# old pricing logic, don't delete just in case
# items.sum { |item| item[:price] * 1.05 }
items.sum { |item| item[:price] }
end
puts total_price([{ price: 10 }, { price: 20 }])
Output:
30
Commented-out code clutters a file, confuses readers about which version is current, and rots because nobody keeps it in sync. Your version control system (git) already keeps every old revision, so there's no need to hoard dead code as comments — delete it and trust git log or git blame to recover it if you ever need to.
def total_price(items)
items.sum { |item| item[:price] }
end
puts total_price([{ price: 10 }, { price: 20 }])
Output:
30
Best Practices
- Write comments that explain why code does something (a non-obvious constraint, a workaround, a business rule), not what it does — well-named methods and variables should already make the "what" obvious.
- Update or delete a comment the instant the code it describes changes; a stale comment actively misleads readers.
- Put a documentation comment directly above every public class, module, and method in a library, using YARD-style
@param/@returntags so both humans and doc generators can use it. - Never commit commented-out code — rely on git history instead.
- Prefer descriptive method and variable names over a comment that just restates poor naming.
- Reach for
=begin/=endonly for genuinely long blocks of prose; for everything else stack#lines, which most linters and style guides prefer. - Place magic comments like
# frozen_string_literal: trueon the very first line of the file (or right after the shebang) — anywhere else, they're silently ignored as ordinary comments.
Practice Exercises
- Write a method
circle_area(radius)with a documentation comment above it describing what it returns, using an@paramand@returntag. Call it withradius = 3and print the result. - Take a method you've written (or the
calculateexample above) and deliberately write a comment that describes it incorrectly. Then fix the comment so it accurately matches the code. This is good practice for spotting stale comments in real projects. - Add
# frozen_string_literal: trueto the top of a small script that builds a string with+=in a loop, run it, and confirm it still behaves correctly (string reassignment with+=creates a new string object each time, so it isn't affected by frozen literals the way<<is).
Summary
- A single-line comment starts with
#and runs to the end of the line; it's the idiomatic way to comment in Ruby. =begin/=endmulti-line comments must start in column 1 and are rarely used in modern Ruby style.- Ruby has no dedicated doc-comment syntax; documentation is a convention — write a
#comment directly above a definition, optionally with YARD-style@param/@returntags, and tools like RDoc or YARD can turn it into generated docs. - Magic comments such as
# frozen_string_literal: truelook like comments but are read by the parser and change how the file is compiled; they must be on the first line (or after a shebang). - A misleading or stale comment is worse than no comment — keep comments in sync with the code, delete dead commented-out code, and let good names carry the "what" so comments can focus on the "why."
