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: typeafter a variable is called a type annotation.greetingcan 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: stringis 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 passedis 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 propertyProperty 'email' does not exist on type '...'— thepersonobject has noemailproperty
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 code | Meaning | Example situation |
|---|---|---|
| TS2322 | Type mismatch | const x: number = "hello" |
| TS2339 | Non-existent property | obj.nonExistent |
| TS2345 | Argument type mismatch | fn("str") (number expected) |
| TS2304 | Cannot find name | Using an undeclared variable |
| TS2531 | Object is possibly null | Accessing a value that could be null |
| TS7006 | Implicit any type | Parameter 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:
.JStab: The compiled JavaScript outputDTStab: Preview of the auto-generated type declaration file (.d.ts)Logstab: Output fromconsole.logcalls
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
| Concept | Syntax | Example |
|---|---|---|
| Variable type annotation | let x: type | let age: number = 25 |
| Function parameter type | (param: type) | (name: string) |
| Function return type | (): type | function fn(): string |
| No return value | void | function log(): void |
| Optional parameter | param? | (name?: string) |
| Type inference | Automatic | const x = 5 → number |
| Type error codes | TSxxxx | TS2322, 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.