// TypeScript without constraint: merge<T, U>(a: T, b: U): T & U
// Bug: works at type level but no constraint prevents primitives
function merge(a, b) {
if (typeof a !== 'object' || a === null || typeof b !== 'object' || b === null) {
// No TypeScript error without constraint — silently returns wrong type
return String(a) + String(b); // treats as string concat when primitives
}
return { ...a, ...b };
}
// Correct usage: two objects
const combined = merge({ name: 'Alice' }, { age: 30 });
console.log(JSON.stringify(combined));
// Bug: passing primitives — TypeScript should reject with T extends object constraint
const wrong = merge('hello', 42);
console.log(wrong);
console.log(typeof wrong);// Fix: validate inputs are objects before merging
function merge(a, b) {
if (typeof a !== 'object' || a === null) {
throw new TypeError('First argument must be an object');
}
if (typeof b !== 'object' || b === null) {
throw new TypeError('Second argument must be an object');
}
return { ...a, ...b };
}
// Correct usage only — TypeScript constraint prevents primitives at compile time
const combined = merge({ name: 'Alice' }, { age: 30 });
console.log(JSON.stringify(combined));
const full = merge({ id: 1, name: 'Alice' }, { role: 'admin', active: true });
console.log(full.name + ':' + full.role);Bug: Without T extends object constraint, TypeScript allows merging primitives. The function falls into the string concatenation branch and returns "hello42" — not an object merge. TypeScript fix: T extends object, U extends object.
Explanation: With proper inputs, spread merge works correctly. TypeScript's T extends object constraint prevents primitives at compile time — no need for runtime checks when types are enforced.
Key Insight: Constraints (T extends object) are compile-time guarantees. They prevent whole classes of misuse without runtime overhead. Always constrain generics to the minimum required interface — it documents intent AND prevents bugs.