Comments #

Comments are one of the most important communication tools in code — not communication with the compiler, but with humans: teammates, reviewers, and your future self six months from now. TypeScript inherits all comment syntax from JavaScript, but adds one far more powerful layer: JSDoc. With JSDoc, comments are no longer passive text the compiler ignores — they become a source of type information and documentation readable by IDEs, documentation tools, and even the TypeScript compiler itself. This article covers all types of comments in TypeScript, when to use them, and just as importantly — when not to use them.

Single-Line Comments (//) #

Single-line comments are the simplest form. Everything after // until the end of the line is ignored by the compiler. They suit short notes explaining why something is done, not what is done — because good code already explains the “what” by itself.

let batasPercobaan: number = 3;        // Limit per security policy
let intervalKadaluarsa: number = 3600; // In seconds — 1 hour

// Start counting from 1, not 0, so error messages feel more natural to users
for (let percobaan = 1; percobaan <= batasPercobaan; percobaan++) {
  console.log(`Percobaan ${percobaan} dari ${batasPercobaan}`);
}

What’s Worth Commenting #

Single-line comments are most useful for explaining decisions that aren’t visible from the code itself:

// ANTI-PATTERN: Comments that only repeat the code — no added information
const usia: number = 17; // Set usia to 17
if (usia >= 18) {        // Check whether usia is greater than or equal to 18
  console.log("Dewasa"); // Print "Dewasa"
}

// CORRECT: Comments that explain the reason (the "why"), not the action (the "what")
const usia: number = 17;
if (usia >= 18) {
  // Per Law No. 35 of 2014 — the legal adult age is 18
  console.log("Akses penuh diizinkan");
}

Multi-Line Comments (/* ... */) #

Multi-line comments enclose a block of text between /* and */. Use them when an explanation doesn’t fit in a single line, or when you want to temporarily disable a large block of code.

/*
  This algorithm uses a sliding window approach to compute the
  moving average. Time complexity O(n), far more efficient than
  the naive O(n²) approach that recomputes from scratch each iteration.

  Reference: https://en.wikipedia.org/wiki/Moving_average
*/
function rataRataBergerak(data: number[], ukuranWindow: number): number[] {
  const hasil: number[] = [];
  let jumlah = 0;

  for (let i = 0; i < data.length; i++) {
    jumlah += data[i];

    if (i >= ukuranWindow) {
      jumlah -= data[i - ukuranWindow];
    }

    if (i >= ukuranWindow - 1) {
      hasil.push(jumlah / ukuranWindow);
    }
  }

  return hasil;
}

Temporarily Disabling Code #

Multi-line comments are also often used during debugging to temporarily disable a block of code:

function prosesData(input: string[]): string[] {
  const hasil = input.map((item) => item.trim().toLowerCase());

  /*
  // Additional normalization — temporarily disabled for bug #4521 investigation
  const hasilNormalisasi = hasil.map((item) => {
    return item.replace(/[^a-z0-9]/g, "-");
  });
  return hasilNormalisasi;
  */

  return hasil;
}
Long-term commented-out code is a code smell — it creates confusion about whether the code is still relevant or safe to delete. If you disable code for debugging, remove the comment before committing. To keep an old version of code, use version control (Git), not comments.

JSDoc Comments (/** ... */) #

JSDoc is the most powerful form of comment in TypeScript. It isn’t just text — it’s structured metadata read by IDEs (for tooltips and autocomplete), tools like TypeDoc (to generate HTML documentation), and in some cases the TypeScript compiler itself. JSDoc starts with /** (two asterisks) and ends with */.

/**
 * Calculates the total price after discount and tax are applied.
 *
 * @param harga - Base product price in Rupiah
 * @param diskon - Discount percentage (0–100). Default: 0
 * @param pajakPersen - Tax percentage (0–100). Default: 11 (VAT)
 * @returns Final total price after discount and tax
 *
 * @example
 * ```typescript
 * hitungTotal(100000, 10, 11); // 99900 (10% discount, 11% VAT)
 * hitungTotal(50000);          // 55500 (no discount, 11% VAT)
 * ```
 */
function hitungTotal(
  harga: number,
  diskon: number = 0,
  pajakPersen: number = 11
): number {
  const setelahDiskon = harga * (1 - diskon / 100);
  const setelahPajak = setelahDiskon * (1 + pajakPersen / 100);
  return Math.round(setelahPajak);
}

When you hover over the function name hitungTotal in an IDE like VS Code, the entire JSDoc is shown as a tooltip — including parameter descriptions, return value, and usage examples.

JSDoc for Interfaces and Types #

JSDoc is very useful for documenting interfaces and types so every property has an explanation that shows up in the IDE:

/**
 * Product data representation in the e-commerce system.
 * Used for API responses and frontend state.
 */
interface Produk {
  /** Unique product ID — UUID v4 format */
  id: string;

  /** Product name shown to users */
  nama: string;

  /** Price in Rupiah (IDR), not cents */
  harga: number;

  /** Available stock count. 0 means out of stock, -1 means not tracked */
  stok: number;

  /** Product category. Use values from the enum KategoriProduk */
  kategori: string;

  /** Creation timestamp in ISO 8601 format */
  dibuatPada: string;

  /** Main product image URL. Optional — can be null if no image yet */
  gambarUrl?: string | null;
}

JSDoc for Classes #

/**
 * Service for managing user authentication.
 *
 * @remarks
 * All tokens are stored in memory, not localStorage, to prevent
 * XSS attacks. Tokens are auto-refreshed 5 minutes before expiry.
 *
 * @example
 * ```typescript
 * const auth = new AuthService("https://api.example.com");
 * const token = await auth.masuk("[email protected]", "password");
 * ```
 */
class AuthService {
  private baseUrl: string;
  private tokenAktif: string | null = null;

  /**
   * Creates a new AuthService instance.
   * @param baseUrl - Authentication API base URL (without trailing slash)
   */
  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  /**
   * Authenticates the user and stores the token.
   *
   * @param email - User's email address
   * @param password - User's password (sent via HTTPS, never logged)
   * @returns JWT token on success
   * @throws {Error} If credentials are wrong or the server is unreachable
   */
  async masuk(email: string, password: string): Promise<string> {
    const response = await fetch(`${this.baseUrl}/auth/login`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, password }),
    });

    if (!response.ok) {
      throw new Error(`Autentikasi gagal: ${response.status}`);
    }

    const data = await response.json();
    this.tokenAktif = data.token;
    return data.token;
  }
}

