| |

TypeScript 4 ๐Ÿ”ท Basic Types โ€” string, number, boolean, null, undefined

TypeScript’s type system starts with a small set of primitive types: string, number, boolean, null, and undefined. These are the atoms โ€” every more complex type is built by combining them. They map directly to JavaScript’s runtime primitives, but TypeScript gives them a compile-time identity that the runtime doesn’t have. Understanding them properly is not just “know the names” โ€” it’s knowing how they interact, how null and undefined behave under strict mode, how inference picks them, and where the common traps are.

Key point: These five types are the foundation of everything else. Every object type, every union, every generic resolves down to a combination of these. Get them right and the rest of TypeScript is easier. Get them wrong โ€” especially null and undefined โ€” and every subsequent chapter becomes harder than it needs to be.


string

A string is a sequence of characters. At runtime it’s JavaScript’s string primitive; at compile time it’s TypeScript’s string type.

let greeting: string = 'Hello';
let name = 'Alice';              // inferred as string
const literal = 'fixed';          // inferred as 'fixed' (literal type)

greeting = "Single or double";    // both work
greeting = `Template ${name}`;    // template literals are strings too

What string supports:

  • Concatenation โ€” a + b
  • Template literals โ€” `${x}`
  • All String.prototype methods โ€” .toUpperCase(), .slice(), .includes()
  • Comparison โ€” ===, <, >

What it doesn’t do:

  • '1' + 1 is '11' at runtime (string wins). TypeScript doesn’t prevent this.
  • You can’t assign a number to a string variable.
  • String (the object wrapper) is not the same as string (the primitive). Use string.

Important distinction โ€” string vs String:

let a: string = 'hello';          // โœ… primitive
let b: String = 'hello';          // โœ… object wrapper โ€” avoid
let c: string = new String('x');  // โŒ String object, not primitive

Almost always use lowercase string. The capital String is the object wrapper that exists in JavaScript and rarely appears in well-written TypeScript.

Why the lowercase: string refers to the primitive type โ€” the one created by literals like 'hello'. String refers to the constructor function and its object instances. They behave differently at runtime (typeof 'x' is 'string', typeof new String('x') is 'object'). You want the primitive.


number

A number is any numeric value. JavaScript has one numeric type โ€” a 64-bit floating point โ€” so TypeScript has one too.

let count: number = 42;
let price = 19.99;               // inferred as number
let hex = 0xFF;                  // 255
let binary = 0b1010;             // 10
let octal = 0o755;               // 493
let big = 1_000_000;             // underscores for readability
let nan = NaN;                   // number
let inf = Infinity;              // number

Everything numeric is a number: integers, floats, hex, binary, NaN, Infinity. There’s no int or float in TypeScript.

The floating-point caveat: 0.1 + 0.2 !== 0.3. TypeScript doesn’t warn about this โ€” it’s a JavaScript runtime issue, and TypeScript preserves JavaScript semantics. If exact decimal math matters, use a library like decimal.js or big.js.

bigint โ€” when number isn’t enough:

let huge: bigint = 9007199254740991n;
let alsoHuge = 10n;              // bigint literal

bigint is a separate type. It’s not interchangeable with number, and you can’t mix them in arithmetic without an explicit conversion. Use it when you need integers beyond Number.MAX_SAFE_INTEGER โ€” rare in everyday work, essential for crypto, IDs, and finance.

Numeric literal types: Every specific number is also a type.

let five: 5 = 5;                  // only 5 is assignable
let dice: 1 | 2 | 3 | 4 | 5 | 6;  // union of literals

Literal types are covered in a later chapter, but they exist for number and string alike.

Why “everything is a number”: JavaScript’s number type was designed for simplicity โ€” one numeric type, floating-point semantics, no integer/float split. TypeScript preserves that. It means fewer surprises when moving between the two languages, but it also means you don’t get automatic precision guarantees.


boolean

