Skip to main content

TS Ep 2: Primitive Types — Deep Dive into JS Primitives

Rachmat Hidayat
Author
Rachmat Hidayat
Learn & sharing insights on TypeScript, Go, Kubernetes, DevOps, DevSecOps, SRE, Platform Engineering, AI/ML Engineering, and MLOps.
typescript - This article is part of a series.
Part 2: This Article
At the base of the TypeScript type system sit the primitive types. While they mirror JavaScript’s primitive values, understanding how TypeScript types them—and avoiding common pitfalls like Object wrapper types—is fundamental to writing clean code.

1. The 7 Primitive Types in JavaScript & TypeScript
#

JavaScript defines 7 primitive values (data that is not an object and has no methods). TypeScript provides corresponding lowercase static types for each:

  1. string
  2. number
  3. boolean
  4. bigint
  5. symbol
  6. undefined
  7. null

2. Core Primitives: string, number, boolean
#

The string Type
#

Represents textual data formatted as single quotes ('), double quotes ("), or template literals (``).

const firstName: string = "Rachmat";
const message: string = `Welcome back, ${firstName}!`;

The number Type
#

Unlike languages with distinct int, float, or double types, JavaScript numbers are all 64-bit double-precision IEEE 754 floating-point numbers. TypeScript’s number type represents all integers, floats, NaN (Not a Number), and Infinity.

const count: number = 42;
const price: number = 99.99;
const invalidResult: number = NaN;
const maxBoundary: number = Infinity;
Warning

Because NaN is technically a value of type number at runtime, TypeScript will permit NaN wherever a number is expected.

The boolean Type
#

Accepts only two literal values: true and false.

const isProduction: boolean = true;
const hasFeatureFlag: boolean = false;

3. The String vs string Trap (Object Wrappers)
#

A very common mistake for developers coming from Java or C# is using capitalized type names like String, Number, or Boolean.

In JavaScript, String is a constructor function for object wrappers (created via new String("hello")), while string is the primitive type.

// ❌ ANTI-PATTERN: Do NOT use uppercase wrapper types!
function printMessage(msg: String) {
  console.log(msg);
}

// 🟢 CORRECT: Always use lowercase primitive types!
function printMessageCorrect(msg: string) {
  console.log(msg);
}

Why String as a Type is Dangerous
#

String represents an instance of the String object wrapper class. While a primitive string can be assigned to String, the reverse is not true:

let primitiveStr: string = "hello";
let objectStr: String = new String("hello");

objectStr = primitiveStr; // Allowed!

// Error: Type 'String' is not assignable to type 'string'.
// 'string' is a primitive, but 'String' is a wrapper object.
// primitiveStr = objectStr; 
TypeDescriptionUsage
stringPrimitive string valueAlways use this
StringObject wrapper instance (new String())Never use as a type annotation

4. null and undefined with strictNullChecks
#

In JavaScript:

  • undefined means a variable has been declared, but no value has been assigned yet.
  • null is an explicit assignment representing the deliberate absence of any object value.

The Impact of strictNullChecks
#

When "strictNullChecks": false is configured in tsconfig.json, null and undefined can be assigned to any type (e.g., let x: number = null is valid). This leads to runtime TypeError: Cannot read properties of undefined crashes.

When "strictNullChecks": true is enabled (the mandatory setting for modern codebases), null and undefined are treated as distinct, unassignable types:

// With strictNullChecks: true

let username: string = "Alice";
// username = null;      // Error: Type 'null' is not assignable to type 'string'.
// username = undefined; // Error: Type 'undefined' is not assignable to type 'string'.

// To allow null or undefined, use Union Types:
let nullableUser: string | null = null;
nullableUser = "Bob"; // Valid!

5. bigint and symbol
#

The bigint Type
#

Introduced in ES2020, bigint represents arbitrary-precision integers larger than $2^{53} - 1$ (Number.MAX_SAFE_INTEGER). BigInt literals end with an n suffix.

const maxSafeInt: number = Number.MAX_SAFE_INTEGER; // 9007199254740991
const hugeNumber: bigint = 9007199254740992n;
const calculatedBigInt: bigint = BigInt(9007199254740992);
Note

You cannot mix bigint and number arithmetic without explicit conversion (e.g., hugeNumber + 5 will throw a compiler error).

The symbol Type
#

Symbols are primitive values created via Symbol() that are guaranteed to be unique and immutable. They are frequently used as unique object property keys.

const apiKey: symbol = Symbol("API_KEY");
const backupKey: symbol = Symbol("API_KEY");

console.log(apiKey === backupKey); // false (always unique!)

const config = {
  [apiKey]: "secret_12345",
};

Code Example: Type Narrowing Primitives
#

When dealing with functions that accept primitive unions, use typeof checks to narrow types safely:

function processInput(input: string | number | null): string {
  if (input === null) {
    return "No input provided.";
  }

  // TypeScript narrows input to 'string | number' here
  if (typeof input === "number") {
    // TypeScript knows input is 'number' inside this block
    return `Formatted Currency: $${input.toFixed(2)}`;
  }

  // TypeScript knows input MUST be 'string' here
  return `Upper Message: ${input.toUpperCase()}`;
}

console.log(processInput(null));       // "No input provided."
console.log(processInput(49.5));       // "Formatted Currency: $49.50"
console.log(processInput("hello"));    // "Upper Message: HELLO"

Summary & Next Steps
#

In this episode:

  • We covered JavaScript’s 7 primitive types: string, number, boolean, bigint, symbol, null, and undefined.
  • We learned why uppercase types like String or Number are object wrapper anti-patterns.
  • We analyzed how strictNullChecks eliminates unhandled null and undefined crashes.
  • We demonstrated primitive type narrowing using typeof.

In Episode 3: Type Annotations vs. Inference, we will discover when you should explicitly annotate variables and when you should let the TypeScript compiler infer them automatically!

typescript - This article is part of a series.
Part 2: This Article