TypeScript Fundamentals: Types, Interfaces, Unions, and Generics
Learn the TypeScript concepts developers use most often, including type annotations, interfaces, union types, narrowing, generics, and practical patterns for building safer JavaScript applications.

Tools used: TypeScript, Node.js, VS Code, npm
Prerequisites: Basic JavaScript knowledge, including variables, functions, objects, arrays, and modules.
TypeScript Fundamentals: Types, Interfaces, Unions, and Generics
JavaScript gives developers a lot of flexibility, but that flexibility can become difficult to manage as applications grow.
A function may expect a number but receive a string. An API response may be missing a property. A component may accept the wrong object shape. A value may be one of several possible types, but the code does not handle every case.
TypeScript adds static type checking to JavaScript development, allowing many of these problems to be detected before the application runs.
But learning TypeScript is not about adding : string everywhere.
The real value comes from understanding how to model data and how TypeScript can help you write safer, more maintainable code.
This guide focuses on the TypeScript concepts developers use frequently:
- Type annotations
- Object types
- Interfaces
- Type aliases
- Union types
- Narrowing
- Functions
- Optional properties
- Generics
- Practical API examples
- Common mistakes
Quick Answer
| Concept | What it helps with | Example |
|---|---|---|
| Type annotation | Describing a value | let age: number |
| Interface | Describing object structure | interface User |
| Type alias | Creating reusable types | type ID = string |
| Union | Allowing multiple possible types | string | number |
| Narrowing | Making a union more specific | typeof value === "string" |
| Generic | Reusing logic with different types | function get<T>() |
A simple way to remember the concepts:
Types describe data. Interfaces describe objects. Unions describe alternatives. Generics make code reusable.
1. TypeScript Starts With JavaScript
TypeScript is built around JavaScript.
A normal JavaScript function might look like this:
function calculateTotal(price, quantity) {
return price * quantity;
}The problem is that JavaScript does not require the caller to provide the values you intended.
calculateTotal(100, 2);
calculateTotal("100", 2);
calculateTotal(true, 2);Some of these may produce surprising runtime behavior.
TypeScript lets you describe the expected types:
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}Now the intended contract is clear:
pricemust be a numberquantitymust be a number- the function returns a number
2. Type Annotations
A type annotation tells TypeScript what kind of value a variable should contain.
let username: string = "Sovit";
let age: number = 25;
let isActive: boolean = true;Arrays can also have types:
let skills: string[] = [
"TypeScript",
"React",
"Node.js"
];Another syntax is:
let scores: Array<number> = [80, 90, 95];Both describe an array of numbers.
Type Inference
You do not always need to explicitly write the type.
TypeScript can often infer it from the assigned value.
let username = "Sovit";TypeScript understands that username is a string.
Likewise:
let age = 25;TypeScript infers:
numberThis means you should not add annotations everywhere just for the sake of adding them.
Prefer useful types where they make the code clearer.
Use TypeScript to make the code clearer, not noisier. Let inference handle obvious cases and add explicit types where the contract matters.
3. Object Types
Real applications usually work with objects.
For example, a job application might contain:
const job = {
title: "Software Engineer",
company: "Karyvio",
location: "Bengaluru"
};You can describe that object with a type:
type Job = {
title: string;
company: string;
location: string;
};Now a variable can use it:
const job: Job = {
title: "Software Engineer",
company: "Karyvio",
location: "Bengaluru"
};If a required property is missing:
const job: Job = {
title: "Software Engineer",
company: "Karyvio"
};TypeScript reports an error because location is required.
This is one of the most useful benefits of TypeScript in application development.
4. Interfaces
Interfaces are commonly used to describe object structures.
interface User {
id: number;
name: string;
email: string;
}You can then use the interface throughout your application:
function displayUser(user: User) {
console.log(user.name);
console.log(user.email);
}Example:
const user: User = {
id: 101,
name: "Amit",
email: "amit@example.com"
};
displayUser(user);Interfaces are especially useful when several parts of an application need to agree on the same object structure.
5. Type Alias vs Interface
Both type and interface can describe object shapes.
For example:
type User = {
id: number;
name: string;
};and:
interface User {
id: number;
name: string;
}Both can describe the same basic object.
A useful practical distinction is that type can represent more than object shapes.
For example:
type ID = string | number;You can also create combinations:
type Status = "pending" | "approved" | "rejected";The important lesson is not to turn the choice into a rigid rule.
Use the construct that communicates the model clearly and follow the conventions of the project you are working in.
6. Optional Properties
Not every property is required.
Use ? when a property may be missing.
interface Developer {
name: string;
experience: number;
github?: string;
}Now this is valid:
const developer: Developer = {
name: "Rahul",
experience: 2
};And this is also valid:
const developer: Developer = {
name: "Rahul",
experience: 2,
github: "https://github.com/rahul"
};This is particularly useful for API responses and forms where some fields are optional.
7. Union Types
Sometimes a value can legitimately have more than one type.
For example:
let userId: string | number;Now both are valid:
userId = 101;
userId = "USR-101";The | symbol creates a union type.
Practical Example
Imagine an API can return either a numeric ID or a string ID:
type UserId = string | number;
function formatUserId(id: UserId): string {
return `USER-${id}`;
}Both work:
formatUserId(101);
formatUserId("ABC123");But TypeScript does not automatically assume that a union value has every method available on every member.
That leads to one of the most important TypeScript concepts: narrowing.
8. Narrowing
Suppose a value can be a string or number:
function printValue(value: string | number) {
console.log(value);
}If you want to use a string-specific operation, TypeScript needs to know which type you have.
function printValue(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}The typeof check narrows the possible type inside each branch.
This is called type narrowing. TypeScript uses control-flow analysis and type guards to refine types based on checks in the code.
Another Narrowing Example
Consider two different API response shapes:
type SuccessResponse = {
status: "success";
data: string[];
};
type ErrorResponse = {
status: "error";
message: string;
};
type ApiResponse = SuccessResponse | ErrorResponse;You can narrow it using the status property:
function handleResponse(response: ApiResponse) {
if (response.status === "success") {
console.log(response.data);
} else {
console.error(response.message);
}
}This pattern is extremely useful when working with APIs.
9. Functions With Types
Functions are one of the most important places to use TypeScript.
function add(a: number, b: number): number {
return a + b;
}The first types describe the parameters.
The final number describes the return value.
Another example:
function createGreeting(name: string): string {
return `Hello, ${name}`;
}You can also use object types:
interface Product {
id: number;
name: string;
price: number;
}
function calculatePrice(product: Product): number {
return product.price;
}Now the function has a clear contract.
10. Function Parameters With Optional Values
Parameters can also be optional.
function greet(name: string, role?: string) {
if (role) {
return `Hello ${name}, you are a ${role}.`;
}
return `Hello ${name}.`;
}Both are valid:
greet("Amit");
greet("Amit", "Developer");This is useful for functions where additional information is optional.
11. Generics: Reusable Type-Safe Code
Generics become important when you want one function or component to work with different types without losing type information.
Consider this function:
function identity(value: string): string {
return value;
}It works for strings, but not other types.
You could create another function:
function identityNumber(value: number): number {
return value;
}That duplicates logic.
Generics solve the problem:
function identity<T>(value: T): T {
return value;
}Now TypeScript can preserve the type.
const username = identity("Sovit");
const age = identity(25);The returned values retain their respective types.
TypeScript's current documentation describes generics as a way to create reusable components that work across different types while keeping type relationships intact.
12. A Practical Generic API Response
Generics become especially useful when building API clients.
Imagine every API response has this structure:
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}Now you can reuse the same response structure for different data.
For users:
interface User {
id: number;
name: string;
}
const userResponse: ApiResponse<User> = {
success: true,
data: {
id: 1,
name: "Amit"
}
};For jobs:
interface Job {
id: number;
title: string;
company: string;
}
const jobResponse: ApiResponse<Job[]> = {
success: true,
data: [
{
id: 1,
title: "Java Developer",
company: "Example Corp"
}
]
};The response structure stays consistent while the actual data type changes.
That is the practical power of generics.
13. Generic Functions in Real Applications
Consider a function that returns the first item from an array.
Without generics:
function firstItem(items: string[]): string {
return items[0];
}This only works with strings.
A generic version is reusable:
function firstItem<T>(items: T[]): T {
return items[0];
}Now:
const firstJob = firstItem([
"Java Developer",
"Backend Developer"
]);
const firstNumber = firstItem([
10,
20,
30
]);TypeScript understands the appropriate type for each result.
14. Generics With Constraints
Sometimes a generic type needs to satisfy a requirement.
For example, suppose a function needs an object with an id.
function getId<T extends { id: number }>(item: T): number {
return item.id;
}This works:
const user = {
id: 10,
name: "Amit"
};
getId(user);But an object without an id would not satisfy the constraint.
The extends here does not mean class inheritance in the normal object-oriented sense. In this context, it constrains what types can be used for T.
15. any vs unknown
One common TypeScript mistake is using any whenever the type is inconvenient.
For example:
let data: any;any effectively disables much of TypeScript's type checking for that value.
A safer alternative for genuinely unknown data is often:
let data: unknown;With unknown, you must check the value before treating it as a specific type.
function processValue(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase());
}
}This forces you to establish what the value actually is.
The TypeScript documentation specifically describes unknown as useful for values whose type is not known ahead of time and notes that such values can be narrowed with checks.
16. A Practical API Example
Suppose a frontend application receives this API response:
{
"id": 101,
"name": "Amit",
"role": "Software Engineer"
}You could model it like this:
interface Developer {
id: number;
name: string;
role: string;
}Then:
async function getDeveloper(): Promise<Developer> {
const response = await fetch("/api/developer");
return response.json();
}Now code using the function knows what it should receive:
const developer = await getDeveloper();
console.log(developer.name);
console.log(developer.role);This makes the API contract visible in the code.
17. TypeScript in a React Application
TypeScript becomes especially useful in frontend applications.
For example, a component can describe its props:
interface UserCardProps {
name: string;
role: string;
experience: number;
}
function UserCard({
name,
role,
experience
}: UserCardProps) {
return (
<div>
<h2>{name}</h2>
<p>{role}</p>
<p>{experience} years</p>
</div>
);
}Now a developer using the component gets immediate feedback if the required props are missing or incorrectly typed.
This becomes increasingly valuable as React applications grow.
18. TypeScript for Backend Development
TypeScript is not limited to frontend development.
It is also useful for Node.js backend applications.
For example:
interface CreateUserRequest {
name: string;
email: string;
age: number;
}An API handler can use that model:
function createUser(data: CreateUserRequest) {
console.log(data.name);
console.log(data.email);
console.log(data.age);
}The same type-driven approach can be applied to:
- API requests
- API responses
- database models
- service functions
- configuration
- authentication data
- event payloads
19. Type Composition
TypeScript allows developers to create new types from existing ones.
For example:
interface User {
id: number;
name: string;
email: string;
}You can create another type:
type UserPreview = Pick<User, "id" | "name">;Now:
const user: UserPreview = {
id: 1,
name: "Amit"
};This is useful when different parts of an application need different views of the same data.
TypeScript's type system includes tools such as keyof, indexed access types, conditional types, mapped types, and template literal types for creating types from other types.
20. A Simple Developer Workflow
A practical TypeScript workflow can look like this:
The key idea is that TypeScript checks your code before the resulting JavaScript executes.
21. Common TypeScript Mistakes
Mistake 1: Using any everywhere
function processUser(user: any) {
return user.name;
}This removes useful type information.
Prefer:
interface User {
name: string;
}
function processUser(user: User) {
return user.name;
}Mistake 2: Overusing type assertions
You may see:
const user = value as User;A type assertion tells TypeScript to treat the value as a particular type.
It does not magically validate the runtime data.
If external data is untrusted, validate it instead of blindly asserting its type.
Mistake 3: Making everything optional
This:
interface User {
id?: number;
name?: string;
email?: string;
}may look flexible, but it also means every consumer must handle missing properties.
If the properties are required in reality, make them required:
interface User {
id: number;
name: string;
email: string;
}Mistake 4: Ignoring narrowing
This can cause problems:
function format(value: string | number) {
return value.toUpperCase();
}number does not have toUpperCase().
Instead:
function format(value: string | number) {
if (typeof value === "string") {
return value.toUpperCase();
}
return value.toFixed(2);
}22. TypeScript and JavaScript: The Practical Difference
| JavaScript | TypeScript |
|---|---|
| Dynamically typed | Statically type-checked |
| Types checked at runtime behavior | Types checked during development/build |
| Very flexible | Adds explicit type information |
| Easier to start | Better tooling for large codebases |
| Runtime errors can reveal type mistakes | Many type mistakes are caught earlier |
| Native browser runtime | Compiled/transpiled to JavaScript |
TypeScript does not replace JavaScript.
Your application ultimately runs JavaScript.
TypeScript adds a development-time type system around JavaScript code.
23. A Small TypeScript Project
If you want to practice these concepts, build a simple Job Tracker.
Define a job:
interface Job {
id: number;
title: string;
company: string;
status: JobStatus;
}Define possible statuses:
type JobStatus =
| "saved"
| "applied"
| "interview"
| "rejected"
| "offer";Create jobs:
const jobs: Job[] = [
{
id: 1,
title: "Frontend Developer",
company: "Example Corp",
status: "applied"
},
{
id: 2,
title: "Backend Developer",
company: "Tech Company",
status: "interview"
}
];Create a generic helper:
function findById<T extends { id: number }>(
items: T[],
id: number
): T | undefined {
return items.find(item => item.id === id);
}Use it:
const job = findById(jobs, 2);
console.log(job);This small project lets you practice:
- interfaces
- type aliases
- union types
- arrays
- functions
- generics
- optional return values
24. What You Should Learn After the Basics
Once these fundamentals become comfortable, move into more advanced TypeScript concepts.
A practical progression is:
Do not try to memorize every advanced type feature immediately.
First become comfortable modeling real application data.
25. TypeScript Skills That Matter in Real Projects
Knowing syntax is only one part of being good at TypeScript.
Developers should also understand:
- how to model API responses
- how to handle optional data
- how to narrow union types
- how to avoid unnecessary
any - how to design reusable types
- how to use generics
- how to work with third-party libraries
- how to configure
tsconfig.json - how TypeScript fits into React or Node.js
- how runtime validation differs from compile-time typing
The goal is not to create the most complicated type.
The goal is to make the application's contracts clearer.
TypeScript Cheat Sheet
Basic types
let name: string = "Amit";
let age: number = 25;
let active: boolean = true;Arrays
let skills: string[] = ["Java", "TypeScript"];Object
type User = {
id: number;
name: string;
};Interface
interface Product {
id: number;
name: string;
}Union
type ID = string | number;Optional property
interface User {
name: string;
github?: string;
}Function
function add(a: number, b: number): number {
return a + b;
}Narrowing
function print(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase());
}
}Generic
function identity<T>(value: T): T {
return value;
}Generic constraint
function getId<T extends { id: number }>(item: T) {
return item.id;
}Interview Questions
1. What is TypeScript?
TypeScript is a statically typed programming language built around JavaScript that adds compile-time type checking and other developer tooling.
2. What is the difference between type and interface?
Both can describe object structures, but type can also represent unions, intersections, primitives, and other type expressions.
3. What is a union type?
A union allows a value to be one of several types.
let id: string | number;4. What is type narrowing?
Narrowing is the process of using runtime checks or control flow to refine a value from a broader type to a more specific type.
5. What are generics?
Generics allow reusable functions, classes, and types to work with different types while preserving type relationships.
6. Why is unknown often safer than any?
unknown requires the value to be checked or narrowed before most operations, while any largely bypasses type checking.
7. Does TypeScript replace JavaScript?
No. TypeScript builds on JavaScript and is ultimately used to produce JavaScript that can run in JavaScript environments.
Final Takeaway
You do not need to master every TypeScript feature before building applications.
Start with the concepts that solve everyday problems:
- Types for describing values.
- Interfaces and type aliases for modeling application data.
- Union types for values with multiple possibilities.
- Narrowing for safely working with those possibilities.
- Generics for reusable type-safe code.
- Utility and advanced types once your application actually needs them.
The biggest shift is learning to think about your application's data before writing the implementation.
Instead of asking:
"How do I add TypeScript to this JavaScript?"
Start asking:
"What data can this function receive, what should it return, and what guarantees should the code provide?"
That mindset is what makes TypeScript useful in real-world development.
Key Takeaways
- TypeScript adds static type checking to JavaScript development.
- Type inference means you do not need to annotate everything.
- Interfaces and type aliases help model application data.
- Union types represent multiple possible types.
- Narrowing lets TypeScript safely refine those types.
unknownis generally safer than usinganyfor unknown external data.- Generics allow reusable code without losing type information.
- TypeScript is especially useful for API contracts, React applications, Node.js services, and larger codebases.
- Advanced types should be learned progressively as application complexity increases.
Learn the types. Model the data. Narrow the possibilities. Build with confidence.




Comments (0)
Be the first to share your thoughts.