A boolean is true or false. Nothing else. Under strict mode, 0, '', null, and undefined are not booleans โ€” they’re truthy or falsy values, but TypeScript requires an actual boolean.

let isActive: boolean = true;
let isLoading = false;             // inferred as boolean
let literal: true = true;          // literal type, only true

isActive = false;
isActive = 1;                      // โŒ number is not boolean
isActive = 0;                      // โŒ number is not boolean

Truthy/falsy vs boolean: In JavaScript, if (value) runs when value is truthy. In TypeScript with strict mode, that’s still fine inside an if, but you can’t assign a non-boolean where a boolean is expected.

const name = 'Alice';
const hasName: boolean = name;     // โŒ string is not boolean
const hasName: boolean = !!name;   // โœ… double negation coerces
const hasName: boolean = name !== '';  // โœ… explicit comparison

The idiomatic way to produce a boolean from a possibly-falsy value is !! or an explicit comparison. Boolean(value) also works but is noisier.

Why this strictness matters: Treating 0 or '' as booleans hides bugs. A user with age = 0 shouldn’t be treated as “no age.” TypeScript forces you to be explicit โ€” if (user.age !== undefined) rather than if (user.age) โ€” which catches real logic errors.

Why booleans are stricter than JavaScript: JavaScript’s loose truthiness is convenient but imprecise. TypeScript’s boolean type requires an actual boolean, so if (user) where user is an object still works (narrowing handles it), but assigning 0 to a variable typed boolean fails. The rule: use boolean when you mean a boolean, not “truthy.”


null and undefined

These are the two “absence” types. They’re separate types in TypeScript โ€” and under strict mode, they’re not assignable to anything except their own type or unknown/any.

let a: null = null;
let b: undefined = undefined;

The fundamental question: What’s the difference?

  • undefined โ€” “not yet assigned” โ€” a variable that hasn’t been set, a missing property, a function that doesn’t return anything, a missing function argument
  • null โ€” “intentionally empty” โ€” a value that’s deliberately set to nothing, like a database query result that found nothing, or a nullable field that was cleared

In practice, JavaScript and TypeScript blur these constantly. Many codebases use null for “explicitly absent” and undefined for “not provided.” Some use only undefined. The key is consistency โ€” the compiler will track either way.

Under strictNullChecks (part of strict: true):

let name: string = 'Alice';
name = null;                      // โŒ null is not assignable to string
name = undefined;                 // โŒ undefined is not assignable to string

let maybeName: string | null = 'Alice';
maybeName = null;                 // โœ…

This is the single most important behavior in modern TypeScript. Before strict null checks (and still, if you turn it off), null and undefined were assignable to everything โ€” leading to the runtime classic: Cannot read property 'x' of null.

Why strictNullChecks matters: Without it, every value might be null, and you can’t know. With it, TypeScript tracks which variables might be null and forces you to handle the null case before using them. It’s the biggest single improvement in type safety that TypeScript offers.

Why strict null checks exist: Tony Hoare called null the “billion-dollar mistake” โ€” the source of countless crashes. TypeScript’s response is to make nullability explicit in the type system. If a value can be null, the type says so. If it can’t, using it is safe. That distinction catches crashes at compile time.


Optional properties, parameters, and returns

undefined shows up in three specific places: optional object properties, optional function parameters, and functions that don’t return a value.

Optional properties โ€” ?:

interface User {
  id: number;
  nickname?: string;              // string | undefined
}

const u1: User = { id: 1 };
const u2: User = { id: 2, nickname: 'Al' };

u1.nickname;                      // string | undefined
u1.nickname.length;               // โŒ possibly undefined
u1.nickname?.length;              // โœ…

The ? adds undefined to the type. A property with ? may be absent โ€” accessing it gives string | undefined.

Optional parameters โ€” ?:

function greet(name: string, title?: string): string {
  return title ? `${title} ${name}` : name;
}

greet('Alice');                   // โœ…
greet('Alice', 'Dr.');            // โœ…
greet('Alice', undefined);        // โœ… explicit undefined

Optional parameters must come after required ones. title is string | undefined inside the function.

Optional return โ€” implicit undefined:

function log(message: string): void {
  console.log(message);
  // returns undefined at runtime, but declared as void
}

void means “this function doesn’t return a useful value.” At runtime, it returns undefined, but the type is void โ€” you can’t use the result.

void vs undefined:

function a(): void { }             // doesn't return a value
function b(): undefined { }        // returns undefined explicitly
function c(): undefined { return undefined; }  // must return undefined

Use void for functions whose return value is meaningless. Use undefined (rarely) when a function must literally return undefined.

Why void is different from undefined: A void function is one where the caller shouldn’t use the return value. undefined is a real value. The distinction matters in callbacks and generics: Array<number>.forEach(fn) expects fn to return void, but a function that returns a value is still assignable (the result is just ignored). TypeScript handles this specifically for void.


Type inference for basics

TypeScript infers types from initial values. You don’t annotate every variable.

let s = 'hello';                  // string
let n = 42;                       // number
let b = true;                     // boolean
let nl = null;                    // null (widened)
let ud = undefined;               // undefined (widened)

let vs const widening:

let a = 'hello';                  // string
const b = 'hello';                // 'hello' (literal)

const produces a literal type โ€” the exact value. let widens to the general type. That distinction matters for discriminated unions and object shapes.

When to annotate:

SituationAnnotate?
Simple initializerโŒ Let inference work
Function parametersโœ… Always
Function returnsโš ๏ธ Sometimes โ€” for public APIs
Empty variableโœ… Can’t infer from nothing
Complex expressionsโš ๏ธ If inference is unclear

Example with no annotation:

const user = { name: 'Alice', age: 30 };
// Inferred: { name: string; age: number }

TypeScript figures out the shape. You don’t need an interface unless you plan to reuse the shape or want to constrain it.

Empty variable โ€” needs annotation:

let result;                       // any
result = 5;                       // still any

let typed: number;                // number, uninitialized
typed = 5;                        // โœ…

let result; without an initializer is any โ€” avoid it. Annotate empty variables.

Why inference matters: Writing types for everything is noise. TypeScript’s whole ergonomic advantage is that you annotate where it matters (boundaries, public APIs) and let inference handle the rest. If you’re annotating every local variable, you’re fighting the language.


null vs undefined in practice

Both represent absence. Choosing between them is a style decision โ€” but it should be a consistent one.

Common conventions:

Stylenull meansundefined means
Explicit absenceValue is deliberately emptyValue was never set
Undefined-onlyNot usedEverything absent
Null-onlyEverything absentNot used

Undefined-only (modern preference):

interface User {
  id: number;
  nickname?: string;              // string | undefined
}

function findUser(id: number): User | undefined {
  return users.find(u => u.id === id);  // undefined if not found
}

The reason: undefined is the default for missing properties and missing arguments, so relying on it exclusively keeps types simpler. null only appears when an external system (JSON APIs, databases) sends it.

Null-and-undefined explicitly:

type Maybe<T> = T | null | undefined;

This is often a signal that nullability is getting complicated. Prefer one or the other.

?? โ€” nullish coalescing:

const display = name ?? 'Anonymous';
// Uses 'Anonymous' if name is null OR undefined

?? only triggers for null and undefined. Unlike ||, it doesn’t treat 0, '', or false as “missing.”

const count = 0;
const a = count || 10;             // 10 (0 is falsy)
const b = count ?? 10;             // 0 (0 is not null/undefined)

?. โ€” optional chaining:

const city = user?.address?.city;
// undefined if user or address is null/undefined

?. short-circuits when the left side is null or undefined. It’s the safe way to access nested properties.

! โ€” non-null assertion (use carefully):

const el = document.getElementById('app')!;
el.innerHTML = 'Hello';           // โœ… compiles โ€” trusts you

The ! tells TypeScript “trust me, this isn’t null.” If you’re wrong, you get a runtime crash. Use it sparingly โ€” only when you have information the compiler doesn’t.

