Question

String vs string in TypeScript: Primitive vs Wrapper Object

Question

In TypeScript, what is the difference between String and string? At first glance they seem like they should represent the same kind of value, but this code behaves differently:

var a: String = "test";
var b: string = "another test";

a = b;
b = a; // compiler error

The compiler reports:

Type 'String' is not assignable to type 'string'.
  'string' is a primitive, but 'String' is a wrapper object.
  Prefer using 'string' when possible.

Why does this happen? Is it a bug, or is there an important difference between String and string in TypeScript?

Short Answer

By the end of this page, you will understand why string and String are not the same in TypeScript, how primitive values differ from wrapper objects, why the assignment error happens, and why developers almost always use lowercase string in real code.

Concept

In TypeScript, string and String represent related but different things.

  • string is the primitive string type.
  • String is the wrapper object type.

This mirrors JavaScript itself.

Primitive vs wrapper

JavaScript has primitive values such as:

  • string
  • number
  • boolean
  • bigint
  • symbol
  • null
  • undefined

A primitive string is a simple value:

let name: string = "Ada";

JavaScript also has object wrappers like:

  • String
  • Number
  • Boolean

These are object forms created with constructors:

let wrapped: String = new String("Ada");

That value is an object, not a primitive string.

Why this matters

TypeScript treats these differently because they behave differently at runtime.

let a = "hello";
let b = new String("hello");

console.log(typeof a); // "string"
console.log(typeof b); // "object"

Even though both can hold text, they are not the same type of value.

Why a = b works but b = a fails

A primitive string can be used where a String object-like type is expected, because JavaScript temporarily boxes primitives when accessing methods:

let text: string = "hello";
console.log(text.toUpperCase());

This works because JavaScript briefly treats the primitive like a String object behind the scenes.

But a String object is not the same as a primitive string, so TypeScript does not allow assigning it to a string variable.

The rule to remember

In TypeScript, prefer:

  • string
  • number
  • boolean

Avoid using:

  • String
  • Number
  • Boolean

unless you specifically need the wrapper object type, which is rare.

Mental Model

Think of string as the actual text written on a note, and String as a plastic box that can hold that note.

  • string = the text itself
  • String = an object container around the text

If someone asks for a box-like thing, plain text can often still be handled because JavaScript can temporarily wrap it. But if someone asks for plain text, handing over a box is not the same thing.

That is why this feels one-way:

  • string can often act like String
  • String cannot safely act like string

For everyday programming, you usually want the text itself, not the box.

Take Quiz

Syntax and Examples

Core syntax

let primitiveText: string = "hello";
let wrappedText: String = new String("hello");

Recommended usage

Use lowercase string for variables, parameters, return types, and properties.

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

Example: assignment difference

let a: String = "test";
let b: string = "another test";

a = b; // OK
b = a; // Error

Why?

  • b is a primitive string.
  • a is allowed to hold values compatible with the wrapper type.
  • But a might contain an actual String object, which is not a primitive.

Example: runtime difference

let x: string = "abc";
let y: String = new String("abc");

console.log(typeof x); // "string"
console.log(typeof y); // "object"
console.log(x === y);  // false

Even though they look similar, they are different kinds of values.

Best practice

// Good
let title: string = "TypeScript Guide";

// Avoid
let titleObject: String = new String("TypeScript Guide");

Step by Step Execution

Consider this example:

let a: String = "test";
let b: string = "another test";

a = b;
b = a;

Step 1: let a: String = "test";

  • a is declared as type String.
  • The value assigned is a primitive string: "test".
  • TypeScript allows this because primitive strings can be treated like string objects in many situations.

Step 2: let b: string = "another test";

  • b is declared as primitive type string.
  • This is the normal and recommended way to store text.

Step 3: a = b;

  • b contains a primitive string.
  • A primitive string is acceptable for a.
  • So this assignment succeeds.

Step 4: b = a;

  • a is typed as String.
  • A variable of type String could contain a wrapper object such as:
new String("hello")
  • But b expects a primitive string only.
  • Since not every String value is a primitive string, TypeScript rejects the assignment.

A clearer version

let wrapped: String = new String("hello");
let primitive: string = wrapped; // Error

This makes the problem easier to see: an object is not assignable to a primitive.

Real World Use Cases

Where this matters in practice

Function parameters

Most functions should accept primitive strings:

function formatUsername(name: string): string {
  return name.trim().toLowerCase();
}

API data

Data from APIs, forms, JSON, and databases is typically treated as primitive strings.

type User = {
  email: string;
  role: string;
};

Validation

When validating input, you usually check for primitive strings:

function isNonEmpty(value: string): boolean {
  return value.length > 0;
}

Avoiding surprising behavior

