What problem does TypeScript solve?
In JavaScript, a function that expects a number will silently accept a string, and the bug shows up several files away, usually in front of a user. TypeScript adds a type-checking layer on top of the same language and surfaces this class of mistakes while you write, inside the editor.
The second benefit is less obvious: precise autocomplete and safe renaming. When every value has a known type, the editor knows which fields an object has, and you can rename a field across hundreds of files without fear. In a team, that doubles as living documentation.
There is a real cost too: a build step, a learning curve and occasionally complex types to write. It is not worth it for a tiny throwaway script; for anything that lives for months and has more than one author, it almost always pays off. Frontend frameworks such as Next.js apps scaffold TypeScript by default for this reason.
Setup and <code>tsconfig.json</code>
TypeScript is a development dependency. In an empty folder, three commands get you started:
npm init -y
npm install --save-dev typescript
npx tsc --init # creates tsconfig.json
# compile once, or watch for changes
npx tsc
npx tsc --watchThe tsconfig.json file controls the compiler. This small configuration is a solid starting point for most new projects:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noUncheckedIndexedAccess": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}Core types: from annotations to interfaces
TypeScript infers types in many places (type inference), so you do not have to annotate everything. Write types explicitly at boundaries: function parameters and return values.
const count = 3; // inferred: number
let title: string = "TETA"; // explicit annotation
function total(prices: number[], tax = 0.09): number {
return prices.reduce((sum, x) => sum + x, 0) * (1 + tax);
}To describe the shape of an object you use interface or type. In daily work the difference is small; a simple rule is interface for object and class shapes, type for unions and compositions.
interface User {
id: number;
name: string;
email?: string; // optional
readonly createdAt: Date;
}
type Role = "admin" | "editor" | "viewer";
function canEdit(user: User, role: Role): boolean {
return role !== "viewer" && user.id > 0;
}<code>any</code> versus <code>unknown</code>
any switches type checking off and sends the bug right back to where you were trying to escape it. When a type is genuinely unknown (an API response, say), use unknown: the compiler forces you to check it before use.
Unions, narrowing and discriminated unions
TypeScript's most powerful idea is that a value can be one of several shapes and the compiler narrows it based on your checks. Instead of a pile of optional fields where any combination is possible, you model only the valid states.
type Result =
| { status: "loading" }
| { status: "ok"; data: string[] }
| { status: "error"; message: string };
function render(r: Result): string {
switch (r.status) {
case "loading": return "Loading...";
case "ok": return r.data.join(", "); // data exists here
case "error": return "Failed: " + r.message;
}
}The shared status field is the discriminant. If you add a fourth state later and forget to update the switch, the compiler complains, provided strict is on and the return type is declared. The same pattern is excellent for UI state; see React state management for more examples.
Checking external data
Types do not exist at runtime. If you cast a server response to an interface, the compiler simply takes your word for it. For data arriving from the network or a form, use a validation library such as Zod, or a hand-written check, and derive the type from it.

Generics: reusable code with precise types
Generics let you write a function or structure that works with several types without collapsing to any. The type is received as a parameter and preserved in the output.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined
interface ApiResponse<T> {
data: T;
fetchedAt: string;
}
async function getJson<T>(url: string): Promise<ApiResponse<T>> {
const res = await fetch(url);
return { data: (await res.json()) as T, fetchedAt: new Date().toISOString() };
}A few built-in utility types you will use almost daily:
Partial<T>: makes every field optional; handy for edit forms.Pick<T, K>andOmit<T, K>: select or drop a subset of fields.Record<K, V>: an object with known keys and same-typed values.ReturnType<typeof fn>: extracts the return type of an existing function.
Strict mode and gradual migration
The strict option turns on a family of checks, the most important being strictNullChecks: null and undefined no longer sneak into every type and must be handled explicitly. It is the single option that catches the most real bugs.
For an existing JavaScript project you do not need a big-bang rewrite. This path tends to work well in practice:
- Add a
tsconfig.jsonwithallowJsandcheckJs: falseso js and ts files coexist. - Write all new files as
.tsor.tsx. - Convert small, widely imported files first (utilities, domain types, the API layer); they have the biggest payoff.
- Run
tsc --noEmitin CI so type errors cannot be merged. - Finally switch
stricton; if the error count is huge, enable its sub-options one at a time.
A checklist for clean TypeScript
strictis on andtsc --noEmitruns in CI.- No
anywithout a written reason; unknown data goes throughunknownand validation. - UI states are modelled as discriminated unions, not several separate booleans.
- Return types of exported functions are written explicitly.
- Shared types (data models, API responses) live in one clear place.
If your web project needs a migration or a fresh setup, our team handles it in stages as part of web design and development. The official reference lives in the TypeScript documentation. To build interfaces that put these types to good use, also read component design principles.
Frequently asked questions
Can I learn TypeScript without learning JavaScript first?
It is not recommended. TypeScript is built on JavaScript and the runtime behaviour is identical. Learn functions, objects, arrays and async/await first, then add types step by step.
What is the difference between interface and type?
Both describe object shapes. An interface can be extended and merged and suits classes well; a type can express unions, tuples and complex compositions. In most cases either works.
Does TypeScript make my app slower?
No. Types are erased at compile time and the output is ordinary JavaScript. The only cost is build and type-check time during development, not in the user's browser.
Do I need TypeScript for a small project?
Not strictly, but if it will live more than a few weeks or has more than one author it is usually worth it. For one-off scripts, plain JavaScript is fine.

Comments
No comments yet. Be the first to share your thoughts.