Skip to main content
Advertisement

1.4 Hello TypeScript

The environment is set up. Now let's actually write TypeScript code, compile it, and run it. This chapter starts with writing your first .ts file, moves on to intentionally triggering type errors and learning how to read the error messages, and finishes with type inference — one of TypeScript's core characteristics.

Writing Your First .ts File

Create a file called src/hello.ts and write the following code.

// src/hello.ts

const greeting: string = "Hello, TypeScript!";
const version: number = 5;
const isReady: boolean = true;

function sayHello(name: string, language: string): string {
return `Starting ${language} in ${name}!`;
}

const message: string = sayHello("the US", "TypeScript");
console.log(greeting);
console.log(`Version: ${version}, Ready: ${isReady}`);
console.log(message);

Code breakdown

  • const greeting: string — Appending : type after a variable is called a type annotation. greeting can only hold a string value.
  • const version: number — The number type. There is no distinction between integers and floating-point numbers.
  • const isReady: boolean — The true/false type.
  • function sayHello(name: string, language: string): string — Both parameter types and the return type are declared. The final : string is the return type.

Compiling with tsc

Compiling a single file

npx tsc src/hello.ts

This produces src/hello.js in the same directory.

Examining the generated JavaScript file

// src/hello.js — all type annotations have been removed
"use strict";
const greeting = "Hello, TypeScript!";
const version = 5;
const isReady = true;

function sayHello(name, language) {
return `Starting ${language} in ${name}!`;
}

const message = sayHello("the US", "TypeScript");
console.log(greeting);
console.log(`Version: ${version}, Ready: ${isReady}`);
console.log(message);

The type annotations are completely gone. The : string on function parameters, the : string, : number, : boolean on variables, and the return type : string have all been stripped away. What remains is pure JavaScript.

Compiling the whole project (using tsconfig.json)

If rootDir and outDir are configured in tsconfig.json, compile the entire project with a single command.

npx tsc

src/hello.ts is compiled to dist/hello.js.

Running the output

node dist/hello.js
# Hello, TypeScript!
# Version: 5, Ready: true
# Starting TypeScript in the US!

Intentionally Triggering Type Errors

TypeScript's real value comes from catching incorrect code before you run it. Let's deliberately introduce type errors to practice reading error messages.

Error example 1: Passing a value of the wrong type

// src/error-test.ts

function multiply(a: number, b: number): number {
return a * b;
}

const result = multiply(10, "5");
// ^^^
// Error TS2345: Argument of type 'string' is not assignable
// to parameter of type 'number'.

How to read the error message:

  • TS2345 — The TypeScript error code. You can search for this to find a detailed explanation.
  • Argument of type 'string' — The type of the value you passed
  • is not assignable to parameter of type 'number' — The expected type
  • In plain English: "a string cannot be passed to a parameter expecting a number."

Error example 2: Accessing a property that does not exist

const person = {
name: "Alice",
age: 30,
};

console.log(person.email);
// ^^^^^
// Error TS2339: Property 'email' does not exist on type
// '{ name: string; age: number; }'.

How to read the error message:

  • TS2339 — The error code for accessing a non-existent property
  • Property 'email' does not exist on type '...' — the person object has no email property

Error example 3: Return type mismatch

function getAge(): number {
return "thirty";
// ^^^^^^^^
// Error TS2322: Type 'string' is not assignable to type 'number'.
}

How to read the error message:

  • TS2322 — Type mismatch error. One of the most commonly encountered error codes.
  • The declared return type (number) does not match the actual return value's type (string).

Commonly encountered TypeScript error codes

Error codeMeaningExample situation
TS2322Type mismatchconst x: number = "hello"
TS2339Non-existent propertyobj.nonExistent
TS2345Argument type mismatchfn("str") (number expected)
TS2304Cannot find nameUsing an undeclared variable
TS2531Object is possibly nullAccessing a value that could be null
TS7006Implicit any typeParameter with no type annotation (strict mode)

TypeScript Playground

The official TypeScript Playground lets you run TypeScript directly in a browser without any editor setup.

URL: https://www.typescriptlang.org/play

What you can do in the Playground:

  • Write TypeScript code and see the compiled result in real time
  • TypeScript on the left, compiled JavaScript on the right, updating live
  • Immediate display of type errors
  • Share via URL — generate a link to share code with teammates and discuss issues

Playground tips

There are several view options in the tabs at the top:

  • .JS tab: The compiled JavaScript output
  • DTS tab: Preview of the auto-generated type declaration file (.d.ts)
  • Logs tab: Output from console.log calls

The Playground also lets you select the TypeScript version, making it easy to compare behavior across versions.

Explicit Type Annotations vs Type Inference

TypeScript can automatically infer types from values in most cases. You do not need to annotate types everywhere.

When type inference works

// Types are inferred from initial values — no annotation needed
const name = "Alice"; // TypeScript infers string
const age = 30; // TypeScript infers number
const isActive = true; // TypeScript infers boolean

// Annotating explicitly is valid but redundant
const name2: string = "Bob"; // unnecessary annotation

// Return types can also be inferred
function add(a: number, b: number) {
return a + b; // return type inferred as number
}

When explicit annotations are required

// 1. Variable with no initial value
let userInput: string; // annotation required
userInput = "hello";

// 2. When you need a different type than what would be inferred
const numbers: number[] = []; // bare [] would be inferred as never[]

// 3. Function parameters — always annotate
function processData(data: string): void {
console.log(data);
}

// 4. Complex object types
interface Config {
host: string;
port: number;
ssl: boolean;
}

const config: Config = {
host: "localhost",
port: 3000,
ssl: false,
};

When to annotate and when to omit

