Question

How to Extend Types in TypeScript

typescriptjavascript

Question

I have the following TypeScript type:

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

I want to create a new type based on it by adding another property, for example:

type UserEvent extends Event = {
  userId: string;
};

This does not work. How can I correctly extend a type in TypeScript?

Short Answer

By the end of this page, you will understand how to build new types from existing ones in TypeScript. You will learn the correct way to extend object shapes using interface extends and type intersections with &, when to use each approach, and how this pattern appears in real codebases.

Concept

In TypeScript, type aliases and interface declarations are similar, but they are not extended in exactly the same way.

If you define an object shape with type, you cannot write type NewType extends OldType = .... That syntax is not valid TypeScript.

To create a new type from an existing type, the usual approach is to use an intersection type with &:

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type UserEvent = Event & {
  userId: string;
};

This means: take everything from Event and combine it with userId.

Another option is to use interface, which does support extends directly:

interface Event {
  name: string;
  dateCreated: string;
  type: string;
}

interface UserEvent extends Event {
  userId: string;
}

Why this matters:

  • Real applications often build larger types from smaller reusable ones.
  • API responses often share common fields like id, createdAt, or status.
  • Reusing types avoids duplication and reduces bugs.
  • It makes your code easier to maintain when data models change.

A good rule is:

  • Use interface extends when you are working with interfaces.
  • Use & when you are combining type aliases.

Mental Model

Think of a TypeScript object type like a checklist of required fields.

Event says:

  • must have name
  • must have dateCreated
  • must have type

Now imagine you want a stricter checklist for a user-specific event. You do not throw away the original checklist. You stack another checklist on top of it.

So UserEvent means:

  • everything required by Event
  • plus userId

Using & is like merging two checklists together.

Using interface extends is like saying: “Start with this checklist, then add one more rule.”

Take Quiz

Syntax and Examples

Extending a type with &

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type UserEvent = Event & {
  userId: string;
};

UserEvent now requires all four properties.

const loginEvent: UserEvent = {
  name: "User Login",
  dateCreated: "2026-05-04",
  type: "auth",
  userId: "u123"
};

Using interface extends

interface Event {
  name: string;
  dateCreated: string;
  type: string;
}

interface UserEvent extends Event {
  userId: string;
}

This produces a very similar result.

Adding multiple properties

type AdminEvent = Event & {
  userId: string;
  permissionLevel: string;
};

Reusing shared pieces

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type WithUser = {
  userId: string;
};

type UserEvent = Event & WithUser;

This is useful when multiple types share the same extra fields.

Step by Step Execution

Consider this example:

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type UserEvent = Event & {
  userId: string;
};

const event: UserEvent = {
  name: "Purchase",
  dateCreated: "2026-05-04",
  type: "order",
  userId: "user-42"
};

Here is what happens step by step:

  1. Event is defined as an object type with 3 required string properties.
  2. UserEvent is created by combining:
    • all properties from Event
    • one extra property: userId
  3. When TypeScript checks the event object, it expects:
    • name
    • dateCreated
    • type
    • userId
  4. The object contains all required properties, so it is valid.

If you leave one out:

const badEvent: UserEvent = {
  name: "Purchase",
  dateCreated: "2026-05-04",
  type: "order"
};

TypeScript reports an error because userId is missing.

If you add the wrong type:

const badEvent2: UserEvent = {
  name: "Purchase",
  dateCreated: "2026-05-04",
  type: "order",
  userId: 123
};

TypeScript reports an error because userId must be a string, not a number.

Real World Use Cases

This pattern is very common in real applications.

API models

A base response type may contain common fields:

type BaseRecord = {
  id: string;
  createdAt: string;
};

type User = BaseRecord & {
  email: string;
};

type Product = BaseRecord & {
  price: number;
};

Event tracking

Analytics systems often have shared event fields plus event-specific fields.

type BaseEvent = {
  timestamp: string;
  eventType: string;
};

type ClickEvent = BaseEvent & {
  elementId: string;
};

Backend validation models

You may define a base entity and extend it for different use cases such as create, update, or response types.

UI state objects

A component may reuse a base data shape and add local UI properties like loading state or error messages.

Take Quiz

Real Codebase Usage

In real projects, developers usually combine small reusable types instead of repeating full object definitions.

Common patterns

Base domain models

type BaseEntity = {
  id: string;
  createdAt: string;
  updatedAt: string;
};

type Post = BaseEntity & {
  title: string;
  body: string;
};

Error handling

type ApiError = {
  message: string;
  code: string;
};

type FieldError = ApiError & {
  field: string;
};

Configuration objects

type BaseConfig = {
  env: string;
};

type DbConfig = BaseConfig & {
  connectionString: string;
};