Commonly Used JSDoc Tags #

TagPurposeExample
@paramDocuments a parameter@param nama - User name
@returnsDocuments the return value@returns JWT token
@throwsExceptions that may be thrown@throws {Error} If invalid
@exampleUsage example with code@example + code block
@deprecatedMarks a deprecated API@deprecated Use fungsiV2()
@seeReference to other documentation@see https://docs.example.com
@remarksLonger additional notes@remarks Special attention...
@sinceFirst version where available@since v2.1.0
@internalInternal use onlyNot shown in public docs

TypeScript Comment Directives #

TypeScript recognizes several special comments that aren’t just documentation — they give direct instructions to the compiler. These are the most dangerous category of comments when misused.

@ts-ignore — Ignore an Error on One Line #

@ts-ignore instructs the compiler to ignore a TypeScript error on the next line:

// @ts-ignore
const nilai: number = "ini bukan angka"; // An error normally appears, but is ignored

@ts-expect-error — Expect an Error (Safer) #

@ts-expect-error is a safer version of @ts-ignore. The crucial difference: if the next line does not produce an error, TypeScript actually reports an error on the directive itself. This forces you to remove the directive once the code is fixed.

// ANTI-PATTERN: Silent @ts-ignore — no warning if the error is already fixed
// @ts-ignore
fungsiYangSudahDiperbaiki(argumenBenar); // If already correct, @ts-ignore sits "idle" without warning

// CORRECT: @ts-expect-error — the compiler complains if the error doesn't happen
// @ts-expect-error: Deliberately testing with the wrong type to validate runtime error handling
fungsiDenganValidasi("bukan-angka");

Comparison table of the two:

Aspect@ts-ignore@ts-expect-error
Ignores errors✓ Yes✓ Yes
Errors if there’s no error✗ No✓ Yes
Best forEmergency legacy codeTests and intentional suppression
RiskHigh — can hide bugsLow — self-cleaning

@ts-nocheck — Disable an Entire File #

@ts-nocheck on the first line of a file disables all type checking for that entire file:

// @ts-nocheck
// This file is a migration from JavaScript — type checking is temporarily disabled
// TODO: Remove @ts-nocheck after the migration is complete (target: Sprint 24)

const data = ambilData();
prosesData(data.apapun.tanpa.tipe);
@ts-nocheck is a signal that a big problem was postponed, not solved. If you find @ts-nocheck in a codebase without a clear TODO comment, ask why it’s there. Don’t add new @ts-nocheck to files that already have type coverage — that’s a step backward.

@ts-check — Enable Type Checking in .js Files #

The opposite of @ts-nocheck, this directive enables TypeScript type checking in plain JavaScript files — without changing the file extension:

// @ts-check
// A plain JavaScript file, but it gets type checking from TypeScript

/**
 * @param {string} nama
 * @param {number} usia
 * @returns {string}
 */
function buatBio(nama, usia) {
  return `${nama}, ${usia} tahun`;
}