Wrapper objects can behave unexpectedly in comparisons and truthiness.

const value = new String("");

if (value) {
  console.log("This runs because objects are truthy");
}

That can be confusing if you expected empty text behavior.

Take Quiz

Real Codebase Usage

In real TypeScript codebases, developers almost always use lowercase primitive types.

Common patterns

Type annotations

type Config = {
  appName: string;
  apiUrl: string;
};

Function contracts

function parseSlug(slug: string): string[] {
  return slug.split("-");
}

Guard clauses

function printMessage(message: string) {
  if (message.length === 0) {
    return;
  }

  console.log(message);
}

Validation and normalization

function normalizeEmail(email: string): string {
  return email.trim().toLowerCase();
}

What you usually do not see

function normalizeEmail(email: String): String {
  return email;
}

This is avoided because wrapper types make typing less precise and can introduce runtime confusion.

Practical rule for codebases

  • Use string in interfaces, types, classes, and function signatures.
  • Avoid new String(...).
  • Avoid annotating values as String unless you truly need wrapper-object behavior.

Common Mistakes

1. Using String instead of string

Broken example:

function greet(name: String): String {
  return "Hello, " + name;
}

Better:

function greet(name: string): string {
  return "Hello, " + name;
}

Why avoid it?

  • String means object wrapper type.
  • It is not the standard choice for text values.

2. Creating wrapper objects with new String()

Broken or confusing example:

const name = new String("Ada");
console.log(typeof name); // object

Better:

const name = "Ada";
console.log(typeof name); // string

3. Assuming equality behaves the same

const a = "hello";
const b = new String("hello");

console.log(a === b); // false

Why? Because one is a primitive and the other is an object.

4. Forgetting that wrapper objects are truthy

const value = new String("");

if (value) {
  console.log("Runs even though the wrapped text is empty");
}

All objects are truthy in JavaScript.

5. Using capitalized primitive wrapper types for other values too

This same rule applies to:

  • number vs Number
  • boolean vs Boolean

Prefer the lowercase primitive types in TypeScript.

Comparisons

ConceptstringString
KindPrimitive typeWrapper object type
Typical usageEveryday text valuesRare, mostly avoided
Runtime typeof"string""object"
Created withString literal like "abc"new String("abc")
Recommended in TypeScriptYesUsually no
Safe for APIs/interfacesYesUsually no
Can cause assignment issuesLess oftenYes

Related comparison: number vs Number

PrimitiveWrapper
numberNumber
booleanBoolean
stringString

The same best practice applies: use the lowercase primitive type unless there is a very specific reason not to.

Take Quiz

Cheat Sheet

Quick rules

  • Use string for normal text values.
  • Avoid String in type annotations.
  • Avoid new String().
  • string is primitive.
  • String is an object wrapper.

Syntax

let a: string = "hello";
let b: String = new String("hello");

Assignment behavior

let x: String = "hi"; // OK
let y: string = x;     // Error if x is typed as String

Runtime difference

typeof "hello"               // "string"
typeof new String("hello")   // "object"

Best practice

function getName(): string {
  return "Ada";
}

Avoid

function getName(): String {
  return new String("Ada");
}

Similar pairs

  • string vs String
  • number vs Number
  • boolean vs Boolean

Rule of thumb

If you are writing TypeScript application code, choose the lowercase primitive type.

FAQ

Is String the same as string in TypeScript?

No. string is a primitive type, while String is the wrapper object type.

Should I ever use String in TypeScript?

Usually no. In normal code, use string. Wrapper object types are rarely needed.

Why does a = b work but b = a fail?

Because a primitive string can be treated like a string object in some contexts, but a String object is not guaranteed to be a primitive string.

Is this a TypeScript bug?

No. It matches JavaScript's runtime behavior and TypeScript's type system.

Why can I call string methods on a primitive?

JavaScript temporarily boxes primitive strings so methods like .toUpperCase() work.

Does this apply to Number and Boolean too?

Yes. Prefer number over Number and boolean over Boolean.

What should I use in interfaces and function parameters?

Use string almost all the time.

Take Quiz

Mini Project

Description

Build a small TypeScript utility that accepts a user name, validates it, and formats a greeting. The goal is to practice using the primitive string type everywhere and to avoid accidental use of the String wrapper object. This reflects how text is handled in real applications such as forms, APIs, and configuration values.

Goal

Create a greeting helper that safely works with primitive strings and demonstrates why string is the correct type choice.

Requirements

  • Create a function that accepts a user name as a primitive string.
  • Trim extra whitespace from the input.
  • Return a default greeting when the trimmed name is empty.
  • Return a formatted greeting when a valid name is provided.
  • Do not use String or new String() anywhere in the solution.
Take Quiz

Keep learning

Related questions

View more