Guarding and validating data

Developers often define a broad base type, then create more specific versions for validated data.

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type ValidatedEvent = Event & {
  id: string;
};

This helps express the difference between raw input and processed data.

Choosing between type and interface

In many codebases:

  • interface is used for object shapes that may be extended often.
  • type is used when combining unions, intersections, primitives, tuples, or utility types.

Both are valid. Consistency matters more than strict preference.

Common Mistakes

1. Trying to use extends with type

This is the exact problem in the question.

// Incorrect
type UserEvent extends Event = {
  userId: string;
};

Use this instead:

type UserEvent = Event & {
  userId: string;
};

2. Confusing type and interface

This works:

interface Event {
  name: string;
}

interface UserEvent extends Event {
  userId: string;
}

But this does not:

type Event = {
  name: string;
};

// Invalid syntax
type UserEvent extends Event = {
  userId: string;
};

3. Overwriting properties with incompatible types

type Event = {
  type: string;
};

type BadUserEvent = Event & {
  type: number;
};

This creates an impossible type for type, because it would need to be both string and number.

Avoid redefining the same property with conflicting types.

4. Forgetting that all required fields still apply

type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

type UserEvent = Event & {
  userId: string;
};

const e: UserEvent = {
  userId: "u1"
};

This is invalid because name, dateCreated, and type are still required.

5. Using the name Event in browser code without thinking

In DOM-related TypeScript projects, Event is already a built-in type used for browser events. Naming your own type Event can be confusing.

Safer alternatives:

type AppEvent = {
  name: string;
  dateCreated: string;
  type: string;
};

Comparisons

ApproachSyntaxBest forNotes
Extend with interfaceinterface UserEvent extends Event { ... }Object shapesClean and readable for object inheritance
Combine with type intersectiontype UserEvent = Event & { ... }Reusing type aliasesWorks well with unions and utility types
Duplicate fields manuallyWrite all properties againAlmost never idealRepetition increases maintenance cost

type vs interface

Featuretypeinterface
Object shapesYesYes
Union typesYesNo
Intersection typesYesLimited through composition
extends keywordNo, not in alias declarationYes
Declaration mergingNoYes

Which should you use here?

If you already have:

type Event = { ... }

Use:

type UserEvent = Event & { userId: string }

If you prefer inheritance-style syntax, define both as interfaces instead.

Take Quiz

Cheat Sheet

// Base type
type Event = {
  name: string;
  dateCreated: string;
  type: string;
};

// Correct: extend a type alias using &
type UserEvent = Event & {
  userId: string;
};
// Alternative: use interfaces
interface Event {
  name: string;
  dateCreated: string;
  type: string;
}

interface UserEvent extends Event {
  userId: string;
}

Rules to remember

  • type X extends Y = ... is invalid.
  • Use & to combine type aliases.
  • Use extends with interface.
  • Combined types must satisfy all required properties.
  • Avoid conflicting property names with different types.
  • Be careful with the name Event in browser projects.

Quick pattern

type NewType = ExistingType & {
  extra: string;
};

Edge case

type A = { value: string };
type B = A & { value: number };

This is not useful because value cannot be both string and number at the same time.

FAQ

Can a TypeScript type extend another type?

Not with extends syntax in the alias declaration. Use an intersection with & instead.

How do I add properties to an existing type in TypeScript?

Create a new type using &:

type UserEvent = Event & { userId: string };

Should I use type or interface in TypeScript?

Use either consistently. interface is great for object shapes and extends; type is more flexible for unions, intersections, and other compositions.

Is interface extends the same as type &?

They are often similar for object shapes, but they are not identical in every advanced case. For this use case, both can model the same result.

Why does type UserEvent extends Event = ... fail?

Because TypeScript does not allow extends in a type alias declaration like that.

Can I override a property when extending a type?

You can redefine properties, but if the types conflict, the result may become unusable. It is better to avoid incompatible redefinitions.

Is Event a bad type name in TypeScript?

It can be confusing in frontend projects because the DOM already has a built-in Event type.

Take Quiz

Mini Project

Description

Build a small event model for an application that tracks different kinds of events. You will start with a shared base type and create more specific event types by extending it. This demonstrates how real TypeScript projects reuse type definitions instead of duplicating fields.

Goal

Create reusable event types where each specific event includes all base event fields plus its own additional properties.

Requirements

  1. Create a base event type with name, dateCreated, and type.
  2. Create a UserEvent type that adds userId.
  3. Create a PurchaseEvent type that adds userId and amount.
  4. Declare one valid object for each specific type.
  5. Write a function that accepts a UserEvent and logs its information.
Take Quiz

Keep learning

Related questions

View more