Why ?? and ?. exist: They’re the modern way to handle nullability. || and && are too aggressive โ€” || treats 0 and '' as missing. ?? and ?. are precise. Together with strict null checks, they make null-handling clean and safe.


A full example

A small user model that uses all the basic types.

interface User {
  id: number;
  name: string;
  isActive: boolean;
  nickname?: string;              // optional โ€” string | undefined
  deletedAt: Date | null;         // explicit null if deleted
}

function displayName(user: User): string {
  return user.nickname ?? user.name;
}

function isVisible(user: User): boolean {
  return user.isActive && user.deletedAt === null;
}

const alice: User = {
  id: 1,
  name: 'Alice',
  isActive: true,
  deletedAt: null
};

const bob: User = {
  id: 2,
  name: 'Bob',
  isActive: false,
  nickname: 'Bobby',
  deletedAt: new Date()
};

console.log(displayName(alice));    // Alice
console.log(displayName(bob));      // Bobby
console.log(isVisible(alice));      // true
console.log(isVisible(bob));        // false

Every primitive is exercised: number for IDs, string for names, boolean for status, optional nickname, and null for deletedAt. The functions handle absence with ?? and explicit === null checks.

What the compiler enforces:

  • id must be a number
  • name must be a string
  • isActive must be a boolean
  • nickname may be missing or a string
  • deletedAt must be present and either a Date or null โ€” not undefined

Try omitting deletedAt and TypeScript rejects it โ€” the property isn’t optional.

Why this shape: It models a real domain. Users have IDs, names, status flags, and sometimes a deletion date. Every primitive type maps to a real concept. That’s the whole point โ€” types describe your domain, not abstract mathematics.


Complete Example Session

# ============================================
# PART 1: CREATE A FILE
# ============================================

cat > basics.ts << 'EOF'
// ============================================
// STRINGS
// ============================================

let greeting: string = 'Hello';
let name = 'Alice';
const fixed = 'constant string';

// ============================================
// NUMBERS
// ============================================

let count: number = 42;
let price = 19.99;
let hex = 0xFF;
let big = 1_000_000;
let nan = NaN;

// ============================================
// BOOLEANS
// ============================================

let isActive: boolean = true;
let isLoading = false;

// ============================================
// NULL AND UNDEFINED
// ============================================

let nothing: null = null;
let missing: undefined = undefined;

// ============================================
// UNION WITH NULL
// ============================================

let maybeName: string | null = 'Alice';
maybeName = null;

// ============================================
// OPTIONAL PROPERTIES
// ============================================

interface User {
  id: number;
  name: string;
  isActive: boolean;
  nickname?: string;
  deletedAt: Date | null;
}

const alice: User = {
  id: 1,
  name: 'Alice',
  isActive: true,
  deletedAt: null
};

// ============================================
// NULL-HANDLING OPERATORS
// ============================================

const display = alice.nickname ?? alice.name;
const city = (alice as any).address?.city;

console.log(greeting, name, count, isActive, display, city);
EOF

# ============================================
# PART 2: TYPE-CHECK
# ============================================

npx tsc --noEmit basics.ts
# (no output โ€” no errors)

# ============================================
# PART 3: TRIGGER ERRORS
# ============================================

cat > errors.ts << 'EOF'
let s: string = 42;               // โŒ number not assignable to string
let n: number = 'hello';          // โŒ string not assignable to number
let b: boolean = 1;               // โŒ number not assignable to boolean

let name: string = 'Alice';
name = null;                      // โŒ null not assignable under strict

interface User { id: number; }
const u: User = {};               // โŒ missing id
EOF

npx tsc --noEmit errors.ts
# [ errors.ts:1:5  - Type 'number' is not assignable to type 'string'. ]
# [ errors.ts:2:5  - Type 'string' is not assignable to type 'number'. ]
# [ errors.ts:3:5  - Type 'number' is not assignable to type 'boolean'. ]
# [ errors.ts:6:1  - Type 'null' is not assignable to type 'string'. ]
# [ errors.ts:9:7  - Property 'id' is missing. ]

