Primitive types
Learn TypeScript's seven primitive types, from string and number to bigint and symbol, why you write them in lowercase, and what types your constants get.
- 01Name the seven primitive typesWrite string, number, boolean, bigint, symbol, null, and undefined, and say which values each one holds.
- 02Pick lowercase typesExplain why
stringis right andStringis a wrapper object, and read the error TypeScript gives when they meet. - 03Read the type of a constantSay why
const minOrder = 50has the type50while aletgetsnumber, and when an annotation changes that.
Seven kinds of simple values
A user profile on a shop site holds a few simple values: a name, an age, whether the user is a member. JavaScript calls simple values like these primitives. A primitive is one plain value, like "Asha" or 31, with nothing stored inside it. JavaScript has seven kinds of them (the Data types lesson covers each one), and TypeScript has a type for each kind.
As in the last lesson, you write a type after a name with a colon: age: number means "age must hold a number". That is a type annotation. Here is one value of every kind, each with its type:
const userName: string = "Asha";const age: number = 31;const isMember: boolean = true;const profileViews: bigint = 9007199254740993n;const sessionKey: symbol = Symbol("session");const coupon: null = null;const nickname: undefined = undefined;console.log(typeof userName, typeof age, typeof isMember, typeof profileViews);console.log(typeof sessionKey, typeof coupon, typeof nickname);Line by line:
- Lines 1 to 3 are the three you'll write most:
stringfor text,numberfor numbers, andbooleanfortrueorfalse. - Line 4 is a
bigint, a whole number of any size. Thenat the end makes it one. - Line 5 is a
symbol, a unique value made bySymbol(). - Lines 6 and 7 are
nullandundefined, two ways to say "nothing here". Each one is a type with exactly one value. - Line 8 prints
string number boolean bigintand line 9 printssymbol object undefined. JavaScript'stypeofgives back the name of a value's kind (Checking types covers it).
TypeScript reports no errors: every value matches its type. Notice the type names are the same words typeof prints. That's on purpose. There is one exception, null:
| The value | What typeof says | Its TypeScript type |
|---|---|---|
"Asha" | "string" | string |
31 | "number" | number |
true | "boolean" | boolean |
9007199254740993n | "bigint" | bigint |
Symbol("session") | "symbol" | symbol |
null | "object", an old JavaScript mistake. | null |
undefined | "undefined" | undefined |
Read each row as: this value, what JavaScript's typeof prints for it, and the type you write in TypeScript. typeof null prints "object" because of a mistake in the first version of JavaScript that can't be fixed without breaking old pages. TypeScript gives null its own type, named null.
At a bank, you fill in a paper form. Each box takes one kind of short answer: letters for your name, digits for your age, a tick for "member?". Some boxes you leave blank, and some you fill with "none" on purpose. Before the form is filed, the clerk checks that each answer is the kind its box asks for.
- In real life: The name box takes letters
- In TypeScript:
stringholds text:userName: string - In real life: The age box takes digits
- In TypeScript:
numberholds numbers:age: number - In real life: The "member?" box takes a tick or no tick
- In TypeScript:
booleanholdstrueorfalse - In real life: The long account-number box has room for every digit
- In TypeScript:
bigintkeeps every digit of a big whole number - In real life: The ticket number printed on top matches no other form
- In TypeScript:
symbolis a value equal to no other - In real life: A box left blank, and a box where you wrote "none"
- In TypeScript:
undefined(nothing stored yet) andnull(empty on purpose) - In real life: The clerk spots words in the age box before filing the form
- In TypeScript: TypeScript marks
"31"going into anumberbefore the code runs
A primitive type names one kind of simple value. TypeScript has seven: string, number, boolean, bigint, symbol, null, and undefined. You write them in lowercase, after a colon: age: number.
string, number, and boolean
Most values on a page are text, numbers, or yes-or-no answers. Here is one of each for a product on a shop page:
let title: string = "Green tea";let price: number = 3.5;let inStock: boolean = true; price = 4;inStock = false;console.log(title, price, inStock);Line 1 says title is a string: any text, short or long, in single quotes, double quotes, or backticks (Strings). Line 2 says price is a number. Line 3 says inStock is a boolean: true or false, nothing else (Booleans).
These are let variables, so they can change. Line 5 puts a new price in, and line 6 marks the product sold out. Both new values match their types, so there are no errors. Line 7 prints Green tea 4 false.
Some languages have one type for whole numbers and another for decimals. TypeScript doesn't, because JavaScript doesn't: 3, 3.5, -3, and even NaN are all numbers (Numbers). Try it. Type a discount on line 2 and watch the list under the code:
const price: number = 20;const discount: number = 5;console.log(price - discount);5No errorsprints 155 is a number, so line 2 matches its type. There are no errors, and 20 - 5 prints 15.
2.5, -5, and NaN are all numbers, so the errors stay away. "5" in quotes is text, true is a boolean, and 5n is a bigint, so each one gets Type '…' is not assignable to type 'number'. That message means "this kind of value can't be stored where a number has to go". TypeScript checks the kind of value, not whether it's a sensible discount: a negative one is fine.
A type also decides what you can do with a value. Strings have text methods like toUpperCase(), and numbers have number methods like toFixed(), which rounds to a number of decimal places. Mix them up and TypeScript notices:
const title: string = "Green tea";const price: number = 3.5;console.log(title.toUpperCase());console.log(price.toFixed(2));console.log(price.toUpperCase());Line 3 prints GREEN TEA, and line 4 prints 3.50. Line 5 calls toUpperCase() on the price. TypeScript marks it, on purpose here: Property 'toUpperCase' does not exist on type 'number'. In plain words: "a number has no method with that name". When the code runs, line 5 throws a TypeError and stops. The same knowledge drives your editor's suggestions: type price. and it lists only number methods.
null and undefined
JavaScript has two values that mean "nothing here". undefined is what you get when nothing was ever stored, like a variable with no value yet. null is a value you store on purpose to say "empty", like a coupon box the shopper cleared (null and undefined cover both).
In TypeScript, each is its own type, with exactly one value. A string can't hold either of them:
let coupon: string = "SAVE10";coupon = null; let nickname: string = undefined;Line 1 gives coupon the text "SAVE10". Line 2 clears it with null, and TypeScript marks it: Type 'null' is not assignable to type 'string'. Line 4 tries to start nickname as undefined and gets the same error for undefined. Both are on purpose. If they got through, a later coupon.toUpperCase() would throw, because null has no methods.
This check comes from a compiler option called strictNullChecks. It is part of strict, the set of checks most projects turn on, and every example in these lessons uses it. Here is the same code with that one option turned off; the list under the code shows the option:
let coupon: string = "SAVE10";coupon = null; let nickname: string = undefined;console.log(coupon, nickname);Now null and undefined fit into any type, so there are no errors, and line 5 prints null undefined. That's how TypeScript worked before the option existed. Keep it on: without it, a missing value hides inside a string until the page throws.
The types null and undefined themselves look like this:
const coupon = null;// ^?const nickname: undefined = undefined;console.log(typeof coupon, typeof nickname);The query on line 2 shows TypeScript gives coupon the type null. Line 3 annotates nickname as undefined. Line 4 prints object undefined: the old typeof null mistake again. A name that can only ever hold null isn't much use on its own. You'll mostly use these two types together with another one, as in "a string, or null when the coupon is empty". The Union types and null, undefined & strictNullChecks lessons show how.
Lowercase string, not String
TypeScript also has types called String, Number, and Boolean, with capital letters. They look like the same thing. They aren't. String is the type of a wrapper object: an object made with new String("Pune") that holds the text inside it (Methods of primitives explains wrappers).
const city: string = "Pune";const boxedCity: String = new String("Pune");const copy: string = boxedCity;console.log(typeof city, typeof boxedCity);Line 1 is a plain string. Line 2 makes a String object. Line 3 tries to store that object in a string, and TypeScript marks it on purpose with a message in two parts:
Type 'String' is not assignable to type 'string': the object can't go where plain text has to go.'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.TypeScript even tells you which one to write.
Line 4 prints string object: to JavaScript, the wrapper really is an object. The mix-up usually starts the other way round, because TypeScript lets plain text go into a String without complaint:
const label: String = "Order";// ^?console.log(label.toUpperCase());Line 1 annotates label with String, and the query on line 2 confirms that is its type. There are no errors, and line 3 prints ORDER. So code with a capital String seems to work, until you pass label to something that needs a string and get the error from the example before. Write the lowercase types every time:
string is the text itself. String is an object wrapped around the text, which only new String(…) makes. Your values are almost always the plain text, so write string.The table below lists both spellings. Read each row as: this type, what it describes, and whether to write it.
| The type | What it describes | Should you write it? |
|---|---|---|
string | A text value, like "Pune". | Yes. Use it for all text. |
String | A String wrapper object, made with new String("Pune"). | No. Real code almost never makes these objects. |
number and boolean | A number like 3.5, or true or false. | Yes. Use them for all numbers and true-or-false values. |
Number and Boolean | Wrapper objects made with new Number(3.5) or new Boolean(false). | No. Use the lowercase types. |
bigint and symbol
A number keeps whole numbers exact only up to 9007199254740991. Past that, it rounds. A bigint holds a whole number of any size exactly. You write one with an n at the end (BigInt):
const numberViews: number = 9007199254740993;const totalViews: bigint = 9007199254740993n;console.log(numberViews);console.log(totalViews + 1n);Line 1 stores 9007199254740993 in a number. Line 2 stores the same digits in a bigint. Line 3 prints 9007199254740992: the number was rounded to the nearest value it can hold, with no warning. Line 4 adds 1n, another bigint, and prints 9007199254740994n, exactly right.
number to hold 9007199254740993, and it stores 9007199254740992, the nearest number it can hold. Line 2's bigint keeps every digit.number and bigint are different types, and JavaScript refuses to mix them in a sum. TypeScript tells you before it runs:
const totalViews: bigint = 9007199254740993n;const newViews: number = 1;console.log(totalViews + newViews);Line 3 adds newViews, a number, to a bigint. TypeScript marks it on purpose: Operator '+' cannot be applied to types 'bigint' and 'number', which means "+ doesn't work with one of each". When it runs, JavaScript throws TypeError: Cannot mix BigInt and other types. The fix is to turn the number into a bigint first:
const totalViews: bigint = 9007199254740993n;const newViews: number = 1;console.log(totalViews + BigInt(newViews));Line 3 now wraps newViews in BigInt(…), which gives back 1n. Both sides are bigints, the error is gone, and it prints 9007199254740994n.
The table below compares the two number types. Read each row as a question, then each type's answer.
| Question | number | bigint |
|---|---|---|
| Which values does it hold? | Whole numbers and decimals, like 3 and 3.5. | Whole numbers only, written with an n, like 10n. |
| How big can it be and stay exact? | Up to 9007199254740991. Bigger whole numbers can get rounded. | Any size. Every digit is kept. |
Can you add it to the other one with +? | No. TypeScript reports it, and JavaScript throws a TypeError. | No. Turn one into the other first. |
| What turns the other one into this one? | Number(10n) gives 10. | BigInt(10) gives 10n. |
Bigints arrived in JavaScript in 2020, so TypeScript only accepts the n when your project targets a JavaScript version from 2020 or later. The target option says which version your code must run on. This example sets it to 2019:
const totalViews: bigint = 9007199254740993n;console.log(totalViews);Line 1 gets BigInt literals are not available when targeting lower than ES2020. "BigInt literal" means a number written with an n, and ES2020 is the 2020 version of JavaScript. The browser running this page is newer, so it still prints 9007199254740993n, but an older browser couldn't run it. These lessons target ES2022, so bigints work everywhere else on the page.
The last primitive is symbol. Every call to Symbol() makes a brand-new value that equals no other, even with the same description. Symbols are used as hidden property keys, such as a key a library stores on your objects without clashing with your own names (Symbols).
const sessionKey = Symbol("session");// ^?let currentKey = Symbol("session");// ^?console.log(sessionKey === currentKey);console.log(sessionKey.description);The query on line 4 shows let currentKey: symbol: any symbol may go in it later. The query on line 2 shows something new: const sessionKey: typeof sessionKey. Because a const symbol can never be swapped for another, TypeScript gives it a type of its own, which only this one symbol has. (Its name is unique symbol; you'll rarely write it.) Line 5 prints false, because two calls make two different symbols. Line 6 prints session, the description you passed in.
That type of its own lets TypeScript spot a comparison that can never be true:
const cartKey = Symbol("cart");const savedKey = Symbol("cart");console.log(cartKey === savedKey);Lines 1 and 2 make two const symbols with the same description. Line 3 compares them, and TypeScript marks it on purpose: This comparison appears to be unintentional because the types 'typeof cartKey' and 'typeof savedKey' have no overlap. In plain words: "these two can never be equal, so you probably meant something else". It runs and prints false.
Typing constants
Most examples so far wrote their types. You don't have to: TypeScript works out a type from the starting value. With a const, the answer can surprise you:
const minOrder = 50;// ^?let cartTotal = 42;// ^?cartTotal = 58;console.log(cartTotal >= minOrder);Line 1 makes minOrder, the cart total that earns free shipping. The query on line 2 says const minOrder: 50. The type is 50, not number. A const can never change, so TypeScript keeps the exact value. 50 is still a number, so it works anywhere a number goes: line 6 compares it and prints true.
Line 3 makes cartTotal with let, and the query on line 4 says number. A let can change, so TypeScript allows any number later, like the 58 on line 5. Types that are one exact value, like 50, have a lesson of their own, Literal types. For now, read const minOrder: 50 as "a number that is always 50".
minOrder is a const, so it will always hold 50, and its type is 50. cartTotal is a let, so any number may go in later, and its type is number.Each name still only takes what its declaration allows:
const minOrder = 50;let cartTotal = 42; minOrder = 60;cartTotal = "free";Both errors are on purpose:
- Line 4:
Cannot assign to 'minOrder' because it is a constant. "Assign" means "store a new value". Aconstcan't take one, and JavaScript would throw aTypeErrorhere (Variables coversconst). - Line 5:
Type 'string' is not assignable to type 'number'.cartTotalstarted as a number, so its type isnumber, and the text"free"can't go in.
You can still write a type on a constant. Then TypeScript uses the type you wrote instead of the exact value. Switch line 1 between the three ways to declare minOrder and watch the query on line 2:
const minOrder = 50;// ^?minOrder = 60;console.log(minOrder);501 error: line 3stops with a TypeErrorA const can never change, so TypeScript gives minOrder the exact type 50. Line 3 tries to change it anyway: Cannot assign to 'minOrder' because it is a constant. When it runs, JavaScript stops with a TypeError.
const gives 50 and an error on line 3. let gives number and no error. const with : number gives number but keeps the error, because the annotation changes the type, not the fact that it's a constant. Most constants don't need an annotation: const minOrder = 50 already says everything. Write one when you want the wider type, or to make sure the starting value is the kind you meant.
A product card on a shop page
A product card shows a name, a price, a stock badge, and a view count. Here are its values, each with the primitive type it needs, and the text the card shows:
const title: string = "Green tea";const price: number = 3.5;const inStock: boolean = true;const views: bigint = 9007199254740993n; const label = `${title}: $${price.toFixed(2)}`;// ^?const badge = inStock ? "In stock" : "Sold out";console.log(label, badge);console.log(`${views} views`);Lines 1 to 4 hold the product's values: a string, a number, a boolean, and a bigint, because the view count is past what a number holds exactly. Line 6 builds the label with a template literal, and the query on line 7 shows it's a string. TypeScript allowed price.toFixed(2) because price is a number. Line 8 picks a badge from the boolean. Line 9 prints Green tea: $3.50 In stock, and line 10 prints 9007199254740993 views: inside a template literal, a bigint shows without its n.
const title: string = "Green tea";const price: number = "3.5";const inStock: boolean = "yes";const views: bigint = 10;const views: bigint = 10n;const coupon: string = null;const discount: number = NaN;const city: String = "Pune";
Sort each line by what TypeScript says about it. Each line is checked on its own.
Common mix-ups
- "
Stringandstringare the same type." No.stringis text.Stringis a wrapper object made withnew String(…). Write lowercase. - "Decimals need a different type from whole numbers." No.
3and3.5are bothnumber. Only very large whole numbers needbigint. - "
NaNisn't a number, so TypeScript rejects it." No.NaNis a value of thenumbertype, andtypeof NaNis"number". - "
typeof nullis"object", so null's type is an object." No. That's an old JavaScript mistake. TypeScript's type fornullisnull. - "
nullfits in any type." Only withstrictNullChecksoff. With it on, astringcan't holdnullorundefined. - "
const minOrder = 50has the typenumber." No. Its type is50, because aconstnever changes. It still works anywhere a number goes. - "A bigint and a number can be added like any two numbers." No. TypeScript reports it, and JavaScript throws a
TypeError. Convert one withBigInt(…)orNumber(…)first.
Practice exercises
Each exercise is TypeScript. When you fix code, press Edit & run on the starter code: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.
No types are written here. What type does TypeScript give inStock? Guess first, then point at inStock or add a // ^? line under it.
let inStock = false;inStock = true;let inStock = false;// ^?inStock = true;false is a true-or-false value, and a let can change, so TypeScript gives inStock the type boolean. The query on line 2 shows let inStock: boolean, and line 3's true fits.
A sign-up form's values, one with a mistake. Which line will TypeScript mark?
const userName: string = "Asha";
const age: number = 31;
const isMember: boolean = "true";
const city: string = "Pune";const userName: string = "Asha";const age: number = 31;const isMember: boolean = true;const city: string = "Pune";Line 3 gives isMember the text "true", in quotes. TypeScript says Type 'string' is not assignable to type 'boolean'. Without the quotes, true is a boolean and the error is gone.
TypeScript marks line 4. Fix the types so it's happy. What does the fixed code print?
function greet(name: String): String { return "Hi, " + name;}const message: string = greet("Asha");console.log(message);function greet(name: string): string { return "Hi, " + name;}const message: string = greet("Asha");console.log(message);greet said it gives back a String object type, which can't go into message: string. With lowercase string on line 1, the types match, the error is gone, and it prints Hi, Asha.
Line 3 gets an error and would throw. Fix it without changing lines 1 and 2. What does it print?
const totalViews: bigint = 9007199254740993n;const newViews: number = 2;console.log(totalViews + newViews);const totalViews: bigint = 9007199254740993n;const newViews: number = 2;console.log(totalViews + BigInt(newViews));BigInt(newViews) turns 2 into 2n, so both sides of + are bigints. The sum is exact: 9007199254740995n.
What type does TypeScript give maxItems? And itemsInCart? Guess, then add ^? queries to check.
const maxItems = 10;let itemsInCart = 3;itemsInCart = itemsInCart + 1;const maxItems = 10;// ^?let itemsInCart = 3;// ^?itemsInCart = itemsInCart + 1;maxItems is a const, so its type is its exact value, 10. itemsInCart is a let, so its type is number, and the last line can add 1 to it.
What does line 3 print? Then change line 2 so savedKey holds the cart's own symbol and the comparison is true.
const cartKey = Symbol("cart");const savedKey = Symbol("cart");console.log(cartKey === savedKey);const cartKey = Symbol("cart");const savedKey = cartKey;console.log(cartKey === savedKey);The starter prints false: two calls make two different symbols, and TypeScript says they have no overlap. Storing cartKey itself in savedKey makes them the same symbol, so the error is gone and it prints true.
Check your understanding
For each question, keep two things apart: the type TypeScript gives a value, and what JavaScript does with it when the code runs. The quiz shows its code without the checker, so the error list doesn't give the answers away.
Question 1 of 7Which annotation should a user's name, like "Asha", get?
Choose an answer to see the explanation.
Question 2 of 7Does TypeScript accept NaN as a discount, and what prints?
Read the code, then predictconst discount: number = NaN; console.log(typeof discount);Choose an answer to see the explanation.
Question 3 of 7Which line does TypeScript flag in this coupon code?
Read the code, then predictlet coupon: string = "SAVE10"; coupon = null; console.log(coupon);Choose an answer to see the explanation.
Question 4 of 7What type does TypeScript show when you hover limit on line 1?
Read the code, then predictconst limit = 5; let count = 0;Choose an answer to see the explanation.
Question 5 of 7What happens when this code adds 1 to a bigint?
Read the code, then predictconst views: bigint = 10n; console.log(views + 1);Choose an answer to see the explanation.
Question 6 of 7What does typeof print for a coupon that is null?
Read the code, then predictconst coupon = null; console.log(typeof coupon);Choose an answer to see the explanation.
Question 7 of 7How many primitive types does TypeScript have?
Choose an answer to see the explanation.
Key takeaways
- TypeScript has seven primitive types:
string,number,boolean,bigint,symbol,null, andundefined. - One
numbertype covers whole numbers, decimals, andNaN. Abigintis a whole number of any size, written withn, and doesn't mix with numbers. - With
strictNullCheckson,nullandundefinedare types of their own and don't fit in astringornumber. - Write lowercase types.
String,Number, andBooleanare wrapper objects. - A
constgets its exact value as its type, like50; aletgetsnumber. An annotation replaces that with the type you wrote.
Remember the one-liner.
Seven lowercase types, one for each kind of simple value JavaScript has.
Coming next: Type annotations & inference. It shows when to write a type yourself and when to let TypeScript work it out.