TypeScript Comments

Comments are pieces of text in your source code that the compiler ignores when it figures out what your program does – they exist to communicate with humans. TypeScript inherits JavaScript’s two ordinary comment forms unchanged, but it also adds a small set of specially formatted comments that the compiler and your editor actually read and act on. Understanding which comments are "just text" and which ones are instructions to the compiler is the key to using them well.

Overview: How Comments Work in TypeScript

The first stage of compilation is scanning (tokenizing): the compiler reads your source file character by character and, the moment it recognizes // or /* */, it skips everything inside without turning it into part of the abstract syntax tree (AST). Because comments never become AST nodes, they cannot affect whether your code type-checks or what it does at runtime – you can write anything inside one, including disabled code, and the checker will not look at it.

By default, ordinary comments you write are preserved in the emitted JavaScript: the tsc compiler option removeComments defaults to false. This is a separate concern from type erasure. Type annotations like : number are always stripped from emitted JavaScript because plain JavaScript has no syntax for them, but comments are just text, so they survive compilation unless you explicitly turn on removeComments.

What makes TypeScript’s comment story deeper than plain JavaScript’s is that a handful of specially formatted comments are not just documentation – they are read and acted on by the compiler itself:

  • JSDoc comments (a block comment starting with an extra *, placed directly above a declaration) are parsed by the language service and surfaced in editor tooltips and autocomplete. In plain .js files compiled with checkJs enabled, JSDoc tags such as @param {string} name serve as the actual type annotations, since .js files cannot use TypeScript’s : type syntax.
  • Triple-slash directives are read by the compiler while it builds the list of files and libraries in a program, before type checking even starts. They are only meaningful at the very top of a file.
  • @ts-ignore and @ts-expect-error are read by the type checker itself: before reporting an error on a line, it checks whether the line directly above contains one of these comments, and changes whether (and how) it reports that error.

Syntax

Here is the full family of comment forms you will encounter in TypeScript, from the ordinary to the compiler-aware:

// 1. Single-line comment
let count = 0; // increments once per click

/*
 * 2. Multi-line (block) comment
 * Can span several lines.
 */
count = count + 1;

/**
 * 3. JSDoc comment - a special block comment
 * that documentation tools and editors understand.
 * @param n - the value to double
 */
function double(n: number): number {
  return n * 2;
}

// 4. Compiler directive comment
// @ts-expect-error - deliberately passing a string to prove the checker still works
double("nope");

console.log(count, double(5));

Output:

1 10
Form Where it’s used Who reads it
// text Anywhere, runs to end of line Humans only
/* text */ Anywhere, can span multiple lines (cannot nest) Humans only
/** text */ Directly above a declaration (function, class, property, etc.) Editors, doc tools, and the compiler in checkJs mode
/// <reference lib="dom" /> The very first lines of a file only The compiler, while resolving which files/libs to include
// @ts-ignore Directly above the line with an error The type checker – suppresses the next line’s error unconditionally
// @ts-expect-error Directly above the line with an error The type checker – suppresses the error, and itself errors if there turns out to be no error

Examples

Example 1: A plain comment explaining intent

// Calculate the price after tax is applied
function applyTax(price: number, taxRate: number): number {
  // taxRate is expressed as a decimal, e.g. 0.07 for 7%
  return price + price * taxRate;
}

const finalPrice = applyTax(50, 0.07);
console.log(finalPrice);

Output:

53.5

Both comments here are purely for humans: the line comment above the function explains what it computes, and the block-style line comment inside explains the unit of taxRate. Neither comment changes the type of anything or how the function behaves – delete them and the code runs identically.

Example 2: JSDoc for editor tooling

/**
 * Formats a person's full name.
 * @param first - The person's first name
 * @param last - The person's last name
 * @returns The formatted full name
 */
function formatName(first: string, last: string): string {
  return `${last}, ${first}`;
}

console.log(formatName("Ada", "Lovelace"));

Output:

Lovelace, Ada