rm errors.ts

# ============================================
# PART 4: COMPILE AND RUN
# ============================================

npx tsc basics.ts
node basics.js
# [ Hello Alice 42 true Alice undefined ]

Quick Reference

The Five Primitives

TypeExampleNotes
string'hello'Use lowercase, not String
number42, 3.14One numeric type โ€” no int/float
booleantrue, falseUnder strict, no truthy coercion
nullnull“Intentionally empty”
undefinedundefined“Not yet set”

Additional Numeric Type

TypeExampleUse when
bigint10nBeyond Number.MAX_SAFE_INTEGER

Absence in Types

SyntaxType
string | nullString or null
string | undefinedString or undefined
nickname?: stringstring | undefined
voidNo meaningful return
neverNever returns (throws)

Operators

OperatorPurpose
??Nullish coalescing
?.Optional chaining
!Non-null assertion
!!Coerce to boolean
=== nullExplicit null check

void vs undefined

Return typeMeaning
voidDoesn’t return useful value
undefinedMust return undefined

Inference

DeclarationInferred type
let x = 'a'string
const x = 'a''a' (literal)
let x = 5number
let x = trueboolean
let x;any (avoid)

Strict Null Checks

BehaviorWith strictWithout strict
null assignable to stringโŒโœ…
undefined assignable to stringโŒโœ…
Optional props| undefinedOften | undefined
Null safetyEnforcedNot enforced

Common String Methods (typed)

MethodReturns
.toUpperCase()string
.slice(n)string
.includes(x)boolean
.split(sep)string[]
.lengthnumber

Common Number Methods (typed)

MethodReturns
.toFixed(n)string
.toString()string
Number.isInteger(n)boolean
parseInt(s)number
parseFloat(s)number

Best Practices

โœ… Do This:

// Let inference do its job
let count = 42;                                         // โœ… number

// Use lowercase primitives
let name: string = 'Alice';                             // โœ…

// Enable strict null checks
"strict": true                                          // โœ…

// Handle nullability with ?. and ??
const display = user.nickname ?? user.name;             // โœ…

// Use explicit comparisons for booleans
if (name !== '') { ... }                                // โœ…

// Coerce truthy values to boolean with !!
const hasName = !!name;                                 // โœ…

// Use void for no-return functions
function log(msg: string): void { console.log(msg); }   // โœ…

// Annotate empty variables
let result: number;                                     // โœ…

// Use bigint for huge numbers
let id: bigint = 9007199254740993n;                     // โœ…

โŒ Don’t Do This:

// Don't use String, Number, Boolean wrappers
let name: String = 'Alice';                             // โš ๏ธ  object wrapper

// Don't trust truthy checks for numbers
if (count) { ... }    // 0 is falsy                  // โš ๏ธ  explicit !== undefined

// Don't use `||` for defaults
const n = count || 10;                                  // โš ๏ธ  use ?? for null only

// Don't overuse non-null assertions
const el = document.getElementById('x')!;               // โš ๏ธ  can crash

// Don't assign null to non-nullable types
let s: string = null;                                   // โŒ

// Don't leave variables untyped
let x;                                                  // โš ๏ธ  implicit any

// Don't mix null and undefined randomly
interface U { a?: string; b: string | null; }           // โš ๏ธ  pick a convention

// Don't use `any` to silence null errors
const v = (x as any).foo;                               // โŒ

Common Pitfalls

PitfallProblemSolution
String vs stringObject wrapper confusionUse lowercase
Loose truthy checks0 and '' treated as missingExplicit !== undefined
|| for defaults0 becomes fallbackUse ??
Forgetting ?.Runtime null crashOptional chaining
Overusing !Hidden runtime errorsNarrow properly
NaN comparisonsNaN !== NaNUse Number.isNaN()
Float precision0.1 + 0.2 !== 0.3Use a decimal library
Missing strictNullChecksNull crashesEnable strict: true
Uninitialized letanyAnnotate
undefined vs null mixesInconsistent APIPick a convention

