TypeScript for Intermediate Developers
Type Foundations
From Flexibility to Structure
In JavaScript, you're used to variables holding any kind of data. A variable that starts as a number can be reassigned to a string without issue. TypeScript introduces a contract system on top of this flexibility. By defining what type of data a variable should hold, you ask the compiler to enforce that rule, catching potential bugs before your code ever runs.
This is done through type annotations. You explicitly tell TypeScript the type of a variable, parameter, or object property. Let's look at a simple variable.
// Explicitly typed as a string
let userName: string = "Alex";
// This is fine
userName = "Jordan";
// TypeScript will throw an error here!
// Error: Type 'number' is not assignable to type 'string'.
userName = 123;
However, you don't always have to write the type annotation. TypeScript is smart enough to figure it out in many cases. This is called type inference — the compiler infers the type based on the initial value assigned.
// TypeScript infers this is a number
let userAge = 30;
// This will cause an error because the type was inferred as 'number'
userAge = "thirty";
Shaping Your Objects
The real power comes when you start defining the structure of objects. In JavaScript, an object is a free-for-all collection of keys and values. TypeScript allows you to create a blueprint, ensuring every object created from that blueprint has the expected properties and types. This prevents common errors like typos in property names or trying to use a property that doesn't exist.
There are two main ways to define an object's shape: using an interface or a type alias. For now, think of them as very similar tools for the same job. Let's model a user profile.
// Using an interface
interface UserProfile {
id: number;
username: string;
isVerified: boolean;
}
const user: UserProfile = {
id: 101,
username: "casey_dev",
isVerified: true
};
// Using a type alias
type Product = {
sku: string;
price: number;
inStock: boolean;
};
const item: Product = {
sku: "TS-001",
price: 19.99,
inStock: false
};
Both interface and type create a named shape for our data. If you tried to create a user object without an id or assigned a string to isVerified, TypeScript would immediately flag it as an error.
Typing Functions
Functions are another area where TypeScript shines. You can add type annotations to function parameters and, crucially, to the function's return value. This acts as a contract for what the function accepts and what it promises to give back. It's the ultimate defense against the dreaded 'undefined is not a function' error because the compiler ensures your inputs and outputs match up.
// A function with typed parameters and a typed return value
function calculateTotal(price: number, quantity: number): number {
return price * quantity;
}
// Correct usage:
const totalCost = calculateTotal(25, 2); // totalCost is inferred as number
// Incorrect usage - TypeScript will show errors:
// const invalidCall = calculateTotal("25", 2); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
Here, the function signature (price: number, quantity: number): number clearly states that it takes two numbers and will always return a number. If you forget the return statement or try to return a different type of value, will catch it.
Functions that don't return a value are given the return type
void. This is an explicit way of saying the function performs an action but doesn't produce an output.
function logMessage(message: string): void {
console.log(`LOG: ${message}`);
// No return statement needed
}
With these basics, you can start converting your JavaScript habits into robust, type-safe TypeScript code. You're not learning a new language from scratch; you're adding a powerful safety net to the language you already know.