RetrospectiveMoving to Programmers After Baekjoon’s Shutdown, and Rethinking External Dependencies
· AlgoSu
- #external-api
- #platform-migration
When "Just Use Baekjoon" Broke
If you've studied algorithms in Korea, you've almost certainly used Baekjoon Online Judge (BOJ), the country's go-to problem-solving site, think LeetCode for Korean developers. It had been around for more than a decade. So when I saw its shutdown notice, my first feeling wasn't shock. It was closer to awkwardness. "Baekjoon is shutting down?" I knew, intellectually, that any service can end. I just hadn't actually felt it.
AlgoSu was built on Baekjoon. Problem data, difficulty badges, the submission flow, GitHub sync, AI feedback: Baekjoon was woven into all of it. After reading the notice, what I had to face wasn't a code problem. It was an assumption I'd been carrying without noticing.
Problem
AlgoSu relied on an external platform (Baekjoon) for its problems, and a shutdown notice arrived. I knew about the dependency, but the assumption "surely not Baekjoon" was still there.
Decision
Move the problem source to Programmers over three sprints (short work cycles), shipping a hand-picked set of problems as JSON files instead of scraping live, and keeping the existing Baekjoon data alongside.
Result
373 problems landed with zero DB migrations and zero regression for existing users. The sourcePlatform (source site) column added early turned out to be a survival condition.
A Dependency I Knew About
Don't get me wrong. I knew AlgoSu depended on Baekjoon. I'd known from the start, which is exactly why I'd put a few safety nets into the design early on. (Baekjoon problem data came through Solved.ac, a community service that adds difficulty ratings and tags to Baekjoon problems.)
- A
sourcePlatformcolumn on the problem table, recording which site a problem comes from, added early - The API that fetches external problem data split into its own module, at the edge of the Gateway, the service that receives every request
- Difficulty values (enums) and UI design tokens designed independently of any one platform
The abstractions were already there. I did think another platform might be added someday.
But somewhere in the back of my mind, I'd quietly assumed: "No way, not Baekjoon." I had planned for another platform being added. I hadn't planned for Baekjoon disappearing. Adding is about extensibility; disappearing is about survival. I'd designed for the first and put off the second.
Nothing Lasts Forever
What Baekjoon's shutdown notice broke wasn't my code. It was that quiet assumption.
The old assumption
- Baekjoon can't disappear
- external APIs are always callable
- this platform is the exception
The new assumption
- Baekjoon can disappear
- external APIs can be cut off at any time
- there are no exceptions
That was the one-line lesson of this migration. Technically, there wasn't much I didn't already know. If anything, it confirmed that the design decisions I'd already made were right. But the attitude behind those decisions (design on the assumption that nothing lasts) only sank in this time.
A Three-Sprint Migration
The new source was Programmers, a Korean coding-test and practice-problem platform.
My first instinct was to cram the entire migration into one sprint: data, backend, frontend, submissions, and docs, all at once. I stopped myself while drafting the plan. The risk of breaking existing features was too high. So I split it into a three-sprint roadmap.
Sprint 95
Backend Infrastructure
Hand-picked 373 Programmers problems, stored them as JSON files in the repository, and added a Gateway endpoint,
/api/external/programmers/*, shaped the same as the Baekjoon one. The rule: zero user-visible change, zero Baekjoon regression, infrastructure only.Sprint 96
Frontend UX
Added a platform toggle to the add-problem dialog (
AddProblemModal), Programmers problem search (theuseProgrammersSearchhook), and made Programmers the default. This was the first change users could actually see.Sprint 97
Pipeline Closure
Extended
formatPlatform()so the service that pushes solutions to users' GitHub (GitHub Worker) prefixes Programmers files withprg_. Fed the source platform (sourcePlatform) into the AI feedback prompt, enriched tags, and verified with web accessibility checks (WCAG AA) and end-to-end tests.
Looking back, the decision I'm most grateful for is bundling a hand-picked set of problems as JSON in the repository instead of scraping them live.
Programmers has no public API. On top of that, Cloudflare's JA3 blocking was in the way. It is a feature that fingerprints the TLS handshake of the connecting client (JA3) and blocks automated, non-browser requests. I could have built a live scraper anyway, but that would have planted yet another external dependency.
Pulling the data into our repository and owning it was partly about operational stability. But it was also the very lesson I'd just learned (external APIs can be cut off at any time) applied straight back into the implementation.
And the existing Baekjoon data? I didn't delete it. Zero migration; the two platforms simply coexist. Every problem a user had solved on Baekjoon stayed in their history, even after the shutdown notice. Platforms can disappear, but what users built on top of them should stay alive inside our service.
A structure that survives a change of problem source
The Outcome
373
Programmers problems imported
Lv.1–5 mapped 1:1 to BRONZE–DIAMOND (AlgoSu's difficulty tiers), zero design-token changes
2,445
tests ALL PASS
Web accessibility WCAG AA 6/6 PASS
0
DB migrations
zero regression for existing Baekjoon users
The Programmers Gateway endpoints mirror the existing Baekjoon ones, so the abstraction layer stayed intact.
More meaningful than the numbers was the shape of the change: we added a new platform without breaking the old one. An external shock, absorbed inside the internal structure.
Lessons for Dependent Services
That's the technical story. What follows is the real conclusion of this migration.
Erase "Surely Not" from the Design
Knowing about a dependency and designing for it to be cut off are not the same thing. I had done the first and put off the second. The sourcePlatform column was about extensibility: "another platform might be added someday." It wasn't about survival: "Baekjoon might disappear."
The two assumptions look similar but carry different weight. Extensibility is "nice to have." Survival is "can't live without." As long as "surely not this platform" lingers anywhere in the design, you'll be a beat late when the day actually comes.
I had the basic abstractions in place, but not deep enough, and that showed two sprints later, in five rounds of QA I ran myself as PM. Difficulty badges couldn't tell platforms apart. The week-calculation logic assumed Baekjoon. Four call sites in the study-room screens weren't passing the sourcePlatform value down. All of it was residue from "surely not here, too."
External APIs Can Be Cut Off at Any Time
I chose pre-bundled data over live scraping because there was no official API, Cloudflare was blocking automated requests, and the problem set was fixed at 373 and rarely grew. It was a decision that put operational stability first, and it ended up removing one more layer of external dependency.
Since this migration, the order of my questions about external APIs has changed. I used to start with "Is this API stable?" Now I start with "Will the service still work if this API is gone?" If the answer is no, I either confine the logic that touches the API to a narrower boundary, or find a way to bring data ownership back to our side.
Abstraction Isn't "Nice to Have": It's a Survival Condition
Only after this migration did I realize how much work one sourcePlatform column did. Without it, I'd have had to handle a DB migration, reclassifying existing records, and preserving user history all at once. When I added that column back at the start, my reasoning was closer to "just in case." That "just in case" came.
A lot of abstraction decisions look like this. They feel excessive in the moment, and adding one more layer takes effort. But in systems that lean on external services, that one layer is often the lifeline. "That overly cautious boundary saved me later" isn't a new experience for me. It probably won't be the last.
Closing
I didn't learn much new technology in this migration. I wrote a collection crawler with Playwright, a browser-automation tool, to build the JSON bundle; designed the bundle structure; and narrowed inputs with an allowed-values check (@IsIn). All combinations of things I already knew.
What I learned was an attitude. No platform lasts forever. If a service as established as Baekjoon can disappear, any external service we lean on today can be cut off tomorrow. All we can do is build that possibility into the design: drop the idea that "this one platform is the exception," and when deciding how deeply to depend on something, also sketch out how we'd survive without it. That's how services built on someone else's ground stay standing.
Baekjoon is going. AlgoSu stays. I don't know which platform disappears next. But when it does, I hope the me of that day is a little less surprised.