Real-World Examples

1. A typed string

const name: string = 'Alice';

2. A typed number

const age: number = 30;

3. A typed boolean

const isActive: boolean = true;

4. Nullable string

let nickname: string | null = null;

5. Optional property

interface User { id: number; nickname?: string; }

6. Void function

function log(msg: string): void { console.log(msg); }

7. Nullish coalescing

const display = user.nickname ?? user.name;

8. Optional chaining

const city = user?.address?.city;

9. Non-null assertion

const el = document.getElementById('app')!;

10. Boolean coercion

const hasName = !!user.name;

11. Explicit null check

if (user.deletedAt === null) { ... }

12. Strict comparison against undefined

if (value !== undefined) { ... }

13. BigInt

const huge: bigint = 9007199254740993n;

14. Literal type

type Direction = 'north' | 'south' | 'east' | 'west';

15. Union with undefined

function find(id: number): User | undefined { ... }

16. Template literal

const msg = `Hello, ${name}!`;

17. Number formatting

const formatted = price.toFixed(2);

18. Safe number parsing

const parsed = Number.parseInt('42', 10);

19. Safe number check

if (Number.isInteger(value)) { ... }

20. Consistent absence

interface Config {
  apiUrl?: string;     // maybe missing
  timeout: number | null;  // explicit null when disabled
}

Visual: The Primitive Types

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  string                                      โ”‚
โ”‚                                              โ”‚
โ”‚  'hello'   "world"   `template ${x}`         โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  number                                      โ”‚
โ”‚                                              โ”‚
โ”‚  42   3.14   0xFF   0b1010   1_000_000       โ”‚
โ”‚                                              โ”‚
โ”‚  (also NaN, Infinity)                        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  boolean                                     โ”‚
โ”‚                                              โ”‚
โ”‚  true   false                                โ”‚
โ”‚                                              โ”‚
โ”‚  (strict โ€” no truthy coercion)               โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  null / undefined                            โ”‚
โ”‚                                              โ”‚
โ”‚  null       โ€” intentionally empty            โ”‚
โ”‚  undefined  โ€” not yet set                    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  bigint                                      โ”‚
โ”‚                                              โ”‚
โ”‚  10n   9007199254740993n                     โ”‚
โ”‚                                              โ”‚
โ”‚  (separate from number)                      โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Strict Null Checks

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  strictNullChecks: false                     โ”‚
โ”‚                                              โ”‚
โ”‚  let name: string = 'Alice';                 โ”‚
โ”‚  name = null;              โœ… allowed        โ”‚
โ”‚  name = undefined;         โœ… allowed        โ”‚
โ”‚                                              โ”‚
โ”‚  (null crashes hide until runtime)           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  strictNullChecks: true                      โ”‚
โ”‚                                              โ”‚
โ”‚  let name: string = 'Alice';                 โ”‚
โ”‚  name = null;              โŒ rejected       โ”‚
โ”‚  name = undefined;         โŒ rejected       โ”‚
โ”‚                                              โ”‚
โ”‚  let maybe: string | null = 'Alice';         โ”‚
โ”‚  maybe = null;             โœ… allowed        โ”‚
โ”‚                                              โ”‚
โ”‚  (null crashes caught at compile time)       โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Null Handling Operators

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ?? โ€” nullish coalescing                     โ”‚
โ”‚                                              โ”‚
โ”‚  a ?? b                                      โ”‚
โ”‚                                              โ”‚
โ”‚  Uses b only if a is null or undefined       โ”‚
โ”‚  Does NOT trigger on 0, '', false            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ?. โ€” optional chaining                      โ”‚
โ”‚                                              โ”‚
โ”‚  user?.address?.city                         โ”‚
โ”‚                                              โ”‚
โ”‚  Short-circuits to undefined if any link     โ”‚
โ”‚  is null or undefined                        โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ! โ€” non-null assertion                      โ”‚
โ”‚                                              โ”‚
โ”‚  el!                                         โ”‚
โ”‚                                              โ”‚
โ”‚  "Trust me, this isn't null"                 โ”‚
โ”‚  Compiles away โ€” no runtime check            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Type Inference

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  let x = 'hello';        โ†’  string           โ”‚
โ”‚  const y = 'hello';      โ†’  'hello'          โ”‚
โ”‚                                              โ”‚
โ”‚  let n = 42;             โ†’  number           โ”‚
โ”‚  const m = 42;           โ†’  42               โ”‚
โ”‚                                              โ”‚
โ”‚  let b = true;           โ†’  boolean          โ”‚
โ”‚  const c = true;         โ†’  true             โ”‚
โ”‚                                              โ”‚
โ”‚  let empty;              โ†’  any (avoid)      โ”‚
โ”‚  let typed: number;      โ†’  number           โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: void vs undefined vs never

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  void                                        โ”‚
โ”‚                                              โ”‚
โ”‚  function log(): void { }                    โ”‚
โ”‚  โ†’ no useful return value                    โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  undefined                                   โ”‚
โ”‚                                              โ”‚
โ”‚  function f(): undefined { return undefined; }โ”‚
โ”‚  โ†’ must return undefined explicitly          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  never                                       โ”‚
โ”‚                                              โ”‚
โ”‚  function fail(): never { throw new Error(); }โ”‚
โ”‚  โ†’ never returns โ€” code after is unreachable โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Visual: Optional Property

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  interface User {                            โ”‚
โ”‚    id: number;                               โ”‚
โ”‚    nickname?: string;                        โ”‚
โ”‚  }                                           โ”‚
โ”‚                                              โ”‚
โ”‚  โ†’ id: number                                โ”‚
โ”‚  โ†’ nickname: string | undefined              โ”‚
โ”‚                                              โ”‚
โ”‚  { id: 1 }              โœ…                   โ”‚
โ”‚  { id: 1, nickname: 'Al'} โœ…                 โ”‚
โ”‚  { nickname: 'Al' }     โŒ missing id        โ”‚
โ”‚  { id: 1, nickname: null} โŒ null not string โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

