Polyglot
Languages TypeScript syntax
TypeScript § syntax

Syntax

TypeScript is a strict syntactic superset of JavaScript: every valid JavaScript program is a valid TypeScript program. The grammar adds type annotations, type declarations, generics, decorators, and several constructs not present in plain JavaScript (enum, namespace, parameter properties, as assertions). The compiler (tsc) checks types and emits JavaScript suitable for any conforming ECMAScript runtime; the type information is erased during emission. The conventional contemporary configuration uses ES module syntax, target: ES2022 or later, and strict: true for the full type-discipline surface.

This page covers the surface a working programmer encounters routinely.

A complete program

The classical hello world:

const greeting: string = "Hello, world!";
console.log(greeting);

A more substantial example:

import { readFile } from "node:fs/promises";

interface Person {
    name: string;
    age: number;
    email?: string;
}

async function loadPeople(path: string): Promise<Person[]> {
    const data = await readFile(path, "utf8");
    return JSON.parse(data) as Person[];
}

function greet(p: Person): string {
    return `Hello, ${p.name}.`;
}

const people = await loadPeople("people.json");
for (const p of people) {
    console.log(greet(p));
}

The principal features visible:

  • import { ... } from "..." — ES module import.
  • interface Person — structural type declaration.
  • name: string — type annotation on field.
  • email?: string — optional field.
  • Promise<Person[]> — generic type.
  • Template literal `Hello, ${p.name}.`.
  • as Person[] — type assertion.
  • for (const p of people) — iteration.
  • Top-level await (admitted in modules).

Compilation and execution:

tsc hello.ts && node hello.js

For development, direct execution via tsx, ts-node, Bun, or Deno is conventional:

tsx hello.ts
bun run hello.ts
deno run hello.ts

File extensions

The principal forms:

ExtensionUse
.tsTypeScript module
.tsxTypeScript with JSX (React, Solid, etc.)
.mtsES module (explicit)
.ctsCommonJS module (explicit)
.d.tsDeclaration file — types only, no runtime code
.d.mts, .d.ctsDeclaration files for the explicit module forms

The .mts/.cts extensions are conventional in projects that mix ES module and CommonJS code; the .d.ts files are the type-only declarations consumed by TypeScript.

Source character set

TypeScript source is interpreted as UTF-8. Identifiers may use Unicode letters; the conventional code uses ASCII identifiers.

Identifiers and keywords

The convention follows the JavaScript style:

  • camelCase — variables, functions, parameters, methods.
  • PascalCase — classes, interfaces, type aliases, enums, constructors.
  • SCREAMING_SNAKE_CASE — constants representing static configuration values; camelCase is conventional for ordinary const bindings.
  • A leading _ is conventional for “intentionally unused”; _unused.

The reserved keywords inherit from JavaScript. TypeScript adds contextual keywords that are reserved only in specific positions:

abstract  any       as        asserts   bigint    boolean
constructor  declare  enum    from      get       implements
infer     interface  is       keyof     let       module
namespace  never     null     number    object    of
out       override  package   private   protected public
readonly  require   satisfies set       static    string
symbol    type      undefined unique    unknown   void

The form r#name is not admitted in TypeScript (unlike Rust); reserved words cannot be identifiers.

Comments

Three comment forms:

// A line comment, terminated by the end of the line.

/* A block comment.
   Block comments do NOT nest. */

/**
 * A JSDoc comment for the next item.
 * @param name the name to greet
 * @returns a greeting string
 */
function greet(name: string): string {
    return `Hello, ${name}.`;
}

The /** ... */ JSDoc form admits structured documentation; the conventional tooling (TypeDoc, IDE integrations) consumes JSDoc and produces HTML documentation.

JSDoc may also be used for type information in .js files:

/**
 * @param {string} name
 * @returns {string}
 */
function greet(name) {
    return `Hello, ${name}.`;
}

The mechanism admits gradual TypeScript adoption in JavaScript projects.

Statements vs expressions

TypeScript inherits JavaScript’s statement-vs-expression distinction. The principal expression-oriented constructs:

const x = a + b;                                 // arithmetic
const max = a > b ? a : b;                       // ternary
const items = [1, 2, 3];                         // array literal
const obj = { x: 1, y: 2 };                      // object literal
const f = () => 42;                              // arrow function
const result = (() => {                          // IIFE
    let s = 0;
    for (let i = 0; i < 10; i++) s += i;
    return s;
})();

The principal statement forms:

let x = 5;                                       // declaration
if (cond) { /* ... */ } else { /* ... */ }       // selection
for (let i = 0; i < n; i++) { /* ... */ }        // iteration
return value;
throw new Error("...");

if, for, while, switch are statements — they do not produce values. The conventional substitutes for value-returning conditionals are the ternary operator (?:) and the IIFE ((() => { ... })()).

Variable declarations

Three forms:

const x = 5;                                     // immutable binding (preferred)
let y = 10;                                      // mutable binding
var z = 15;                                      // legacy; rarely used

The conventional discipline:

  • Use const by default.
  • Use let when reassignment is required.
  • Avoid var — its function-scoped semantics are surprising.

