Beware the Global Const
It is common wisdom in the programming world to discourage the use of global variables.1 It is bug-prone due to shared mutability across modules. It introduces implicit dependencies within interfaces that are not syntactically communicated. It may alter the behavior of dependents across invocations—even when provided with the same arguments. I've recently come across a bug that humbled and reminded me about these pitfalls. Let me tell you a cautionary tale about global configuration objects. DISCLAIMER: The following story is based on an actual bug in one of the projects that I work on, but several details have been altered and simplified. A race condition? The bug was initially simple. A user reported that generating a profile page in the app with ✨AI✨ in quick succession resulted in some components in the page persisting the old values from the previous ✨AI✨ generation. "Simple," I naively thought to myself, "It's a classic case of a race condition." I hypothesized that the previous generation completed after the current one, which resulted in the old values overwriting the new ones in the database. The only problem was: I couldn't find the race condition. Each ✨AI✨ enrichment is persisted as a single row in the database. Even if a race condition were to happen, the generated profile page should swap out the contents on a per-generation basis (i.e., one row as a whole). This was clearly not the case as the generated page interleaved new and old contents. Sure enough, the database was intact. Each ✨AI✨ enrichment was indeed isolated from each other. No interleaving fields. No missing properties. No bad data. Nothing suspicious at all... It's a front-end bug, then? "Okay, surely the bug is in the front end," I thought to myself. I looked into the root component that renders the profile page starting with the props. // A vastly simplified recreation of the page. interface Props { // From the database... config: ProfileDetails; } export function Profile({ config }: Props) { return ( {config.name} {config.description} ); } Nothing seemed off as the page component was literally just rendering what was given to it. "The bug must be in the data loading," I deduced. After some console.log debugging, I confirmed that the rows from the database were still intact. At this point, I was fairly confident that the persisted data was not at fault. Despite my best efforts, I could not reproduce the bug. The data loaded from an enrichment row exactly matched the details rendered in the page. I mean... that's why race conditions are so difficult to debug, huh? At this point, I could only rely on my detective skills. The dangers of default configurations A couple hours passed when I finally raised my eyebrow at the call site of the Profile component. // @/profile export const DEFAULT_PROFILE_DETAILS = { bannerUrl: '...', name: '...', description: '...', // ... }; import merge from 'lodash.merge'; import { DEFAULT_PROFILE_DETAILS } from '@/profile'; import { getProfileDetails } from '@/db'; import { getSessionAuth } from '@/auth'; export default async function Page() { const auth = await getSessionAuth(); const profileDetails = await getProfileDetails(auth); const config = merge(DEFAULT_PROFILE_DETAILS, profileDetails); return ; } Wait a minute. Zoom in... Enhance! const config = merge(DEFAULT_PROFILE_DETAILS, profileDetails); //

It is common wisdom in the programming world to discourage the use of global variables.1
- It is bug-prone due to shared mutability across modules.
- It introduces implicit dependencies within interfaces that are not syntactically communicated.
- It may alter the behavior of dependents across invocations—even when provided with the same arguments.
I've recently come across a bug that humbled and reminded me about these pitfalls. Let me tell you a cautionary tale about global configuration objects.
DISCLAIMER: The following story is based on an actual bug in one of the projects that I work on, but several details have been altered and simplified.
A race condition?
The bug was initially simple. A user reported that generating a profile page in the app with ✨AI✨ in quick succession resulted in some components in the page persisting the old values from the previous ✨AI✨ generation.
"Simple," I naively thought to myself, "It's a classic case of a race condition." I hypothesized that the previous generation completed after the current one, which resulted in the old values overwriting the new ones in the database. The only problem was: I couldn't find the race condition.
Each ✨AI✨ enrichment is persisted as a single row in the database. Even if a race condition were to happen, the generated profile page should swap out the contents on a per-generation basis (i.e., one row as a whole). This was clearly not the case as the generated page interleaved new and old contents.
Sure enough, the database was intact. Each ✨AI✨ enrichment was indeed isolated from each other. No interleaving fields. No missing properties. No bad data. Nothing suspicious at all...
It's a front-end bug, then?
"Okay, surely the bug is in the front end," I thought to myself. I looked into the root component that renders the profile page starting with the props.
// A vastly simplified recreation of the page.
interface Props {
// From the database...
config: ProfileDetails;
}
export function Profile({ config }: Props) {
return (
<main>
<HeroBanner src={config.bannerUrl} />
<h1>{config.name}h1>
<p>{config.description}p>
main>
);
}
Nothing seemed off as the page component was literally just rendering what was given to it. "The bug must be in the data loading," I deduced. After some console.log
debugging, I confirmed that the rows from the database were still intact. At this point, I was fairly confident that the persisted data was not at fault.
Despite my best efforts, I could not reproduce the bug. The data loaded from an enrichment row exactly matched the details rendered in the page. I mean... that's why race conditions are so difficult to debug, huh? At this point, I could only rely on my detective skills.
The dangers of default configurations
A couple hours passed when I finally raised my eyebrow at the call site of the Profile
component.
// @/profile
export const DEFAULT_PROFILE_DETAILS = {
bannerUrl: '...',
name: '...',
description: '...',
// ...
};
import merge from 'lodash.merge';
import { DEFAULT_PROFILE_DETAILS } from '@/profile';
import { getProfileDetails } from '@/db';
import { getSessionAuth } from '@/auth';
export default async function Page() {
const auth = await getSessionAuth();
const profileDetails = await getProfileDetails(auth);
const config = merge(DEFAULT_PROFILE_DETAILS, profileDetails);
return <Profile config={config} />;
}
Wait a minute. Zoom in... Enhance!
const config = merge(DEFAULT_PROFILE_DETAILS, profileDetails); //