I hate conditional spread in JavaScript.

Here is the kind of code that bothers me:

type UserUpdate = {
  isAdmin?: boolean;
  loginAttempts?: number;
  displayName?: string;
};

function buildUserUpdate(input: UserUpdate) {
  return {
    ...(input.isAdmin && { isAdmin: input.isAdmin }),
    ...(input.loginAttempts && { loginAttempts: input.loginAttempts }),
    ...(input.displayName && { displayName: input.displayName }),
  };
}

buildUserUpdate({
  isAdmin: false,      // revoke admin access
  loginAttempts: 0,   // reset the counter
  displayName: '',    // clear the name
});

// Result: {}
// Nothing is updated.

At a glance, this looks concise. In practice, it turns three valid values into an empty update.

The admin keeps their access. The login-attempt counter is not reset. The display name is not cleared. The code does nothing, and it does so without throwing an error.

This is not an unusual edge case. Boolean flags, zero counters, and empty strings appear in normal application code all the time.

Truthiness is not intent

The problem is that the same value is doing two jobs. It is both the data we want to save and the condition deciding whether that data should exist.

JavaScript considers false, 0, and '' falsy. Conditional spread therefore removes the property completely, even though each value can carry a clear instruction: disable this, reset this, or clear this.

For an update payload, the real rule is usually different: omit the property only when its value is undefined. That distinction matters because an omitted property often means “leave the existing value alone.”

Conditional spread hides that decision inside syntax that already takes effort to parse.

I prefer the boring version

I would write this:

function buildUserUpdate(input: UserUpdate) {
  const update: UserUpdate = {};

  if (input.isAdmin !== undefined) {
    update.isAdmin = input.isAdmin;
  }

  if (input.loginAttempts !== undefined) {
    update.loginAttempts = input.loginAttempts;
  }

  if (input.displayName !== undefined) {
    update.displayName = input.displayName;
  }

  return update;
}

// Result: {
//   isAdmin: false,
//   loginAttempts: 0,
//   displayName: '',
// }

Yes, it is longer. It is also obvious.

The condition has a nameable place. The object construction has a nameable place. A future reader can add another property, put a breakpoint inside the block, or change the condition without untangling an expression.

I do not think mutation is automatically worse here. A small, local mutation can be easier to understand than a dense immutable expression.

Concise is not the same as simple

I am not saying conditional spread should never exist. For a tiny configuration object with one clear boolean condition, it can be fine.

But it should not be the default just because it fits on one line.

Simple code is not code with the fewest characters. Simple code is code that exposes its decisions. In this case, I would rather read an if statement.

Oliver Jam wrote a useful breakdown of how conditional object properties work. The mechanics are interesting. My conclusion is still the same: if a small piece of code needs that much explanation, I probably do not want it in everyday application code.