Type annotations are optional; the compiler infers types from initialisers:

const x = 5;                                     // inferred as number
const x: number = 5;                             // explicit
const arr = [1, 2, 3];                           // inferred as number[]
const arr: number[] = [1, 2, 3];                 // explicit

let user: { name: string; age: number };
user = { name: "Alice", age: 30 };

For inferred types that should be the literal type, the as const assertion:

const ROLES = ["admin", "user", "guest"] as const;
// type: readonly ["admin", "user", "guest"]

const config = {
    host: "localhost",
    port: 8080,
} as const;
// type: { readonly host: "localhost"; readonly port: 8080 }

The as const admits substantial type precision for configuration data and lookup tables.

Block syntax

Control-flow constructs admit either a block (preferred) or a single statement:

if (cond) {
    doSomething();
}

if (cond) doSomething();                         // OK; one-liner
if (cond)
    doSomething();                               // OK; admitted

if (cond) doFirst();
else doSecond();

The conventional discipline always uses braces for multi-statement bodies; one-liners are admitted but inconsistent style is discouraged.

Functions

The function keyword introduces a function declaration:

function add(a: number, b: number): number {
    return a + b;
}

function greet(name: string): void {
    console.log(`Hello, ${name}`);
}

Arrow functions admit a more compact form, with subtly different this binding:

const add = (a: number, b: number): number => a + b;
const greet = (name: string): void => console.log(`Hello, ${name}`);

const square = (n: number) => n * n;             // return type inferred

Arrow functions are expressions; function declarations are statements (and are hoisted to the enclosing scope).

Treated in Functions.

Type annotations

Type annotations follow the colon convention:

let n: number;
let s: string = "hello";
const arr: number[] = [1, 2, 3];
const tup: [string, number] = ["alice", 30];

function f(x: number, y: number): number {
    return x + y;
}

interface Point {
    x: number;
    y: number;
}

type Status = "active" | "inactive" | "banned";

The compiler infers types when possible; explicit annotations are conventional for:

  • Function parameters — always.
  • Function return types — for exported APIs.
  • Public class members — always.
  • Local variables — only when the inferred type is wrong.

Modules and imports

The conventional contemporary form is ES modules:

// math.ts:
export function add(a: number, b: number): number { return a + b; }
export const PI = 3.14159;
export default function multiply(a: number, b: number): number { return a * b; }

// main.ts:
import multiply, { add, PI } from "./math.js";

console.log(add(2, 3));                          // 5
console.log(PI);
console.log(multiply(2, 3));

Note the .js extension in the import path — TypeScript imports refer to the emitted JavaScript path, not the source .ts path. (Some configurations admit .ts imports; the conventional setting is to write .js.)

Treated in Scope and modules.

The tsconfig.json

The compiler is configured by tsconfig.json at the project root:

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "Bundler",
        "strict": true,
        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,
        "isolatedModules": true,
        "noUncheckedIndexedAccess": true,
        "outDir": "./dist",
        "rootDir": "./src"
    },
    "include": ["src/**/*"]
}

The conventional contemporary recommendations:

  • strict: true — enables the full type-discipline surface.
  • target: ES2022 (or later) — modern output without unnecessary down-levelling.
  • moduleResolution: "Bundler" — for projects using bundlers (Vite, esbuild, etc.).
  • noUncheckedIndexedAccess: true — array indexing returns T | undefined.

Treated in Tooling and packaging.

JSX

The .tsx extension admits JSX syntax — XML-like elements producing JavaScript:

function Greeting({ name }: { name: string }): JSX.Element {
    return <h1>Hello, {name}!</h1>;
}

const element = <Greeting name="Alice" />;

JSX is principally used with React; alternative libraries (Solid, Preact, Vue’s JSX form) consume the same syntax with different runtime behaviour.

A note on what TypeScript admits

TypeScript adds substantial type-system features over JavaScript:

  • Static type checking — at compile time only; types are erased.
  • Type annotations on variables, parameters, return values.
  • Interfaces and type aliases.
  • Generics — type parameters on functions, classes, types.
  • Enums — including const enum for compile-time constants.
  • Decorators — Stage 3 ECMAScript decorators (TS 5.0+).
  • Access modifiers on class members (public, private, protected, readonly).
  • Abstract classes and abstract members.
  • Namespaces — internal modules, conventionally replaced by ES modules.
  • Mapped types, conditional types, template literal types.
  • Type assertions and type predicates.

Treated in subsequent pages.

A note on what is not in TypeScript

  • Runtime type checking — TypeScript does not check types at runtime; types are erased.
  • Operator overloading — JavaScript-level only.
  • Multiple inheritance — single inheritance plus interfaces.
  • Pointers — JavaScript-level only.
  • Manual memory management — garbage-collected.
  • Macros — none; code generation via build tools or decorators.

The combination — superset of JavaScript, structural type system, type erasure, conventional tooling — is the substance of TypeScript’s identity. The discipline is largely about expressing the program’s intent through the type system while producing standard-compliant JavaScript.