If you’ve spent any time with JavaScript, you’ve likely encountered the infamous undefined is not a function error. It’s a rite of passage. These runtime errors happen because JavaScript is dynamically typed, meaning it only figures out the “type” of a variable (like a string, number, or object) when the code is actually running.
TypeScript changes the game by adding a layer of static types on top of JavaScript. Think of it as a super-smart linter that understands the shape of your data. By defining types *before* you run your code, you can catch a huge class of bugs right in your editor. This is the core promise of TypeScript, and it all starts with understanding its type system.
Let’s dive into the essential TypeScript types you’ll use every day, moving from the simple to the more complex.
The Basics: Type Annotation Syntax
First, how do you even tell TypeScript about a type? You use a colon : after a variable or function parameter name, followed by the type.
let framework: string = "React";
let version: number = 18;
let isAwesome: boolean = true;
// This will now cause an error in your editor!
// version = "eighteen"; // Error: Type 'string' is not assignable to type 'number'.
This simple annotation is your contract with the TypeScript compiler. It lets you declare your intent and helps the compiler enforce it.
The Primitives: Your Daily Drivers
These are the fundamental data types that form the building blocks of any application.
string, number, and boolean
These work exactly as you’d expect. They represent text, numbers (both integers and floats), and true/false values.
let greeting: string = "Hello, world!";
let userCount: number = 1_000_000; // You can use underscores for readability
let hasLoggedIn: boolean = false;
A key thing to note for developers coming from languages like Java or C# is that there’s only one number type in TypeScript for all numeric values.
null and undefined
In TypeScript, null and undefined are considered their own types. They represent the intentional absence of a value and an uninitialized value, respectively.
let selectedUser: string | null = null; // This user might be selected later
function doSomething(callback?: () => void) {
// The an optional callback will be of type 'undefined' if not provided
if (callback) {
callback();
}
}
Pro Tip: Enable the strictNullChecks option in your tsconfig.json file. It forces you to explicitly handle cases where a value could be null or undefined, which eliminates a vast number of runtime errors.
The Special Types: Handling Absence and Impossibility
TypeScript includes a few special types for specific scenarios, particularly with functions.
void
void is used as the return type for functions that don’t return a value. If you’ve written a function that just logs to the console or updates a variable without returning, its return type is void.
function logMessage(message: string): void {
console.log(message);
// No return statement
}
never
This is a more interesting one. never represents a value that *never* occurs. You’ll typically see it in two situations:
- A function that always throws an error.
- A function that has an infinite loop.
In both cases, the function never reaches a return statement, so it never produces a value.
function throwError(message: string): never {
throw new Error(message);
}
function infiniteLoop(): never {
while (true) {
// ...
}
}
You won’t often write functions that return never, but it’s useful for the type system to understand and model these exhaustive checks.
The Escape Hatches: any and unknown
Sometimes you’re working with data where you truly don’t know the type, like from a dynamic API response or a third-party library without type definitions. TypeScript gives you two “escape hatches” for these situations.
any: The Wild West
The any type is the most flexible type in TypeScript. It essentially tells the compiler, “Trust me, I know what I’m doing,” and opts out of all type checking for that variable.
let myVariable: any = "this is a string";
// No errors, even though this is dangerous!
myVariable.someMethodThatDoesNotExist();
myVariable = 42;
Using any can be a crutch. It’s helpful when migrating a JavaScript codebase to TypeScript, but you should avoid it in new code whenever possible. It defeats the purpose of using TypeScript in the first place.
unknown: The Safer Alternative
unknown is the type-safe counterpart to any. It means, “I don’t know what this is, so you can’t do anything with it until you check.”
let myValue: unknown = "this could be anything";
// myValue.toUpperCase(); // Error: 'myValue' is of type 'unknown'.
// You MUST check the type before using it
if (typeof myValue === 'string') {
// Now TypeScript knows myValue is a string in this block
console.log(myValue.toUpperCase());
}
Rule of thumb: Always prefer unknown over any. It forces you to safely inspect a value before operating on it.
Structuring Your Data: Arrays, Tuples, and Objects
Arrays
To declare an array of a certain type, you add square brackets [] after the type.
let numbers: number[] = [1, 2, 3, 4, 5];
let names: string[] = ["Alice", "Bob", "Charlie"];
// An alternative syntax using generics
let booleans: Array<boolean> = [true, false, true];
Tuples
A tuple is like an array with a fixed number of elements where the type of each element is known. It’s perfect for when you want to return a fixed set of different types from a function.
// A tuple representing a key-value pair
let entry: [string, number];
entry = ["age", 30]; // Correct
// entry = ["name", "John"]; // Error: Type 'string' is not assignable to type 'number'.
// entry = ["age", 30, true]; // Error: Tuple of length '2' has extra element 'true'.
// A great use case for React hooks
// const [count, setCount] = useState(0); // This is a tuple: [number, (newValue: number) => void]
Objects
For simple, one-off objects, you can define their “shape” inline.
let user: { name: string; id: number; isActive: boolean };
user = {
name: "Ankur",
id: 1,
isActive: true
};
// user.id = "2"; // Error: Type 'string' is not assignable to type 'number'.
// user.email = "[email protected]"; // Error: Property 'email' does not exist on type...
This is powerful because it describes exactly which properties an object should have and what their types should be. For more complex or reusable object shapes, you’ll want to use type aliases or interface, but that’s a topic for another day!
Conclusion
Understanding these fundamental types is the first and most crucial step toward mastering TypeScript. By moving from the Wild West of dynamic JavaScript to the safer, more predictable world of static types, you empower your editor to become an invaluable partner in writing robust, bug-free code.
Start small. The next time you create a variable or write a function, take a moment to add a type annotation. You’ll be amazed at how quickly it becomes second nature and how many potential errors you’ll catch before they ever make it to the browser.