Pure Sudoku looks like a quiet 9-by-9 grid. Behind that grid is a growing set of systems: a SwiftUI iPhone app, a Jetpack Compose Android app, a browser game, multiplayer matchmaking, daily puzzles and leaderboards, push notifications, an editorial platform, static news pages, PostgreSQL and a blue-green deployment pipeline.

We did not begin by designing all of that. The first iOS code landed on September 12, 2025. Multiplayer followed in October, then daily challenges, accounts and streaks. By July 2026, the codebase included native news, a second mobile client and an API that coordinated games, publishing and notifications. The architecture grew because the product grew.

The engineering lesson is not that every Sudoku app needs a platform. It is that a small product can gain distributed-system problems surprisingly quickly. Our job was to keep those problems away from the part players came for: a fast, dependable puzzle.

The short answer

We split Pure Sudoku along lines of responsibility. Puzzle rules stay close to the player and can run without a network. Shared facts—who joined a match, which daily puzzle is current, who finished first, whether an article is published—belong to the server. PostgreSQL protects durable state. Static sites make public pages fast and crawlable. Platform adapters isolate Apple, Google, Firebase, advertising, purchases and analytics from the game itself.

That sounds obvious when written after the fact. It was earned through refactoring. Early features naturally pulled networking, presentation and business rules toward the same files. As the app added more surfaces, we moved toward MVVM and Clean Architecture: views render state and forward intent; view models coordinate use cases; domain code owns rules; gateways hide frameworks and remote APIs.

Keep the puzzle engine pure

The most important boundary is around Sudoku itself. Generating a board, solving it, checking it and proving that it has one solution do not require an account, a database or an SDK. On iOS, those operations live in entities and use cases written in Swift. On Android, their Kotlin counterparts live in a framework-free domain module.

The generator starts with a valid completed grid, shuffles its structure, then removes symmetric pairs of values. After each removal, the solver checks that the puzzle still has a unique solution. Difficulty controls the target clue count. The solver uses bit masks and searches the cell with the fewest candidates first, which keeps the work quick enough to happen on a phone without blocking the interface.

That local-first decision gives us three benefits. Solo play works without a round trip. Domain rules are easy to unit-test. And an outage in analytics, ads or the API cannot make a basic Sudoku grid invalid.

Share behavior, not source code

We did not force Swift and Kotlin through a cross-platform layer. Instead, we made the product behavior the shared contract. Both apps expose the same difficulty levels, board rules, match states, daily challenge concepts and deep-link shapes. Each platform then implements those contracts using its native strengths.

On iOS, SwiftUI views observe view models and Swift concurrency keeps long-running work away from the main thread. On Android, Compose screens observe immutable state flows, while coroutines live inside lifecycle-aware view models. Concrete networking, storage, purchases and telemetry are assembled at the outside edge. The inner game rules do not know whether they are running under UIKit, Compose, StoreKit or Google Play Billing.

This approach creates some duplication, but it is deliberate duplication. A shared abstraction is valuable only when it makes both clients simpler. Product parity matters more than identical files.

Make the server authoritative only where it must be

The API is a TypeScript application built with Fastify, PostgreSQL and runtime validation. It handles accounts, daily challenges, matchmaking, match state, news, comments, email and push campaigns. It does not render the native interfaces and it does not need to participate in every tap on a solo board.

For multiplayer, the server owns the shared record. Friend matches use invite codes and participant tokens. Quick Match places players into a queue. Both players receive the same puzzle, and the server records join, heartbeat, board and completion events. Solve Together adds a shared board with a revision number so clients can reconcile edits instead of silently overwriting newer state.

We chose ordinary HTTP polling and heartbeats instead of making a persistent socket connection the first dependency of the product. The clients poll match state about every three seconds and send a slower presence heartbeat. They cancel those tasks with the screen lifecycle and resume them when the app returns. It is less glamorous than a real-time transport, but it is easy to observe, retry and operate. For a Sudoku race, that tradeoff has been practical.

Assume every request can happen twice

Mobile networks retry, people double-tap and processes restart at awkward moments. Once daily times and streaks became competitive, accepting a completion twice was no longer a cosmetic bug.

Daily submissions now carry a submission identifier. The server first claims a durable receipt in PostgreSQL, then mutates attempts and streaks only for a new receipt. Database transactions, row locks and uniqueness constraints protect other contested paths, including match transitions and account recovery. In other words, correctness does not depend on the client behaving perfectly.

A Pure Sudoku phone beside a staged receipt gate that accepts one completion token and diverts an identical duplicate.
A durable receipt is claimed before a daily attempt changes competitive state, so a retry cannot count twice.

This is one of the clearest ways the system matured. Reliability work often looks smaller than a new screen, but it changes the product more deeply. A leaderboard that is correct after a timeout is part of the game design.

Separate content from presentation

PS News is not a folder of hand-edited HTML articles. Article data lives behind the API: title, dek, category, body, sources, editorial flags, comments and images. The news website fetches published records during its build and generates static home, category and article pages, along with sitemaps and metadata. The mobile apps read the same article API and present native feeds.

That separation gives one article several outputs without making any of them canonical. Editors update the content once. The API triggers a site build. Search engines receive static, crawlable pages. App readers receive native navigation, likes, comments and push routing. The public website can change its typography without rewriting article data, and the apps can change their interface without changing the article URL.

Deploy each surface independently

Pure Sudoku is a family of repositories because the release boundaries are real. The iOS app goes through App Store review. Android has its own Gradle build, signing and Play testing path. The main website and news site are static deployments. The API and database need a different rollback story.

Production API releases use blue-green containers behind a stable local Nginx proxy. A deploy runs database migrations, builds the inactive color, health-checks it, switches the proxy and only then stops the previous color. The prior application version remains available for rollback, so database changes are designed to remain backward-compatible. Caddy terminates TLS and routes public API and admin traffic to that stable entry point.

The static sites follow a simpler path: GitHub Actions builds or verifies the files, packages them and replaces the deployed directory on the server. Publishing a PS News article can trigger that pipeline automatically. Keeping those paths separate means a news template change does not require shipping a new mobile binary, and an iOS release does not put the API at risk.

What we would keep doing

The architecture is not finished, and that is the point. We will keep moving business rules inward, tightening contracts at system boundaries and adding tests around failures rather than only happy paths. We will also resist infrastructure that is more sophisticated than the current problem.

The best systems decision we made was to preserve the original promise. A player should be able to open Pure Sudoku and see a clean grid immediately. Accounts, multiplayer, news, notifications and deployment machinery can add value, but none of them should make the puzzle feel heavy.

From the outside, Pure Sudoku is still 81 cells and a handful of numbers. From the engineering side, it is a lesson in growing a product without letting its architecture become the product.

Engineering sources

This account was verified against the internal Pure Sudoku repositories and release history. Public product references include the Pure Sudoku browser experience and the iPhone App Store listing.