Mastering TypeScript: Best Practices You Should Start Using Today

By TechGeeta
Mastering TypeScript: Best Practices You Should Start Using Today
4 min read

TL;DR:

Don’t just “use TypeScript” — use it smartly.
Follow these practical, real-world patterns:

  • Prefer types over any.
  • Organize interfaces.
  • Leverage generics and utility types.
  • Use strict mode.
  • Keep your types close to logic.
    Code examples inside. 👇

Let’s skip the fluff. I’ve been building with TypeScript for over a decade — from side projects to production-scale SaaS systems. Here’s what actually matters if you want to go from “just using TS” to writing code others love to maintain.


🧩 1. Never Use any Unless You Really Mean “Anything”

You’ll see this a lot in beginner code:

function getUser(id: any) {
  return fetch(`/users/${id}`).then(r => r.json());
}

This “works”, but defeats the purpose of TypeScript.
Instead, define what you expect:

function getUser(id: string | number) {
  return fetch(`/users/${id}`).then(r => r.json() as Promise<User>);
}

Or even better — use generics:

async function fetchJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  return res.json() as T;
}

const user = await fetchJson<User>('/api/user/1');

💡 Pro Tip:
any → avoid.
unknown → safer fallback if you must accept “anything”.


🗂️ 2. Use type for Composition, interface for Contracts

Both seem similar but have different strengths:

type Base = { id: number }
interface User extends Base { name: string }

But for unions or mapped types:

type Status = 'active' | 'inactive';
type ApiResponse<T> = { data: T; status: Status };

🧠 Rule of thumb:

  • interface = extending, object shape
  • type = combining, unions, utilities

⚙️ 3. Turn on Strict Mode (and Leave It On)

In your tsconfig.json:

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true
  } 
} 

Strict mode feels painful early on — but it saves you from runtime bugs later.

Example:

function greet(name?: string) {
  console.log(`Hello ${name.toUpperCase()}`); // ❌ Error under strictNullChecks
}

Without strict mode, this crashes in production.
With strict mode, TypeScript protects you.


🧱 4. Model Your Data, Don’t Just Type It

If your project uses APIs, model the shape once — reuse everywhere.

Bad:

const user = { id: 1, name: "Sourav", email: "me@dev.com" };

Better:

interface User {
  id: number;
  name: string;
  email: string;
}

const user: User = { id: 1, name: "Sourav", email: "me@dev.com" };

Even better:

type UserPreview = Pick<User, 'id' | 'name'>;
type UserResponse = Omit<User, 'email'>;

🚀 You’ll thank yourself when your app grows and refactors become trivial.


🧩 5. Use Utility Types Instead of Redefining

TypeScript ships with powerful built-ins like Partial, Pick, Omit, Record, and Readonly.

Example:

type Config = {
  host: string;
  port: number;
  secure: boolean;
};

type OptionalConfig = Partial<Config>;
type PublicConfig = Pick<Config, 'host' | 'port'>;

These utilities make your types DRY and scalable.


🧠 6. Write Functions That Infer Types Naturally

Don’t annotate everything manually. Let TypeScript infer.

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

No need to write:

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

🧩 Let TS infer where it can, annotate only where it helps readability.


🧰 7. Use Enums Sparingly — Literal Unions Are Better

Instead of:

enum Role {
  ADMIN,
  USER,
  GUEST
}

Prefer:

type Role = 'admin' | 'user' | 'guest';

It’s simpler, easier to debug, and more tree-shakable in modern bundlers.


📁 8. Organize Types Alongside Features

Don’t dump all types in a types.ts file.
Instead, colocate them:

/src
 ┣ /users
 ┃ ┣ index.ts
 ┃ ┣ user.types.ts
 ┣ /auth
 ┃ ┣ auth.types.ts

This keeps things maintainable as your project grows.


🧬 9. Prefer as const for Literal Inference

const roles = ['admin', 'user', 'guest'] as const;
type Role = typeof roles[number];

No more manually typing out all string literals. Clean and safe.


🧯 10. Never Ignore Type Errors — Fix or Type Narrow

Don’t “fix” by forcing types:

const data = getData() as any; // ❌ 

Instead, narrow them:

if ('user' in data) {
  // ✅ Safe access
}

Or validate:

function isUser(obj: unknown): obj is User {
  return (
    typeof obj === 'object' &&
    obj !== null &&
    'id' in obj &&
    'email' in obj
  );
}

This way, TypeScript remains your ally, not your obstacle.


🧠 Final Advice:

TypeScript isn’t about “adding types” — it’s about communicating intent.
The more clearly your code tells a story, the less your team argues about bugs.

Keep writing, keep refactoring, and remember:
👉 Good TypeScript code reads like good documentation.

Stay Updated with Our Latest News

Subscribe to our newsletter and be the first to know about our latest projects, blog posts, and industry insights.