Moniruzzaman Saikat

Posted Sep 29, 2026 · 6 min read · 1 view

Report

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>;
  • Pick keeps only the listed properties
  • Omit removes the listed properties
  • Partial makes every property optional, which is great for update payloads
  • Record builds 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 any whenever an error appears, instead of fixing the type
  • Using type assertions (as) to silence errors rather than narrowing properly
  • Turning off strict because 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.

1 reaction
1

Written by

Moniruzzaman Saikat

Software Engineer at TheSoftking Ltd

Software engineer who loves building useful things, solving hard problems, and turning ideas into scalable products. Always learning, shipping, and experimenting with new tech.

Founding MemberNew MemberProlific Writer

14 articles · Dhaka Bangladesh · Joined Sep 2026

Discussion (1)

Sign in to join the discussion.

🕓️🕓️🕓️🕓️🕓️🕓️