The Correct Term for “Upsert” is “Save”

When Clever Code Becomes Your Enemy
Fund this Blog

At an old job, our customers were early adopters of smartphones, and we had to ensure our web app worked flawlessly on their devices. As the newbie on the team, I was tasked with optimizing code to leave the smallest footprint possible without compromising features. This was before minifiers were commonplace, so I took it upon myself to write what I thought was beautifully compact code.

I became obsessed with brevity. Every variable had a single-letter name. Every for loop had to fit on one line. I'd write gems like this:

var i=0,l=b.length;for(;i<l;i++){if(b[i].x>m)m=b[i].x;}

Our customers rarely complained about performance, so I felt like a optimization wizard. The problem came two months later when I had to add new features. This "perfectly crafted" code made absolutely no sense to me. I spent more time deciphering my own work than it would have taken to write readable code from the start.

The Upsert Trap

Whenever I see "upsert" in code today, I'm reminded of that painful lesson. Like my overly clever one-liners, upsert functions often prioritize appearing smart over being maintainable.

The typical upsert function promises to save you time by handling both inserts and updates in one place. But if saving time is your goal, upsert is working against you.

Here's what usually happens: I encounter an "upsert" function, put on my thinking cap, and dig in. It's rare to find one that simply checks if records exist before deciding to insert or update. Instead, the name seems to encourage developers to mesh disparate logic together into one complex transaction.

A Better Approach: Just Call It "Save"

Instead of "upsert," use "save." This clearly communicates intent: the data should end up in the database, period.

function save(data) {
  if (data.id != null) {
    return update(data);
  }
  return insert(data);
}

If your function is doing anything more complex than this, you're probably overcomplicating things.

Why Separation Matters

The problem with cramming insert and update logic together goes beyond naming. You're creating a function that behaves completely differently depending on the situation. This violates the principle of single responsibility and makes your code harder to reason about.

I prefer keeping "save" at the abstraction level while maintaining distinct create and update functions underneath. Here's why this matters:

Database queries become unreadable. When you try to combine insert and update logic in SQL, you end up with either complex conditional queries or unreadable merge statements. Each approach increases the likelihood of introducing bugs when you need to modify the query later.

Adding fields becomes a nightmare. Want to add a new field to your saved entity? With separated functions, you update the insert query and the update query independently. With merged logic, you're debugging why your new field appears in inserts but not updates, or vice versa.

Testing becomes more complex. Instead of testing insert behavior and update behavior separately, you're testing a function that switches between two modes. Your test suite needs to cover more permutations and edge cases.

The Long Game

Clear separation simplifies your code and saves time in the future. Like my smartphone optimization days taught me, what feels efficient in the moment often creates more work down the line.

When you write code, you're not just solving today's problem. You're creating the foundation for tomorrow's features. Choose clarity over cleverness. Your future self (and your teammates) will thank you.

Remember, if you have to put on your thinking cap to understand what a function does, it's probably trying too hard to be smart.


Comments

There are no comments added yet.

Let's hear your thoughts

For my eyes only