buatBio("Budi", 25); // ✓
// buatBio(123, "dua puluh lima"); // ✗ TypeScript will report an error

This is very useful during a gradual migration from JavaScript to TypeScript — you can add type checking per file without converting everything at once.


Comment Directive Usage Flow #

Here’s a guide for when to use each directive:

flowchart TD
    A{Is there a TypeScript error\\nyou want to ignore?} -- No --> B[Don't add any directive]
    A -- Yes --> C{Is this error\\ndeliberately for testing?}
    C -- Yes --> D[Use @ts-expect-error\\nwith a reason explained]
    C -- No --> E{Is the error from a third-party\\nlibrary that can't be changed?}
    E -- Yes --> F[Use @ts-ignore\\nwith an explanatory comment]
    E -- No --> G{Is the whole file a\\nmigration from JavaScript?}
    G -- Yes --> H[Use @ts-nocheck\\nwith a TODO deadline]
    G -- No --> I[Fix the code — don't\\nsuppress errors that can be fixed]

    style B fill:#51cf66,color:#fff
    style D fill:#339af0,color:#fff
    style F fill:#fcc419,color:#000
    style H fill:#ff922b,color:#fff
    style I fill:#51cf66,color:#fff

Best Practices for Writing Comments #

Comment the “Why”, Not the “What” #

Well-written code already explains what it does. Good comments explain why that decision was made — information that can’t be seen from the code itself.

// ANTI-PATTERN: A comment explaining the "what" — redundant with the code
// Iterate the array and add each item to the result
const hasil = items.map((item) => item.harga * item.kuantitas);

// CORRECT: A comment explaining the "why" — adds context
// Price is multiplied by quantity here (not in the model) so the calculation
// can be overridden per transaction without changing product data
const hasil = items.map((item) => item.harga * item.kuantitas);

TODO and FIXME Comments #

Use standard prefixes so they’re easy to find with grep or IDE features:

// TODO: Add email format validation before Sprint 15
// FIXME: Race condition here if two requests arrive simultaneously — see issue #892
// HACK: Workaround for a bug in axios v1.2.3, remove after upgrade
// NOTE: This function is called in 47 places — changing the signature needs a migration
// PERF: Could be optimized with memoization if it becomes a bottleneck in profiling

Don’t Duplicate Information Already in Types #

TypeScript already has an expressive type system. Comments that duplicate type information only add noise and can go stale after refactoring:

// ANTI-PATTERN: Comments that duplicate types — will go stale after refactoring
/**
 * @param pengguna - User object with nama (string) and usia (number) properties
 * @returns string
 */
function formatPengguna(pengguna: { nama: string; usia: number }): string {
  return `${pengguna.nama} (${pengguna.usia})`;
}

// CORRECT: Comments focus on business context, not repeating types
/**
 * Formats user data for display in the profile header.
 * Format: "Nama (Usia)" — per Figma v3 design of the profile page.
 */
function formatPengguna(pengguna: { nama: string; usia: number }): string {
  return `${pengguna.nama} (${pengguna.usia})`;
}

Generating Documentation from JSDoc #

One of JSDoc’s biggest benefits is its ability to generate HTML documentation automatically. The most popular tool for TypeScript is TypeDoc:

# Install TypeDoc
npm install --save-dev typedoc

# Generate documentation
npx typedoc src/index.ts --out docs/

# Or configure via typedoc.json
{
  "entryPoints": ["src/index.ts"],
  "out": "docs",
  "excludePrivate": true,
  "excludeInternal": true,
  "theme": "default"
}

With this configuration, every JSDoc comment you write shows up as a tidy documentation page — complete with navigation, search, and links between types.


Summary #

  • Single-line comments (//) — use for short notes explaining why, not what; good code already speaks for itself about what it does.
  • Multi-line comments (/* */) — use for algorithm explanations, architectural decisions, or temporarily disabling code; but remove commented-out code before committing to the repository.
  • JSDoc (/** */) — required for all public functions, interfaces, and classes; JSDoc isn’t just a comment — it’s documentation read by IDEs, TypeDoc, and your library’s users.
  • @ts-expect-error is safer than @ts-ignore — use @ts-expect-error for intentional suppression because the compiler will complain when the error disappears, forcing you to clean up directives that are no longer needed.
  • @ts-nocheck is a danger sign — if you add it, include a TODO comment with a deadline for when it will be removed.
  • @ts-check in .js files — an easy way to add gradual type checking without changing the file extension, ideal for migrating from JavaScript.
  • Avoid duplicating types in comments — don’t explain types in comments if the information already exists in TypeScript annotations; keep comments focused on business context and decisions not visible from the code.
  • Standard prefixes for action items — use TODO, FIXME, HACK, NOTE, PERF consistently so they’re easy to find across the codebase.

← Previous: Core Syntax   Next: Variables →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact