Every house starts with bricks. In TypeScript, those bricks are the basic types — the small, built-in labels you attach to your data so the compiler knows what kind of value it is dealing with. Once you are comfortable with these, almost everything else in TypeScript starts to click into place.
In this article we will walk slowly through the types you will reach for on day one: string, number, and boolean; the two “empty” values null and undefined; and a handful of special types — any, unknown, void, and never — that solve specific problems. We will keep the examples tiny on purpose, so you can read each one in a few seconds.
What a “type” actually means
A type is a promise about a value. When you write let age: number, you are telling TypeScript: “this box will only ever hold a number.” If you later try to put a word in it, TypeScript stops you before the code even runs.
let age: number = 30;
age = 31; // fine, still a number
age = "thirty"; // Error: Type 'string' is not assignable to type 'number'
That little : number is called a type annotation. It is the colon, followed by the type name. You will see this pattern everywhere.
Often you do not even need to write the annotation, because TypeScript can guess the type from the value you assign. This guessing is called type inference.
let city = "Jakarta"; // TypeScript infers: string
city = "Bandung"; // fine
city = 42; // Error: number is not assignable to string
Let inference do the work
When a variable is assigned a value right away, you usually do not need to write the type by hand. Save explicit annotations for places where the type is not obvious — like function parameters and return values.
The three you will use most: string, number, boolean
These three primitive types cover the vast majority of everyday data.
string
A string is text: a name, an email, a sentence. You can write it with single quotes, double quotes, or backticks. Backticks allow template literals, where you drop variables straight into the text using ${ }.
const firstName: string = "Jane";
const greeting: string = `Hello, ${firstName}!`; // "Hello, Jane!"
number
In TypeScript there is just one number type for everything numeric — whole numbers, decimals, negatives, and even special values like Infinity. There is no separate int or float.
const price: number = 19.99;
const quantity: number = 3;
const total: number = price * quantity; // 59.97
boolean
A boolean holds one of two values: true or false. It answers yes-or-no questions.
const isActive: boolean = true;
const hasDiscount: boolean = false;
Put together, these three already let you describe a lot. Here is a small user, the kind of object you will model constantly:
const user: { name: string; email: string; isVerified: boolean } = {
name: "John Doe",
email: "john@example.com",
isVerified: false,
};
The two empty values: null and undefined
Not every value is present. Sometimes a field is intentionally empty, and sometimes it simply has not been set yet. TypeScript gives you two words for this.
undefinedusually means “this has not been given a value.” It is what you get from a variable you declared but did not assign.nullusually means “this is intentionally empty.” You set it on purpose to say “there is no value here.”
let middleName: string | undefined; // not assigned yet -> undefined
let manager: string | null = null; // intentionally no manager
That string | undefined is a union type: it means the value can be a string or undefined. We are not diving deep into unions here, but it is worth noting that combining a normal type with null or undefined is one of the most common things you will do.
Turn on strictNullChecks
With the strictNullChecks option enabled (it is on by default in strict mode), TypeScript forces you to handle the empty case before using a value. This prevents a huge category of “cannot read property of undefined” crashes. Always keep it on.
Because of that option, TypeScript will nudge you to check before you act:
function printName(name: string | null) {
// name.toUpperCase(); // Error: name might be null
if (name !== null) {
console.log(name.toUpperCase()); // safe here
}
}
The escape hatch: any
any is the type that means “turn off type checking for this value.” A variable typed as any can hold anything, and you can do anything to it. TypeScript steps back and stops watching.
let data: any = "hello";
data = 42; // ok
data = true; // ok
data.foo.bar.baz(); // ok at compile time... and a crash at runtime
That last line shows the problem. With any, you lose every safety check. The whole reason you reached for TypeScript was to catch mistakes early, and any quietly switches that off. It tends to spread, too: values touched by an any often become any themselves.
Treat any as a last resort
Reaching for any is like removing the seatbelts to drive faster. It works until it does not. Use it only when you genuinely cannot describe a type, and even then, leave a comment explaining why. Most of the time there is a better option — and that option is unknown.
The safe sibling: unknown
unknown is the type for “I do not know what this is yet, but I want to stay safe.” Like any, it can hold any value. Unlike any, TypeScript will not let you use it until you have proven what it is.
let value: unknown = fetchSomething();
// value.toUpperCase(); // Error: object is of type 'unknown'
if (typeof value === "string") {
console.log(value.toUpperCase()); // now TypeScript knows it is a string
}
This is exactly the behaviour you want when handling data from outside your program — an API response, user input, a parsed JSON file. You accept it as unknown, then narrow it down with checks before trusting it.
| Type | Holds any value? | Lets you use it freely? | Safety |
|---|---|---|---|
any |
Yes | Yes, no checks | None |
unknown |
Yes | No, must check first | Full |
The short rule: prefer unknown over any whenever you are tempted to use any.
void and never: two types about “nothing”
These two appear less often, but understanding them clears up a lot of confusion later.
void
void is the return type of a function that does not return a useful value. Think of a function whose job is to do something — log a message, save a record — rather than to compute something.
function logMessage(text: string): void {
console.log(text);
// no return statement that gives back a value
}
You rarely write void on variables; it mostly shows up as a function’s return type, and TypeScript usually infers it for you.
never
never is the type for a value that can never happen. A function returns never when it never finishes normally — for example, because it always throws an error or loops forever.
function fail(message: string): never {
throw new Error(message);
}
You will not type never by hand very often as a beginner. It is mostly the compiler’s way of telling you “this branch is impossible,” which becomes useful later when you work with exhaustive checks. For now, just recognize the name.
void -> "I return, but with nothing useful"
never -> "I never even return normally"
A quick reference of everything covered
Here is the whole set in one place, with the one-line idea behind each:
| Type | Meaning | Typical use |
|---|---|---|
string |
Text | Names, emails, messages |
number |
Any numeric value | Prices, counts, ages |
boolean |
true or false |
Flags, conditions |
undefined |
Not yet given a value | Optional / unset fields |
null |
Intentionally empty | “No value on purpose” |
any |
Anything, no checks | Avoid — last resort |
unknown |
Anything, must check first | Untrusted external data |
void |
Function returns nothing useful | Side-effect functions |
never |
Value that cannot occur | Always-throwing functions |
Putting it together
Let us tie these into one small, realistic snapshot — registering a user:
interface NewUser {
name: string;
age: number;
isAdmin: boolean;
referredBy: string | null;
}
function registerUser(input: NewUser): void {
console.log(`Registering ${input.name}, age ${input.age}`);
}
registerUser({
name: "Jane Doe",
age: 28,
isAdmin: false,
referredBy: null,
});
Read that and notice how the types document the data. Anyone opening this file knows, at a glance, that age is a number, isAdmin is a yes/no flag, and referredBy might be empty. That clarity is the real payoff of typing your values.
Recap
The basic types are the foundation you will build everything else on. string, number, and boolean describe most everyday data. null and undefined describe the absence of a value — keep strictNullChecks on so TypeScript reminds you to handle them. void and never describe functions that return nothing useful, or nothing at all.
And the two that share a job, with very different attitudes: any switches off type safety and should be your last resort, while unknown keeps you safe by forcing a check before you use the value. When you feel the urge to write any, pause and ask whether unknown would do — almost always, it will. Get comfortable with these, and the rest of TypeScript will feel a lot less intimidating.