25 Apr 2026
If you're not familiar with the first two points, this blog might not be suitable for you. Otherwise, you're good to go. The examples are intentionally simple and can be easily replicated in other languages.
Hey devs, welcome back to the design pattern series! So far in our Booking System:
Now let's solve another real problem. In our Booking System, we often encounter scenarios where users need to create bookings that are similar to ones they've made before. So, how can we create a similar booking? Copying everything from scratch is time-consuming and error-prone.
And even if we decide to copy everything manually, the object holds more than what meets the eye. A Booking isn't just a flat bag of fields. It carries nested dependencies - a TimeSlot with a Date inside it, an Insurance object with its own coverage rules, a PaymentMethod with sensitive details, and so on.
When you try to copy it from the outside, you only see the surface. You grab the references - not the actual objects behind them. So your copy ends up sharing the same TimeSlot, the same Insurance, the same PaymentMethod as the original.
Mutate one, you accidentally mutate the other.
This is the classic shallow copy trap.
And it gets worse - some fields might be private. You can't even read them from the outside to copy them properly. So you end up with an incomplete clone, like the image below - the original has all its dependencies intact, but your cloned object is missing half of them.

Instead of creating everything from scratch... can we just reuse an existing booking? That’s where the Prototype Pattern comes in.
Prototype is a creational design pattern that lets you copy existing objects without depending on their concrete classes.
Instead of creating objects from scratch, we clone an existing instance.

In our Booking System, recreating similar bookings from scratch is time-consuming and error-prone - and complex booking configurations with many add-ons, insurance options, and payment preferences make it even more tedious to set up repeatedly. On top of that, the system needs to copy objects without always knowing their specific types at runtime.
Without Prototype:
function repeatBooking(original: Booking, newTimeSlot: TimeSlot): Booking {
return new Booking(
original.customer,
original.resource,
newTimeSlot,
original.addOns,
original.insurance,
original.promoCode,
original.specialRequests,
original.paymentMethod
);
}
This approach is prone to errors and becomes unwieldy as we add more properties to the Booking class. If we modify the Booking class in the future, we'll need to update all the places where we manually create copies.

Instead of copying manually, let the object clone itself.

interface Prototype<T> {
clone(): T;
}
abstract class Booking implements Prototype<Booking> {
public customer: Customer;
public resource: Resource;
public timeSlot: TimeSlot;
public addOns: string[] = [];
public insurance?: Insurance;
public promoCode?: string;
public specialRequests?: string;
public paymentMethod?: PaymentMethod;
constructor(builder: BookingBuilder) {
this.customer = builder.customer;
this.resource = builder.resource;
this.timeSlot = builder.timeSlot;
this.addOns = builder.addOns;
this.insurance = builder.insurance;
this.promoCode = builder.promoCode;
this.specialRequests = builder.specialRequests;
this.paymentMethod = builder.paymentMethod;
}
abstract clone(): Booking;
}
class BusinessBooking extends Booking {
clone(): Booking {
return new BookingBuilder(
this.customer,
this.resource,
this.timeSlot.clone(),
)
.withAddOns([...this.addOns])
.withInsurance(this.insurance?.clone())
.withPromoCode(this.promoCode)
.withSpecialRequests(this.specialRequests)
.withPaymentMethod(this.paymentMethod)
.build();
}
}
class TimeSlot implements Prototype<TimeSlot> {
constructor(
public startDate: Date,
public durationDays: number
) {}
clone(): TimeSlot {
return new TimeSlot(
new Date(this.startDate.getTime()),
this.durationDays
);
}
}
class Insurance implements Prototype<Insurance> {
constructor(public type: string, public coverage: number) {}
clone(): Insurance {
return new Insurance(this.type, this.coverage);
}
}
class PaymentMethod implements Prototype<PaymentMethod> {
constructor(public type: string, public details: any) {}
clone(): PaymentMethod {
return new PaymentMethod(
this.type,
JSON.parse(JSON.stringify(this.details))
);
}
}
class BookingService {
repeatBooking(existing: Booking, newTimeSlot: TimeSlot): Booking {
const cloned = existing.clone();
cloned.timeSlot = newTimeSlot;
return cloned;
}
}
Think of this like "Book Again" in real apps.
class BookingRegistry {
private templates: Map<string, Booking> = new Map();
addTemplate(name: string, booking: Booking): void {
this.templates.set(name, booking);
}
getTemplate(name: string): Booking | undefined {
const template = this.templates.get(name);
return template?.clone();
}
}
// Build the template properly using the builder
const businessTemplate = new BookingBuilder(
customer,
new Room("Business Suite"),
new TimeSlot(new Date(), 1),
)
.withAddOns(["wifi", "breakfast"])
.build();
const registry = new BookingRegistry();
registry.addTemplate("business-trip", businessTemplate);
// User selects template
const booking = registry.getTemplate("business-trip");
if (booking) {
booking.timeSlot = new TimeSlot(new Date("2025-12-15"), 1);
bookReservation(booking);
}

Ok devs, that's it for today. I tried my best to explain this pattern. In the next post, we'll explore another pattern in our Design Pattern series. If you have any queries or suggestions, please feel free to reach out to me.

Code, learn, refactor, repeat