TypeRepresentsUnder strict, assignable to
stringTextstring only
numberAny numeric valuenumber only
booleantrue / falseboolean only
nullIntentional absencenull only
undefinedNot-yet-set absenceundefined only
voidNo meaningful returnFunction returns only
bigintArbitrary-precision integerbigint only

Key takeaways:

  • TypeScript’s primitives mirror JavaScript’s โ€” string, number, boolean, null, undefined โ€” plus bigint
  • Use lowercase names โ€” string, not String; number, not Number
  • number covers all numeric values โ€” integers, floats, hex, binary, NaN, Infinity
  • boolean under strict mode rejects truthy/falsy values โ€” use !! or explicit comparisons
  • null means “intentionally empty”; undefined means “not yet set” โ€” pick a convention and stick to it
  • With strict: true (especially strictNullChecks), null and undefined are not assignable to other types
  • ?? and ?. handle nullability precisely โ€” ?? only triggers on null/undefined, ?. short-circuits
  • ! asserts non-null โ€” use sparingly
  • void means “no useful return value”; it’s different from returning undefined
  • Inference handles most variables โ€” let widens, const produces literal types
  • Optional properties (nickname?: string) add undefined to the type
  • Annotate empty variables โ€” let x; is any

Remember: These five primitives โ€” plus bigint โ€” are the atoms of every TypeScript type. Get null and undefined right, and strict null checks become a superpower instead of a nuisance. Use ?? and ?. for safe access, annotate where inference can’t help, and lean on strict: true to catch the bugs that JavaScript hides until runtime. Everything else in TypeScript โ€” objects, arrays, generics, unions โ€” is built by combining these atoms.


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!