An interface describes object structure for the compiler. It does not allocate an object or validate an external record. A readonly array prevents mutation through that typed reference; it is not a runtime freeze or a guarantee that every nested object is immutable.
Our display helper accepts records with an optional label and returns fresh display rows. An absent label falls back to service, but an empty label is intentionally preserved. That small distinction exposes the difference between absence and truthiness before a larger migration depends on it.
Use the nullish operator ?? for absence rather than || for truthiness. Empty strings, zero and false are different values from missing data; choosing the wrong fallback quietly changes the API. Here an empty label is valid.
With exactOptionalPropertyTypes, label?:string permits omission but does not permit explicitly assigning undefined in a typed object unless undefined is included in that property’s type. Reading the property can still produce undefined, so the helper handles that branch deliberately.
export interface DisplayJob { service: string; label?: string; }
export interface DisplayRow { service: string; label: string; }
export function displayRows(jobs: readonly DisplayJob[]): DisplayRow[] {
return jobs.map(job => ({ service: job.service, label: job.label ?? job.service }));
}
console.log(JSON.stringify(displayRows([{service:"api"},{service:"db",label:""}])));Fresh output objects avoid aliasing the original record. The tests freeze inputs and inspect identity as well as values. That checks behavior beyond whether a type declaration compiles.
The compiler check contrasts an omitted optional property with an explicitly undefined value under the chosen settings. These settings are part of this lab’s contract; another project’s tsconfig may choose different optional-property behavior.
interface Options { label?: string; }
const omitted: Options = {};
const empty: Options = {label:""};
console.log(omitted.label ?? "fallback");
console.log(JSON.stringify(empty.label ?? "fallback"));Implement fresh display rows and verify empty labels, absent labels and readonly input behavior. Next use tagged unions to make alternative outcomes explicit.