
I’m Kayla. I write code all day. I also break it sometimes. Then I fix it. You know what? One tiny thing saved me a lot of time this year: using “named” arguments for class constructors in TypeScript. It’s not a real language feature. It’s a pattern. But it feels real once you use it.
If you’re curious about how this pattern shows up in everyday JavaScript as well, the wider community often calls it the named-arguments pattern.
And yes, I’ve used it in my own apps. On a late night, tea in hand, cat on keyboard. Let me explain.
The pain: positional args make my brain hurt
I used to write classes like this:
class User {
id: string;
name: string;
email?: string;
isActive: boolean;
constructor(id: string, name: string, email?: string, isActive: boolean = true) {
this.id = id;
this.name = name;
this.email = email;
this.isActive = isActive;
}
}
// Call site
const u = new User("u1", "Kayla", undefined, false);
Now read that call. What is false here? Is it isActive? Is email missing? I had to count the commas. Not fun.
The switch: one object, clear names
So I changed to “named” args. It’s just one object. But it reads like a story. If you’d like an expanded, real-world walkthrough, I’ve published one on ImprovingCode.com that pairs perfectly with the examples below.
For readers who want every gritty detail, you can dig into the full deep-dive here: I tried TypeScript “named” arguments for constructors — here’s my take.
For a succinct overview from a different angle, there’s also this Medium explainer on named arguments for constructors in TypeScript.
interface UserArgs {
id: string;
name: string;
email?: string;
isActive?: boolean;
}
class User {
id: string;
name: string;
email?: string;
isActive: boolean;
constructor({ id, name, email, isActive = true }: UserArgs) {
this.id = id;
this.name = name;
this.email = email;
this.isActive = isActive;
}
}
// Call site
const u = new User({
id: "u1",
name: "Kayla",
isActive: false, // very clear
});
I can pass fields in any order. I can see what each value means. My future self says thanks.
Defaults that just work
I like sane defaults. I set them right in the destructuring.
interface ReportArgs {
title: string;
author?: string;
date?: Date;
pageSize?: "A4" | "Letter";
}
class Report {
title: string;
author: string;
date: Date;
pageSize: "A4" | "Letter";
constructor({
title,
author = "System",
date = new Date(),
pageSize = "A4",
}: ReportArgs) {
this.title = title;
this.author = author;
this.date = date;
this.pageSize = pageSize;
}
}
const r = new Report({ title: "Q3 Numbers" }); // uses all defaults
One nice thing: the defaults kick in when a field is missing or undefined. But not when it’s null. That bit tripped me once.
Real use: a Mailer config that didn’t bite me later
I shipped a small Mailer. Then my PM asked for TLS and timeouts. I didn’t break calls, because the args were named.
interface MailerArgs {
host: string;
port?: number;
secure?: boolean;
username?: string;
password?: string;
timeoutMs?: number;
}
class Mailer {
host: string;
port: number;
secure: boolean;
username?: string;
password?: string;
timeoutMs: number;
constructor({
host,
port = 587,
secure = false,
username,
password,
timeoutMs = 5000,
}: MailerArgs) {
this.host = host;
this.port = port;
this.secure = secure;
this.username = username;
this.password = password;
this.timeoutMs = timeoutMs;
}
}
const mailer = new Mailer({
host: "smtp.myapp.com",
username: "no-reply",
password: "secret",
secure: true,
});
Later, I added timeoutMs. Old calls still worked. New calls could set the field. No headache. No reorder mess.
Tiny add-on: type once, reuse everywhere
I like to reuse the args type for factories and helpers.
interface ProductArgs {
id: string;
name: string;
priceCents?: number;
tags?: string[];
}
class Product {
id: string;
name: string;
priceCents: number;
tags: string[];
constructor({
id,
name,
priceCents = 0,
tags = [],
}: ProductArgs) {
this.id = id;
this.name = name;
this.priceCents = priceCents;
this.tags = tags;
}
static freeSample(args: Omit<ProductArgs, "priceCents">) {
return new Product({ ...args, priceCents: 0 });
}
}
const p = Product.freeSample({ id: "p1", name: "Sticker" });
Clear types. Clear calls. Less guessing.
A few “gotchas” I hit (and how I handled them)
-
Extra fields: Passing an object literal with unknown fields will warn. I like that. If I truly need extra stuff, I capture it.
interface WithRest extends UserArgs { [key: string]: unknown; } class UserWithRest extends User { rest: Record<string, unknown>; constructor(args: WithRest) { const { id, name, email, isActive, ...rest } = args; super({ id, name, email, isActive }); this.rest = rest; } } -
Destructuring and “parameter properties”: You can’t do this neat trick:
// Not allowed: // constructor(public { id, name }: UserArgs) {}So I keep fields normal and assign inside the body. Simple wins.
-
Null vs undefined: Defaults don’t run on
null. If someone setssomething: null, it stays null. I check it when needed. -
Mixing styles: For legacy code, I sometimes support both. But I don’t love it. It adds noise.
class LegacyUser { id: string; name: string; constructor(id: string, name: string); constructor(args: { id: string; name: string }); constructor(a: string | { id: string; name: string }, b?: string) { if (typeof a === "string") { this.id = a; this.name = b!; } else { this.id = a.id; this.name = a.name; } } }
When I don’t use it
- For two tiny fields with no defaults? I may keep them positional.
- For perf hot paths in tight loops? I stick to simple calls. Though, honestly, it’s rarely the bottleneck.
One niche scenario where the pattern also shined was while prototyping a personal “message generator” for different chat platforms. The function accepted optional fields like caption, mediaUrl, and isNSFW, and I really didn’t want to confuse them. If you need real-world inspiration (or sample data) for risqué chat content, this curated collection of WhatsApp sexts showcases how people actually phrase, punctuate, and emoji-up their messages, which is handy when you’re seeding test databases or refining content-moderation rules. For a location-specific angle on adult-themed listings, I even browsed the structure of posts over at AdultLook Poway — the page gives a clear look at the fields real advertisers use (think location, rates, and bio snippets), so you can model realistic payloads or sanity-check your own naming conventions before they hit production.
A quick checklist I follow
- Define
Argsinterface. - Make only real requirements required.
- Put defaults in the destructuring.
- Keep calls readable at a glance.
- Don’t hide type errors with broad casts.
Final word: small change, calmer code
This pattern made my code easier to read. It made refactors less scary. The calls tell a story, field by field. It’s not magic. It’s just one object. But it feels nice. And when you’re tired and fixing bugs at 11 pm, nice matters.
If your

