Skip to main content

TypeScript Interfaces vs Types in React: When to Use Each

Both interface and type can describe object shapes in TypeScript, but they have different strengths. Interfaces are better for describing contracts (like React component props), while types are better for unions and single values. Understanding when to reach for each tool makes your React code more maintainable and easier for teammates to read.

What Is a TypeScript Interface?

An interface is a contract that describes the shape of an object. It declares that an object must have certain properties with certain types. Interfaces are structural—any object with the right properties satisfies an interface, even if it wasn't explicitly declared to implement it.

Here is a basic interface:

interface User {
id: number;
name: string;
email: string;
}

// This object satisfies the User interface
const user: User = {
id: 1,
name: "Alice",
email: "[email protected]",
};

// This also works—same shape, not declared as User
const anotherUser = {
id: 2,
name: "Bob",
email: "[email protected]",
};

const assignedUser: User = anotherUser; // TypeScript accepts it (structural typing)

Interfaces support extending other interfaces (inheritance), making them ideal for building hierarchies:

interface Entity {
id: number;
createdAt: Date;
}

interface User extends Entity {
name: string;
email: string;
}

const user: User = {
id: 1,
name: "Alice",
email: "[email protected]",
createdAt: new Date(),
};

What Is a TypeScript Type?

A type alias is a name for any type—primitives, unions, tuples, intersections, or object shapes. Types are more flexible than interfaces because they can represent anything, not just object shapes. You use type for unions and single values:

// Union type
type Status = "idle" | "loading" | "success" | "error";

// Single value type (primitive)
type Age = number;

// Object type (works like interface)
type User = {
id: number;
name: string;
email: string;
};

// Intersection of two types
type WithTimestamp = {
createdAt: Date;
updatedAt: Date;
};

type UserWithTimestamp = User & WithTimestamp;

const user: UserWithTimestamp = {
id: 1,
name: "Alice",
email: "[email protected]",
createdAt: new Date(),
updatedAt: new Date(),
};

How Do Interfaces and Types Differ?

Both can describe objects, but they have subtly different capabilities. Here is a comparison:

AspectInterfaceType
Object shapeYesYes
Union typesNoYes
PrimitivesNoYes
TuplesNoYes
IntersectionNo (but extends)Yes (&)
MergeYes (declaration merging)No
Extendsextends keywordNo

The most practical difference: interfaces are for contracts (component props, class shapes), while types are for unions and single values. In React, you typically use interfaces for props and types for state and utility types.

When Should You Use Interfaces in React?

Use interfaces for component props, class methods, and any contract describing the shape of an object that will be implemented or extended. Interfaces make intent clear—"this is a contract that objects must satisfy."

// Component props: always use interface
interface ButtonProps {
label: string;
onClick: () => void;
disabled?: boolean;
}

function Button(props: ButtonProps) {
return <button disabled={props.disabled} onClick={props.onClick}>{props.label}</button>;
}

// Extending props with more specific button types
interface PrimaryButtonProps extends ButtonProps {
variant: "primary";
}

interface SecondaryButtonProps extends ButtonProps {
variant: "secondary";
}

function PrimaryButton(props: PrimaryButtonProps) {
return <Button {...props} />;
}

Interfaces shine when you need inheritance hierarchies—extending one interface with another is cleaner than intersecting types. Most React teams standardize on interfaces for props.

When Should You Use Types in React?

Use types for unions (like state status), single values, and when you need intersection. Types are more flexible, so they handle edge cases interfaces cannot:

// Union type for state management
type LoadingState = "idle" | "loading" | "success" | "error";

// Single value type
type UserId = string & { readonly __brand: "UserId" };

// Intersection for combining shapes
type Response = {
status: number;
data: unknown;
};

type SuccessResponse = Response & {
status: 200;
data: string;
};

// Conditional type (advanced)
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number

In React, use types for state enums, return types of hooks, and discriminated unions that handle multiple state shapes.

How Do You Combine Interfaces and Types?

You can use both in the same codebase. A common pattern is to use interfaces for props and types for the internal state shape:

// Interface for component contract (props)
interface TodoListProps {
title: string;
}

// Type for internal state
type TodoItem = {
id: string;
text: string;
completed: boolean;
};

type TodoListState = {
items: TodoItem[];
filter: "all" | "active" | "completed";
};

function TodoList({ title }: TodoListProps) {
const [state, setState] = useState<TodoListState>({
items: [],
filter: "all",
});

const completedCount = state.items.filter((item) => item.completed).length;

return (
<div>
<h1>{title}</h1>
<p>Completed: {completedCount}</p>
{state.items.map((item) => (
<div key={item.id} style={{ textDecoration: item.completed ? "line-through" : "none" }}>
{item.text}
</div>
))}
</div>
);
}

This pattern keeps props contracts explicit (interfaces) while leaving internal state flexible (types).

What Is Intersection and How Does It Compare to Extension?

Intersection (&) combines two types into one. Extension (extends on interfaces) achieves similar goals but has different rules. Interfaces extend cleanly; types intersect and may cause conflicts:

// Using interface extension
interface Entity {
id: number;
}

interface User extends Entity {
name: string;
}

// Using type intersection
type EntityType = {
id: number;
};

type UserType = EntityType & {
name: string;
};

// Both work, but extension is cleaner for objects
const user: User = { id: 1, name: "Alice" };
const userType: UserType = { id: 1, name: "Alice" };

For React, prefer extending interfaces when possible. Intersection works but is noisier to read. Reserve intersection for the cases interfaces cannot handle (unions, complex combinations).

How Do You Use Declaration Merging with Interfaces?

Interfaces support declaration merging—declaring the same interface twice merges their properties. This is powerful for extending global types but should be used sparingly in component code:

interface Window {
customProperty: string;
}

// Later, in another file
interface Window {
anotherProperty: number;
}

// TypeScript merges them
const w: Window = {
customProperty: "hello",
anotherProperty: 42,
// ... all original Window properties
};

Declaration merging is rarely needed in React component code but is useful for augmenting global types (like window or document).

Key Takeaways

  • Use interface for component props and class contracts; use type for unions, primitives, and intersections.
  • Interfaces support extending other interfaces (extends); types support intersections (&).
  • Prefer interfaces for object shapes in React; prefer types for state enums and discriminated unions.
  • Declaration merging works only with interfaces, not types; this is rarely needed in modern React.
  • Mixing interfaces and types in the same codebase is fine; choose based on intent (contract vs. flexible combination).

Frequently Asked Questions

Should I use interface or type for all my props?

Use interface for component props. It makes intent clear and supports extension if you need to create specialized prop types (e.g., PrimaryButtonProps extends ButtonProps). Reserve type for state, return values, and utility types.

Can I convert an interface to a type?

Yes, they are almost identical for object shapes. If you have an interface and realize you need a union elsewhere, convert it to a type with curly braces:

// Before
interface User {
id: number;
name: string;
}

// After (allows union)
type User = {
id: number;
name: string;
} | null;

What is a brand type and when should I use it?

A brand type is a type-level tag that prevents you from passing the wrong value even though it is structurally identical. It is an advanced technique:

type UserId = string & { readonly __brand: "UserId" };
type PostId = string & { readonly __brand: "PostId" };

const userId: UserId = "user-123" as UserId;
// ERROR: PostId is not assignable to UserId (different brands)
const id: UserId = "post-456" as PostId;

Use brand types for IDs and sensitive values where structural typing could cause bugs. Avoid them until you need them.

Is it okay to use both in my props?

Yes. You can define props with interface and state with type or vice versa. The codebase is easier to read if you use them for their intended purposes consistently.

Further Reading