Posted Sep 29, 2026 · 6 min read · 1 view
TypeScript Basics: Write Safer JavaScript in Less Time
JavaScript lets you write code quickly, but it also lets you ship bugs quickly. Calling a function with the wrong argument, reading a property that does not exist, or forgetting that a value can be null all fail at runtime, often in front of your users.
TypeScript adds a type system on top of JavaScript so many of these mistakes show up in your editor before the code ever runs. In this tutorial you will learn the core features you need to start using it in real projects.
What is TypeScript?
TypeScript is a superset of JavaScript. Any valid JavaScript is valid TypeScript, and you add type annotations gradually. The compiler checks your code, then removes the types and outputs plain JavaScript that runs anywhere.
Types exist only at compile time. They do not validate data at runtime, which is an important point we will return to later.
Step 1: Set up a project
You need Node.js 18 or newer. Create a folder and install the tools:
mkdir ts-demo && cd ts-demo
npm init -y
npm install --save-dev typescript tsx @types/node
npx tsc --init
Here typescript is the compiler, tsx lets you run TypeScript files directly during development, and tsc --init creates a tsconfig.json.
Open tsconfig.json and make sure strict mode is on:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"outDir": "dist"
}
}
"strict": true turns on the checks that give TypeScript most of its value, including null checks. Always enable it for new projects.
Create index.ts and run it with:
npx tsx index.ts
Step 2: Basic types
You add a type after a colon:
const name: string = "Rahim";
const age: number = 28;
const isActive: boolean = true;
const skills: string[] = ["PHP", "Laravel"];
You rarely need to write these out, because TypeScript infers types from values:
const city = "Dhaka"; // inferred as string
let count = 0; // inferred as number
A good habit is to let inference work for simple variables and add explicit types on function parameters and return values.
function add(a: number, b: number): number {
return a + b;
}
add(2, 3); // fine
add(2, "3"); // error: string is not assignable to number
That last line is flagged in your editor immediately.
Step 3: Objects, interfaces, and type aliases
Describe the shape of an object with an interface or a type alias:
interface User {
id: number;
name: string;
email: string;
phone?: string; // optional property
}
function greet(user: User): string {
return `Hello, ${user.name}`;
}
greet({ id: 1, name: "Nadia", email: "nadia@example.com" });
Now if you misspell a property or forget a required one, the compiler tells you. Autocomplete for user. also works everywhere the type is known.
Use interface for object shapes and type for unions and more complex combinations. For most everyday work either is fine, so pick a convention and stay consistent.
Step 4: Union types and narrowing
A value can be one of several types. This is where TypeScript starts to shine:
type Status = "pending" | "paid" | "cancelled";
function label(status: Status): string {
switch (status) {
case "pending":
return "Waiting for payment";
case "paid":
return "Payment received";
case "cancelled":
return "Order cancelled";
}
}
The Status type only accepts those three exact strings. A typo like "payed" becomes a compile error, and the compiler can warn you if you forget to handle one of the cases.
Unions also work with different types, and you narrow them with checks:
function formatId(id: string | number): string {
if (typeof id === "string") {
return id.toUpperCase(); // here id is a string
}
return id.toFixed(0); // here id is a number
}
Strict mode also forces you to deal with null and undefined:
function firstChar(text: string | null): string {
if (text === null) return "";
return text[0];
}
This single feature prevents a huge class of "cannot read properties of null" crashes.
Step 5: Generics
Generics let you write reusable code that keeps type information. Here is a function that returns the first item of any array:
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([10, 20, 30]); // number | undefined
const s = first(["a", "b"]); // string | undefined
You will see generics constantly in libraries, for example Promise<User> or Array<string>. You do not need to write complex ones on day one, but you should be comfortable reading them.
Step 6: Handy utility types
TypeScript includes built in helpers that transform existing types:
interface Product {
id: number;
title: string;
price: number;
stock: number;
}
type ProductPreview = Pick<Product, "id" | "title">;
type NewProduct = Omit<Product, "id">;
type ProductUpdate = Partial<Product>;
type PriceMap = Record<string, number>;
Pickkeeps only the listed propertiesOmitremoves the listed propertiesPartialmakes every property optional, which is great for update payloadsRecordbuilds an object type with given keys and value type
These save you from copying and pasting near identical types.
Step 7: Avoid any, prefer unknown
The any type switches off type checking, which defeats the purpose of using TypeScript. When you truly do not know a value's type, use unknown. It forces you to check before using the value:
function parse(input: unknown) {
if (typeof input === "string") {
return input.trim();
}
throw new Error("Expected a string");
}
Types do not validate runtime data
Here is the catch mentioned earlier. If you fetch JSON from an API and write const user = data as User, TypeScript trusts you, even if the server sends something different. For data crossing a boundary, such as API responses or form input, validate it at runtime with a library like Zod, then derive your types from the schema.
Common mistakes
- Using
anywhenever an error appears, instead of fixing the type - Using type assertions (
as) to silence errors rather than narrowing properly - Turning off
strictbecause the errors feel annoying - Assuming types protect you from bad runtime data
How to adopt it in an existing project
You do not have to convert everything at once. Set "allowJs": true in tsconfig.json, rename files from .js to .ts one at a time, and start with the most important modules such as data models and API helpers.
Final thoughts
TypeScript is worth the small learning cost. You get safer refactoring, better autocomplete, and documentation that lives right in your code. Start with strict mode, basic types, interfaces, and unions, and learn generics as you need them.
Are you using TypeScript in your projects yet, or still on plain JavaScript? Let us know in the comments.
Discussion (1)
🕓️🕓️🕓️🕓️🕓️🕓️