Because this comment uses the /** opener directly above the function, editors like VS Code parse it and show the description plus each @param line in a tooltip whenever you hover over formatName or call it elsewhere – even though the parameter types are already given by : string, the JSDoc adds human-readable meaning on top.

Example 3: Suppressing an expected type error

function double(n: number): number {
  return n * 2;
}

// @ts-expect-error - "hello" is not assignable to parameter of type 'number'
const result = double("hello");

console.log(double(21));

Output:

42

The call double("hello") would normally fail to compile, since double expects a number. The // @ts-expect-error comment directly above tells the checker "I know the next line has an error, don’t report it." The result variable is never logged, so only double(21) prints anything at runtime.

How It Works Step by Step (Under the Hood)

It helps to think of comment handling as happening across several distinct compiler phases:

  • Scanning: the tokenizer walks the raw text and discards ordinary // and /* */ content immediately, so the parser never sees it as code.
  • Parsing: a JSDoc comment (/** ... */) that sits directly above a declaration is specially attached to that declaration’s AST node, rather than discarded, so later tools can retrieve it.
  • Program construction: before type checking begins, the compiler scans the top of each file for triple-slash directives and uses them to decide which extra declaration files or library definitions to load into the program.
  • Type checking: for every diagnostic (error) the checker is about to emit, it first looks at the source line immediately above for a // @ts-ignore or // @ts-expect-error comment. If found, the error is swallowed – and for @ts-expect-error, the checker also remembers that it expected an error there, and raises a new error if none actually occurred.
  • Emit: the compiler generates plain JavaScript. Type annotations are deleted entirely, since JavaScript has no syntax for them. Ordinary comments, however, are copied into the output as-is unless removeComments is enabled.
  • Runtime: by the time Node or a browser executes the emitted file, none of this machinery exists anymore. Comments (if kept) are inert text, and there is no trace of types anywhere – a variable declared as let x: number is, at runtime, just let x holding whatever value was assigned.

Common Mistakes

Mistake 1: Trying to nest block comments

Block comments end at the very first */ they contain, so attempting to nest one inside another breaks in a confusing way:

/* Outer comment starts here.
   Attempting to nest: /* this breaks it */
   console.log("This line looks like real code, but was meant to be a comment!");
*/

The outer comment actually closes right after "this breaks it */". Everything after that – the console.log call and the final stray */ – is parsed as real code, and that trailing */ has no matching opener, so tsc reports a syntax error such as "Declaration or statement expected." Use line comments for anything that needs to be nested or that comments out code which might itself contain a block comment:

// Outer comment starts here.
// Note: block comments can't be nested, so nested notes use line comments instead.
console.log("This line runs as real code.");

Output:

This line runs as real code.

Mistake 2: A stale @ts-expect-error directive

@ts-expect-error is not a silent suppressor – it has a contract: the following line must actually produce an error, or the directive itself becomes an error. This is exactly what protects you from suppressions outliving the bug they were written for:

function getLength(value: string): number {
  return value.length;
}

// @ts-expect-error - value should be a string
const len = getLength("42");

Here getLength("42") is perfectly valid TypeScript – "42" is a string – so no error occurs on that line. Because @ts-expect-error found nothing to suppress, tsc reports "Unused ‘@ts-expect-error’ directive." The fix is simply to remove the now-unnecessary directive:

function getLength(value: string): number {
  return value.length;
}

const len = getLength("42");
console.log(len);

Output:

2

Best Practices

  • Prefer @ts-expect-error over @ts-ignore for known, temporary type errors – it self-destructs (raises its own error) once the underlying bug is fixed, so stale suppressions can’t quietly linger.
  • Write a JSDoc comment above exported functions and public APIs so editors show useful hover documentation, even though the types themselves are already expressed in TypeScript syntax.
  • Never use a block comment to wrap out a region of code that might itself contain /* or */ – use line comments, or a code-folding region in your editor, instead.
  • Keep triple-slash reference directives at the very top of a file, before any imports or statements – the compiler only honors them there.
  • Don’t reach for removeComments unless bundle size truly matters; most production bundlers already strip comments during minification.
  • Let the type system carry information the type checker can express directly (parameter types, return types, unions) instead of describing it only in a comment that can drift out of sync with the code.

Practice Exercises

  • Exercise 1: Write a function celsiusToFahrenheit(celsius: number): number with a JSDoc comment documenting its @param and @returns, then call it and log the result for an input of 0.
  • Exercise 2: Deliberately call a function with the wrong argument type (for example, passing a number where a string is expected) and suppress the resulting error with @ts-expect-error. Then fix the call so it’s correct, and observe what happens if you forget to delete the directive.
  • Exercise 3: Take a block comment that spans multiple lines and try adding a second /* inside it to “nest” a note. Predict where the comment will actually end before checking with the compiler.

Summary

  • TypeScript keeps JavaScript’s two ordinary comment forms, // and /* */, unchanged – the scanner discards them before parsing, so they never affect type checking or behavior.
  • Comments are preserved in emitted JavaScript by default (removeComments defaults to false); this is separate from type erasure, which always removes type annotations.
  • JSDoc comments (/** */) are read by editors and doc tools, and become real type annotations in checked plain JavaScript files.
  • Triple-slash reference directives, placed at the top of a file, are read by the compiler while assembling a program, before type checking begins.
  • @ts-ignore unconditionally suppresses the next line’s error; @ts-expect-error does the same but itself errors if no error was actually there – prefer it for safer, self-checking suppressions.