The principle is simple: omit the annotation when TypeScript can infer correctly, and add it when inference is ambiguous or doesn't match your intent.

// Safe to omit (inference is clear)
const count = 0; // number
const title = "TypeScript"; // string
const items = [1, 2, 3]; // number[]

// Should annotate (inference is ambiguous or wrong)
const mixedArray: (string | number)[] = []; // empty array
let value: string | null = null; // starts as null

// Function parameters always need annotations (inference not possible)
function double(n: number): number {
return n * 2;
}

Adding Types to Functions

Functions are where TypeScript type annotations are used most often.

Basic function types

// Annotating parameter types and return type
function add(a: number, b: number): number {
return a + b;
}

// Arrow function
const multiply = (a: number, b: number): number => a * b;

// Function with no return value (void)
function logMessage(message: string): void {
console.log(message);
// no return, or return; only
}

// Function that never returns (never)
function throwError(message: string): never {
throw new Error(message);
}

Optional parameters

// Append ? to make a parameter optional
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}!`;
}
return `Hello, ${name}!`;
}

greet("Alice"); // "Hello, Alice!"
greet("Alice", "Hi there"); // "Hi there, Alice!"

Default parameter values

function createUser(name: string, role: string = "user"): object {
return { name, role };
}

createUser("Alice"); // { name: "Alice", role: "user" }
createUser("Bob", "admin"); // { name: "Bob", role: "admin" }

Practical Examples: Writing Code with Types

Example 1: Discount calculator

// src/discount-calculator.ts

interface Product {
name: string;
price: number;
category: string;
}

interface DiscountResult {
originalPrice: number;
discountAmount: number;
finalPrice: number;
discountPercent: number;
}

function calculateDiscount(product: Product, discountPercent: number): DiscountResult {
if (discountPercent < 0 || discountPercent > 100) {
throw new Error("Discount percentage must be between 0 and 100.");
}

const discountAmount = product.price * (discountPercent / 100);
const finalPrice = product.price - discountAmount;

return {
originalPrice: product.price,
discountAmount,
finalPrice,
discountPercent,
};
}

const laptop: Product = {
name: "Laptop",
price: 1200,
category: "Electronics",
};

const result = calculateDiscount(laptop, 15);
console.log(`${result.discountPercent}% discount applied:`);
console.log(`Original price: $${result.originalPrice}`);
console.log(`Discount amount: $${result.discountAmount}`);
console.log(`Final price: $${result.finalPrice}`);

// Output:
// 15% discount applied:
// Original price: $1200
// Discount amount: $180
// Final price: $1020

Example 2: User greeting function

// src/greeter.ts

type Language = "ko" | "en" | "ja";

interface User {
name: string;
preferredLanguage: Language;
isVip?: boolean;
}

function greetUser(user: User): string {
const greetings: Record<Language, string> = {
ko: "안녕하세요",
en: "Hello",
ja: "こんにちは",
};

const greeting = greetings[user.preferredLanguage];
const vipBadge = user.isVip ? " [VIP]" : "";

return `${greeting}, ${user.name}${vipBadge}!`;
}

const vipUser: User = {
name: "Alice",
preferredLanguage: "en",
isVip: true,
};

const regularUser: User = {
name: "Bob",
preferredLanguage: "en",
};

console.log(greetUser(vipUser)); // "Hello, Alice [VIP]!"
console.log(greetUser(regularUser)); // "Hello, Bob!"

// Type error example — unsupported language is blocked at compile time
const invalidUser: User = {
name: "Test",
preferredLanguage: "fr", // Error: '"fr"' is not assignable to type 'Language'
};

Pro Tips

Using the Declaration tab in the Playground

The DTS tab in the TypeScript Playground shows the type declaration file (.d.ts) that would be auto-generated from your code. It lets you see how your functions and types appear from the outside — which type information is exposed publicly. This is especially useful when building libraries.

Use type inference to reduce verbosity

A common mistake TypeScript beginners make is annotating types everywhere unnecessarily.

// Bad — redundant and verbose
const numbers: number[] = [1, 2, 3];
const doubled: number[] = numbers.map((n: number): number => n * 2);
const sum: number = doubled.reduce((acc: number, cur: number): number => acc + cur, 0);

// Good — let inference do the work, annotate only where needed
const numbers = [1, 2, 3];
const doubled = numbers.map(n => n * 2);
const sum = doubled.reduce((acc, cur) => acc + cur, 0);

Both versions produce identical type checking. The second is easier to read and maintain.

It is worth annotating return types explicitly

Parameter types are mandatory, but return types can be left to inference. However, for public-facing functions (APIs, libraries) it is worth annotating the return type explicitly. Declaring the intended return type means TypeScript will immediately report an error if the function accidentally returns something else.

// Inferred return type — return type can silently change if internal logic changes
function getUser(id: number) {
// If someone accidentally returns undefined later, the error only shows at the call site
return users.find(u => u.id === id);
}

// Explicit return type — intent is declared, errors caught inside the function
function getUser(id: number): User {
return users.find(u => u.id === id);
// Error: Type 'User | undefined' is not assignable to type 'User'
// Tells you that find() can return undefined
}

Summary

ConceptSyntaxExample
Variable type annotationlet x: typelet age: number = 25
Function parameter type(param: type)(name: string)
Function return type(): typefunction fn(): string
No return valuevoidfunction log(): void
Optional parameterparam?(name?: string)
Type inferenceAutomaticconst x = 5 → number
Type error codesTSxxxxTS2322, TS2345, TS2339

The next chapter covers how to configure VS Code, integrate ESLint, and connect Prettier to make your TypeScript development environment even more powerful.

Advertisement