If JavaScript works, why was TypeScript created?
Imagine you're building a small calculator app using JavaScript. Everything works perfectly. You write a few functions, display some buttons, and you're done in a couple of hours.
Now imagine you're building something much larger—a banking application, an e-commerce platform like Amazon, or a social media platform like Instagram. Thousands of files. Hundreds of developers. Millions of users.
Suddenly, JavaScript starts showing its limitations.
A small mistake in one file might not be discovered until someone actually uses that feature. A function expecting a number might accidentally receive a string. A property name could be misspelled. An API might return unexpected data.
The browser won't complain until the code runs.
That's where TypeScript changes everything.
TypeScript doesn't replace JavaScript—it improves it. Think of it as a smart assistant that checks your code before it ever reaches the browser. It catches mistakes early, provides better tooling, and makes large projects much easier to maintain.
Today, TypeScript powers projects at companies like Microsoft, Google, Airbnb, Shopify, Discord, and countless startups. If you're learning modern web development with React, Next.js, Angular, or Node.js, TypeScript has become an essential skill.
In this article, we'll build a strong foundation by understanding:
Why TypeScript was created
How static typing works
Type annotations and type inference
Interfaces and type aliases
Union and intersection types
Generic functions
The purpose of tsconfig.json
How TypeScript becomes JavaScript
By the end, you'll understand not just how TypeScript works, but why developers around the world rely on it every day.
What is TypeScript?
TypeScript is an open-source programming language developed by Microsoft.
The simplest way to understand TypeScript is this:
TypeScript is JavaScript with an additional type system.
That means every valid JavaScript program is also a valid TypeScript program.
For example, this JavaScript code works perfectly in TypeScript:
function greet(name) {
return "Hello " + name;
}
console.log(greet("Shivam"));
TypeScript doesn't force you to rewrite everything. Instead, it lets you gradually add types whenever you need them.
Here's the same code written in TypeScript:
function greet(name: string): string {
return Hello ${name};
}
console.log(greet("Shivam"));
Notice the difference?
name: string
and
): string
These tell TypeScript:
The parameter name must be a string.
The function will always return a string.
These simple additions allow TypeScript to detect mistakes before your application even runs.
JavaScript vs TypeScript
Let's compare them side by side.
JavaScript
function add(a, b) {
return a + b;
}
add(10, 20);
add("10", 20);
Output:
30
1020
The second call isn't an error.
JavaScript simply converts the number into a string and concatenates them.
Sometimes this is useful.
Sometimes it's a nightmare.
Now look at TypeScript.
function add(a: number, b: number): number {
return a + b;
}
add(10, 20);
add("10", 20);
Immediately, your editor shows:
Argument of type 'string'
is not assignable to parameter of type 'number'.
The mistake is caught before your code runs.
This is one of the biggest reasons developers love TypeScript.
JavaScript Workflow
Developer
│
▼
Write JavaScript
│
▼
Browser Runs Code
│
▼
Runtime Error (maybe)
If something is wrong, you'll only know after executing the application.
TypeScript Workflow
Developer
│
▼
Write TypeScript
│
▼
TypeScript Compiler
│
├── Errors Found ❌
│
└── JavaScript Generated ✅
│
▼
Browser Executes
Instead of discovering problems at runtime, you fix them during development.
Why TypeScript Exists
To understand why TypeScript exists, let's first understand the problems JavaScript faces in large applications.
JavaScript is an amazing language. It's flexible, easy to learn, and runs almost everywhere—from browsers to servers.
However, flexibility comes with trade-offs.
Problem 1: Dynamic Typing
JavaScript variables can change their type at any time.
let age = 20;
age = "Twenty";
age = true;
age = {};
JavaScript allows all of this without any warnings.
While this flexibility is convenient, it can also lead to confusing bugs.
Imagine a function that calculates someone's birth year.
function getBirthYear(age) {
return 2026 - age;
}
console.log(getBirthYear(20));
Works perfectly.
But someone accidentally calls:
getBirthYear("Twenty");
The result?
NaN
The error isn't discovered until the function actually executes.
In TypeScript:
function getBirthYear(age: number): number {
return 2026 - age;
}
getBirthYear("Twenty");
Error:
Argument of type 'string'
is not assignable to parameter of type 'number'.
The bug never reaches production.
Problem 2: Runtime Errors
One of the biggest issues in JavaScript is that many mistakes are only discovered while the application is running.
Example:
const user = {
name: "Shivam"
};
console.log(user.email.toLowerCase());
The application crashes.
Cannot read property 'toLowerCase' of undefined
The code looked fine.
But the object didn't contain an email property.
Now let's define the structure using TypeScript.
interface User {
name: string;
email: string;
}
const user: User = {
name: "Shivam"
};
TypeScript immediately reports:
Property 'email'
is missing.
Instead of discovering the issue after deployment, you fix it while writing the code.
Compile-Time Errors vs Runtime Errors
Understanding this difference is one of the most important concepts in TypeScript.
Compile-Time Error
A compile-time error is detected before the program runs.
Example:
let age: number = "Twenty";
TypeScript immediately stops you.
Type 'string'
is not assignable to type 'number'
Runtime Error
A runtime error occurs after the application starts.
Example:
const numbers = null;
console.log(numbers.length);
Output:
Cannot read property 'length' of null
The browser discovers the problem only after executing the code.
Visual Comparison
JavaScript
Write Code
│
▼
Run Program
│
▼
Error Appears
TypeScript
Write Code
│
▼
Compiler Checks
│
├── Error Found
│
▼
Fix Code
│
▼
Run Program
TypeScript shifts many common errors from runtime to compile time, saving debugging time and improving reliability.
Benefits of Static Typing
Static typing is the core feature that makes TypeScript so valuable.
When you specify the type of your variables, functions, and objects, TypeScript can understand your code better and provide useful feedback.
Here are some key benefits:
- Fewer Bugs
TypeScript catches many mistakes before your application runs.
let price: number = 999;
price = "999";
Error immediately.
- Better Autocomplete
Because TypeScript knows the structure of your objects, editors like VS Code can provide intelligent suggestions.
const user = {
name: "Shivam",
age: 20,
city: "Mumbai"
};
user.
The editor instantly suggests:
name
age
city
No need to remember every property.
- Easier Refactoring
Imagine changing a property name in a project with 500 files.
Without TypeScript, you might accidentally miss several references.
With TypeScript, every broken reference is highlighted automatically.
- Better Team Collaboration
When another developer reads this function:
function createOrder(order: Order): Promise
They immediately understand:
What goes in
What comes out
Which data structure is expected
The code becomes self-documenting.
- Improved Maintainability
Large projects often stay active for years.
Developers join and leave.
Features are added constantly.
Strong typing helps ensure that changes in one part of the application don't unintentionally break another.
TypeScript Is a Superset of JavaScript
A common misconception is that TypeScript replaces JavaScript.
It doesn't.
Instead, TypeScript builds on top of JavaScript.
Think of JavaScript as the foundation and TypeScript as an extra safety layer.
TypeScript
┌───────────────────┐
│ Types │
│ Interfaces │
│ Generics │
│ Enums │
│ Compiler Checks │
└───────────────────┘
▲
│
JavaScript
Every valid JavaScript program can be renamed from .js to .ts and still work. You can then gradually introduce TypeScript features as your project grows.
Why Modern Companies Choose TypeScript
Large applications demand reliability, maintainability, and developer productivity. That's why TypeScript has become the standard choice for modern web development.
Some key reasons include:
Early error detection before deployment
Better IDE support with autocomplete and refactoring
Improved code readability and self-documentation
Safer collaboration across large teams
Easier maintenance of long-term projects
Excellent integration with frameworks like React, Next.js, Angular, and Node.js
As projects grow in size and complexity, these advantages save countless hours of debugging and reduce the likelihood of production bugs.
In the next part, we'll dive into Type Annotations—learning how to add types to variables, functions, objects, arrays, and how TypeScript's powerful type inference works. We'll also compare explicit and inferred types with practical examples before moving on to Interfaces and Type Aliases.
next
Understanding Type Annotations
Now that we know why TypeScript exists, it's time to learn how TypeScript actually understands our code.
The biggest feature of TypeScript is its type system.
When we tell TypeScript what kind of data a variable should store, it can warn us whenever we accidentally use the wrong type.
This process is called Type Annotation.
What is a Type Annotation?
A type annotation is simply a way of telling TypeScript the expected type of a variable, parameter, or return value.
The syntax is very simple:
variableName: Type
For example,
let username: string = "Shivam";
Here,
username is the variable.
string is its type.
"Shivam" is the value.
Now TypeScript knows that username should always contain a string.
If we try to assign another type, TypeScript immediately reports an error.
username = 20;
Type 'number' is not assignable to type 'string'
Instead of waiting until the application runs, the mistake is caught while we're writing the code.
Why Are Type Annotations Important?
Imagine you're building an e-commerce application.
Each product has a price.
let price = 999;
Months later another developer accidentally writes
price = "999";
JavaScript happily accepts it.
Later your discount calculation breaks because it expected a number.
With TypeScript:
let price: number = 999;
price = "999";
Immediately:
Type 'string' is not assignable to type 'number'
A bug that could have reached production is stopped instantly.
Common Primitive Types
TypeScript provides several built-in types.
string
Stores text.
let firstName: string = "Shivam";
let city: string = "Mumbai";
let course: string = "Web Development";
number
Stores integers and decimal values.
let age: number = 20;
let salary: number = 50000;
let temperature: number = 35.6;
Unlike some languages, JavaScript and TypeScript have only one number type.
There is no separate int, float, or double.
boolean
Represents true or false.
let isLoggedIn: boolean = true;
let isAdmin: boolean = false;
Very commonly used for authentication and feature flags.
bigint
For very large integers.
let views: bigint = 12345678901234567890n;
symbol
Used to create unique identifiers.
let id: symbol = Symbol("id");
Most beginners won't use this often, but it's part of the language.
Arrays
An array stores multiple values of the same type.
JavaScript
const numbers = [1,2,3,4];
JavaScript doesn't enforce that future values remain numbers.
You could accidentally do:
numbers.push("Hello");
Now your array contains mixed data.
TypeScript
const numbers: number[] = [1,2,3,4];
Trying to push a string:
numbers.push("Hello");
Produces:
Argument of type 'string'
is not assignable to parameter of type 'number'
Another syntax:
const names: Array = [
"Shivam",
"Rahul",
"Ankit"
];
Both are correct.
Most developers prefer
string[]
because it's shorter.
Objects
Objects become much safer in TypeScript.
JavaScript:
const user = {
name: "Shivam",
age: 20
};
Nothing prevents someone from writing
user.age = "Twenty";
TypeScript:
const user: {
name: string;
age: number;
} = {
name: "Shivam",
age: 20
};
Now
user.age = "Twenty";
gives an error.
Functions
Functions are where TypeScript becomes incredibly useful.
We can specify
parameter types
return types
JavaScript
function greet(name){
return "Hello " + name;
}
No one knows what type name should be.
TypeScript
function greet(name: string): string {
return Hello ${name};
}
Let's understand this carefully.
name: string
means
The parameter must be a string.
): string
means
This function always returns a string.
Calling the function correctly
greet("Shivam");
works perfectly.
But
greet(10);
shows
Argument of type 'number'
is not assignable to parameter of type 'string'
Multiple Parameters
function add(
a: number,
b: number
): number {
return a + b;
}
Usage
add(10,20);
Wrong
add("10",20);
Error immediately.
Optional Parameters
Sometimes a value isn't always available.
For example,
function greet(name: string, city?: string) {
if(city){
return `Hello ${name} from ${city}`;
}
return `Hello ${name}`;
}
Now both calls are valid.
greet("Shivam");
greet("Shivam","Mumbai");
The ? means the parameter is optional.
Default Parameters
function greet(
name: string,
city: string = "Mumbai"
){
return `Hello ${name} from ${city}`;
}
Now
greet("Shivam");
returns
Hello Shivam from Mumbai
Void Return Type
Some functions don't return anything.
function printMessage(message: string): void {
console.log(message);
}
The return type is
void
meaning
This function performs an action but returns nothing.
Never Return Type
The never type is used for functions that never successfully finish.
Example:
function throwError(message: string): never {
throw new Error(message);
}
Because an error is thrown, the function never reaches the end.
Any Type
Sometimes we don't know the type.
TypeScript provides
any
let value: any;
value = 10;
value = "Hello";
value = true;
value = {};
This behaves almost exactly like JavaScript.
However,
avoid using any whenever possible.
Using any disables TypeScript's safety checks.
Think of it as turning off your seatbelt.
Unknown Type
Instead of any, TypeScript introduced unknown.
let value: unknown;
Unlike any, you cannot use it directly.
value.toUpperCase();
Error.
You must first check its type.
if(typeof value === "string"){
console.log(value.toUpperCase());
}
This makes your code much safer.
Type Inference
One of TypeScript's smartest features is Type Inference.
Sometimes you don't even have to write the type.
Example
let age = 20;
Notice we didn't write
let age: number = 20;
Yet TypeScript already knows
age → number
If you later write
age = "Twenty";
You still get an error.
TypeScript automatically inferred the type.
Another example
const city = "Mumbai";
TypeScript infers
city: string
Functions also benefit from inference.
function multiply(a: number,b: number){
return a*b;
}
We didn't write the return type.
TypeScript automatically infers
number
Explicit Types vs Inferred Types
There are two ways to define types.
Explicit
You manually specify the type.
let username: string = "Shivam";
Everything is written clearly.
Inferred
TypeScript figures it out.
let username = "Shivam";
Both produce the same result.
Which One Should You Use?
A common question is:
Should I always write types?
The answer is no.
Use inference whenever the type is obvious.
Good example
const age = 20;
No need to write
const age: number = 20;
But when writing functions, APIs, exported variables, or complex objects, explicit types improve readability.
function createUser(
name: string,
age: number
): User {
}
Anyone reading the code immediately understands what is expected.
Best Practices
Let TypeScript Infer Simple Types
Good
const username = "Shivam";
Not necessary
const username: string = "Shivam";
Always Type Function Parameters
function login(email: string,password: string){
}
Avoid
function login(email,password){
}
Avoid any
Instead of
let response: any;
prefer
let response: unknown;
or define a proper interface.
Always Specify Return Types for Public Functions
function calculateTotal(): number{
}
This improves readability and prevents accidental changes.
Common Beginner Mistakes
Using any Everywhere
let data:any;
This removes almost every benefit of TypeScript.
Forgetting Function Return Types
Large projects become difficult to understand.
Mixing Types
let age:number=20;
age="Twenty";
TypeScript exists specifically to prevent mistakes like this.
Overusing Explicit Types
Writing
const city:string="Mumbai";
everywhere makes code unnecessarily verbose.
Trust TypeScript's inference when appropriate.
Summary
Type annotations are the foundation of TypeScript. They allow you to describe the shape of your data, making your code more predictable and easier to maintain. Combined with TypeScript's powerful type inference, they strike a balance between safety and developer productivity.
By understanding how to annotate variables, functions, arrays, and objects—and knowing when to rely on inference—you'll write cleaner, more reliable applications with fewer runtime surprises.
Top comments (0)