Postgres ENUM management: sunny adding versus gloomy removing on the bus ride
Why is this Databases meme funny?
Level 1: Permanent Marker Problem
Think of it like writing in different ways. Imagine you have a special list of allowed words written with permanent marker. Adding a new word to that list is easy – you just grab the marker and write another one at the bottom. Now your list has a new option, and everyone is happy because it was so simple, like drawing a new sun in a sunny picture. 😃 But what if you want to erase a word from that permanent marker list? Uh oh! You can’t just rub it with an eraser like pencil; it’s stuck there. Removing it would mean you might have to get a whole new piece of paper or paint over it and rewrite everything except that one word. That’s a lot of work, and it’s messy – like staring at a dark, rocky wall with no easy path. 😢
This meme is joking about that kind of situation in a programmer’s world. The happy person is like someone who gets to simply add a new item (easy peasy!), and the sad person is someone who was told to take an item away (which is really hard and frustrating). In simpler terms: adding new things is often easy and fun, but removing something that’s already there can be really hard. The picture uses a bus ride: both people are on the same bus, but one side sees a beautiful view (because adding something was carefree) and the other side sees a rocky cliff (because removing something feels impossible). The joke is highlighting how a task that sounds small (“let’s remove this now”) can actually be a big problem if the thing was written in “permanent marker.” It’s a funny way to remember: be careful what you write in permanent marker (or set in stone), because you might be stuck with it for a long time!
Level 2: Easy Adding, Hard Removing
Time to break down what’s going on in this meme in simpler terms. We’re dealing with PostgreSQL, a popular database, and a special kind of column type it offers called an ENUM (short for enumeration). An enum in a database is kind of like an enum in programming languages: it’s a data type that allows only a predefined set of values. For example, imagine we design a mood enum type that can only be one of {'happy', 'okay', 'sad'}. If a table has a column of this enum type, every row must use one of those three words – nothing outside the list. This is great for maintaining data consistency (no typos or unexpected values can creep in).
Now, the meme compares two actions on a Postgres enum: adding a new value vs. removing an existing value. The right side passenger, cheerful and excited, is labeled “adding enum values in Postgres.” This corresponds to the scenario where a developer needs to add a new allowed value to an enum type. In PostgreSQL, that’s usually straightforward. The developer can run an SQL command like:
ALTER TYPE Mood ADD VALUE 'excited';
This would, for instance, add 'excited' as a new mood option in our Mood enum type. PostgreSQL handles this happily – it updates the type’s definition, and now any table column using Mood can also store the value 'excited'. No existing data is harmed, and it’s typically done in a instant with minimal fuss. It feels as easy as looking out at a sunny landscape. Developers love when a requirement is this easy to implement! This is why the passenger adding a value is smiling at a bright valley: it’s a smooth ride and the future (with new features or statuses) looks bright.
Now look at the left side: the passenger is miserable, staring at a dark rocky wall, labeled “removing enum values in Postgres.” This is the poor developer who was told, “Hey, we don’t need one of those enum options anymore – let’s remove it.” 😟 In theory, you’d expect a symmetric command like ALTER TYPE Mood DROP VALUE 'sad'; to remove 'sad' from the allowed list. But here’s the catch: PostgreSQL doesn’t allow that! If you try the above command, you’ll get an error. There is no simple DROP command to take an enum value out once it’s been created. This often comes as a surprise to someone new: “Wait, I can add but I can’t remove? Why not?” The meme is built around that very frustration. Removing an enum value is “gloomy” because it involves a complicated, multi-step process instead of one simple step.
To actually get rid of an enum value in Postgres, you have to perform what’s essentially a manual migration. Here’s what usually needs to happen in simpler terms: you create a new enum type that has all the old values except the one you don’t want. Then you have to update every place (every table column) that used the old enum to use the new enum type. This might involve copying data over or converting data types. Finally, you remove the old enum type entirely. Imagine having to replace a foundation pillar in a house – you need to build a new pillar, move the structure to it, then remove the old pillar. It’s that kind of careful operation. During this change, you often have to put the system in read-only mode or take downtime because the data is being juggled around. It’s not impossible, but it’s a lot of work and must be done very carefully. No wonder the left passenger looks like they’re on a scary, rocky road!
In everyday development, this means when you design your database schema (the structure of your database), using an enum type is a bit of a commitment. You can add to it easily – which is perfect when your product keeps growing with new categories, statuses, or features – but you can’t easily subtract from it. Many juniors and new developers find this out the hard way: the first time they happily use an enum for something like user roles or status codes, it works great. Then months later someone says, “This old role is no longer used, let’s remove it,” and that’s when they discover that Postgres doesn’t want to let them simply remove it. It can be a head-scratching moment followed by searching through documentation or Stack Overflow for solutions. The answers usually outline the heavy workaround we described above. It’s definitely a lesson in database migrations: not all changes are created equal. Adding is usually considered a non-breaking change (no existing data or code fails when you add a new option), whereas removing is a breaking change (it can invalidate existing data or code expecting that value). PostgreSQL enforces that by design – it will happily let you do the former, but will stop you from accidentally doing the latter.
The meme uses the bus ride cartoon format to make this lesson entertaining. It’s a popular format in online humor: two people on the same bus, one delighted and one dismayed, usually because they’re looking at different views or experiencing a situation differently. Here, both passengers are dealing with Postgres enums (they’re on the same “ride”), but one is performing an easy task (so they’re happy) and the other a difficult task (so they’re upset). If you’re a backend or database developer, you probably relate to this instantly. If you’re newer: just remember that in PostgreSQL, schema changes like this can be lopsided. Adding an enum value with SQL is quick, but removing one is a project. This meme is a friendly warning wrapped in humor: Plan your enums carefully, because once on board, removing a stop on this bus route is a rough journey!
Level 3: Enums Are Forever
For experienced backend developers and DBAs, this meme elicits a knowing chuckle (or perhaps a groan). It perfectly captures a notorious PostgreSQL quirk: adding a new enum value is a breeze, but removing an existing enum value is a nightmare. The two bus passengers illustrate these polar experiences. The happy passenger on the right represents a developer adding a new allowed value to a Postgres enum – it’s a one-line change, trivial to do during a sprint, and usually doesn’t even require downtime. The view out that window is a sun-lit paradise, reflecting the sunny optimism of schema extension. In contrast, the gloomy passenger on the left is every developer who’s been asked to delete or rename an enum value that’s no longer needed. Their window shows a dark, rocky cliff – the bleak road of schema cleanup. And indeed, once you’ve hit that cliff, you realize there’s no straightforward path forward.
The humor here comes from how disproportionate the efforts are: joyously easy to add, ridiculously hard to remove. It’s an exaggeration, but not by much – anyone who’s maintained a Postgres database will tell you the pain is real. There’s even an unofficial rule: “Once created, an enum is eternal.” This permanence is why the subtitle jokingly says “Enums Are Forever.” In practice, removing an enum value in Postgres often feels like a herculean task or an epic quest. You can’t simply run a DROP command for that one value (Postgres will flat-out refuse). Instead, you must undertake a multi-step database migration that can involve:
- Creating a new enum type that is identical to the old one minus the value you want to remove.
- Altering every table and column that used the old enum to now use the new enum type – which typically means a costly
ALTER TABLE ... ALTER COLUMN TYPEoperation. This can lock the table and rewrite data, incurring downtime especially if the table is large. - Converting or updating any data that might have the obsolete value. All rows using the old value must be handled (perhaps changed to a different value or temporarily to a placeholder) before the type change, otherwise the conversion will fail.
- Finally, dropping the old enum type (now unused) once all data and code have been moved over.
Often the process looks like:
-- Suppose we have an enum type status with values ('active', 'inactive', 'beta')
-- We want to remove 'beta'.
-- 1. Rename the old type to keep it around temporarily
ALTER TYPE status RENAME TO status_old;
-- 2. Create a new type without the unwanted value
CREATE TYPE status AS ENUM('active', 'inactive');
-- (The 'beta' value is intentionally left out)
-- 3. Adjust table to use the new type, casting the old values to text then to the new enum
ALTER TABLE users
ALTER COLUMN account_status TYPE status
USING account_status::text::status;
-- (account_status is now of type status, with 'beta' values converted or rejected)
-- 4. Drop the old type now that it's no longer used
DROP TYPE status_old;
This is a gloomy ordeal compared to the one-liner for adding a value. And if the enum is used in many tables, or if there are foreign key constraints, default values, stored procedures, etc., referencing it, the migration becomes even more convoluted. It’s the kind of refactoring that senior engineers plan carefully for a low-traffic period (often late at night, hence those 3 A.M. war stories) or avoid altogether if possible. Downtime and risk loom like that rocky cliff outside the left side of the bus.
The industry collectively finds this both frustrating and funny because it’s a textbook example of how software “easy to use” features can hide sharp edges when requirements change. Early in a project, using a Postgres enum for a status field feels clean and robust – no invalid values, easy to read, and one-step addition of new statuses. That’s the smiling passenger enjoying the scenic route. But later, when one of those statuses needs to be retired, you confront the technical debt of that choice. The meme resonates especially with backend engineers who’ve learned this lesson the hard way: what was sunny in development can turn stormy in maintenance. It’s an inside joke that also carries a gentle warning.
This scenario also underscores a common pattern in database schema design – schema evolution is usually additive, not subtractive. Adding columns, tables, or enum values tends to be simpler (doesn’t break existing usage), whereas removing or altering them can break dependencies and requires careful planning. Seasoned developers often joke about this dichotomy. You might hear a senior dev quip, “One does not simply drop an enum value in Postgres,” echoing the meme. The comedy comes from shared pain: everyone in the room remembers a time they either avoided using enums in the first place (to dodge this fate) or spent a long weekend wrestling with an enum removal migration. The bus meme format visualizes that shared experience perfectly – two people on the same ride (the same database), one thrilled and one despairing, depending on what task they’re doing.
From an organizational perspective, this difference can even affect how teams plan features. For example, a team might quickly roll out a new SQL enum value for a new feature (no big deal), but they’ll be very hesitant when someone suggests removing or renaming an existing value. It might spark debates, design discussions for alternatives, or decisions to deprecate in code only. Some teams adopt conventions like never reusing or removing enum values; instead they mark them “deprecated” but leave them in the database to avoid the hassle. Others switch to using more flexible solutions (like a lookup table with rows that can be easily added or removed) once they get burned by the enum approach. In essence, the meme is poking fun at how a seemingly minor task (removing a now-unneeded option) turns into a drama. The contrast between “sunny adding” and “gloomy removing” isn’t just visual – it’s emotionally true for developers. The bright side of the bus is the ease of moving forward and expanding, and the dark side is the pain of trying to go back and change what’s already set in stone. Enums in Postgres are, for better or worse, a one-way trip. 🚌💨
Level 4: Append-Only Schema
At the deepest technical level, this meme highlights PostgreSQL's design choice of treating ENUM types as essentially append-only data structures. In PostgreSQL, an ENUM (enumerated type) is internally represented by a set of fixed values stored in system catalogs (like pg_type and pg_enum). Each enum value is given a unique identifier and a specific sort order. Adding a new value is a relatively simple operation: the database inserts a new entry into the enum’s catalog definition. This is why ALTER TYPE ... ADD VALUE is a quick, O(1) metadata change that doesn’t disturb existing rows. The new value just gets an ID and, if no explicit place is given, is usually appended to the end of the allowed list (new green hills appear on the horizon, so to speak 🌄).
Removing a value, however, is a fundamentally different beast. PostgreSQL does not support ALTER TYPE ... DROP VALUE at all – a deliberate limitation reflecting the immutability of its type system. Why such rigidity? It comes down to preserving data integrity and avoiding the complex cascade of changes that removal would entail. If an enum value were carved out (imagine chipping away part of a rock face), any existing table row of that type referencing the removed value would become invalid or orphaned. Even if no current rows use that value, the mere deletion could destabilize the ordering and identities of the remaining values. The internal identifiers (OIDs) assigned to enum labels would shift or leave a gap, potentially confusing any stored data, indexes, or functions that rely on them. In a strongly-typed, ACID-compliant system like Postgres, such unpredictable side effects are a no-go. So the developers opted for a purist approach: enums are static once created. You can extend the domain (add new allowed values) but you cannot contract it without manual intervention. This is analogous to an append-only log in system design – you can always add new entries, but altering or deleting past entries breaks the consistency guarantees.
Historically, schema evolution in relational databases often favors monotonic changes (those that only add information) because they’re backward-compatible and don’t require rewriting existing data. Removing or altering existing schema elements is far more complex – it’s like trying to undo history. PostgreSQL’s enum implementation embodies this principle. The bright side is data safety and simplicity: by disallowing value removal in the core engine, Postgres avoids a whole class of potential inconsistencies. The dark side is inflexibility: once an enum label is in place, it’s effectively permanent (at least until you take drastic measures). This one-way flexibility is why adding a value is as smooth as enjoying a sunny vista, while removing one is akin to staring at an unyielding cliff. It’s a trade-off rooted in deep database internals: preserving schema integrity at the cost of ease-of-change. In academic terms, think of it as maintaining a total order on a set of symbols – you can append new symbols (extending the order), but removing a symbol would require re-ordering or leaving “holes” in that order, which the system is not designed to handle on the fly. The result is an immutable enum type where modification is an irreversible choice. This foundational constraint sets the stage for the very different emotional journeys depicted in the meme.
Description
Cartoon meme of a tour bus interior showing two passengers on opposite sides. The left passenger, labeled "removing enum values in Postgres," looks dejected out a window that shows a dark, rocky cliff face. The right passenger, labeled "adding enum values in postgres," smiles enthusiastically while viewing a bright, sun-lit valley full of green hills and yellow sky. The contrast humorously captures how ALTER TYPE ... ADD VALUE in PostgreSQL is straightforward, whereas deleting an ENUM value involves painful work-arounds like creating new types, data migrations, and downtime. The visual metaphor is relevant for database engineers who wrestle with immutable ENUM constraints during schema evolution
Comments
7Comment deleted
ENUMs feel like domain-driven elegance - right up until marketing renames “gold” to “platinum” and you’re scheduling a read-only window to CAST every table to text, recreate the type, and pray no replica lags behind
The real PostgreSQL enum experience: adding values is like ALTER TYPE ADD VALUE IF NOT EXISTS, but removing them? Time to create a migration script that builds a new type, updates all columns, drops the old type, and renames the new one - all while praying no one deployed code expecting that value in the 30 seconds it takes to run
Ah yes, PostgreSQL enums: where adding a value is a simple ALTER TYPE...ADD VALUE, but removing one requires you to create a new type, migrate all data, update all dependencies, drop the old type, and rename the new one - all while praying your application doesn't explode during the deployment window. It's the database equivalent of 'easy to get in, impossible to get out,' like a roach motel for type definitions. Senior engineers know the real wisdom is designing enums with the foresight of a fortune teller, because once that enum value ships to production, it's basically immortal - or at least until your next major version when you can finally justify the multi-hour maintenance window to exorcise it
Postgres enums: O(1) appends with ADD VALUE, but removing? That's your full dataset's Big O nightmare disguised as a simple DROP
Adding a Postgres enum value is a quick catalog update; removing one is a three-release saga of new type, backfill, CAST USING, and rename - the interest payment for choosing enums over a lookup table
Postgres enums: ADD VALUE is a one‑liner; removing one is a three‑week plan with status_v2, ALTER TABLE … USING, a lock‑dodging backfill, and a blameless retro on why we used enums in prod
adding enum values in Java at the runtime as a part of Minecraft modding Comment deleted