SDSystem design
Start learning free

LESSON 79 OF 140 · PRACTICE PROBLEMS

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Design a QR Code Service: System Design Interview Practice

The problem

"Design a QR code service": a business enters a web address and gets a QR code to print on menus, posters and packaging. Later it can change where the code leads and see how often it's scanned. A phone's camera reads the picture and opens the web address inside it.

It's the URL shortener with a picture on top: a QR code is a short link drawn as squares. Say so early, reuse the lookup by code and the redirect, and spend the interview on what's new. The calls:

POST /codes            {"url": "https://lunacafe.example/fall-menu"}
  → 201 Created        {"code": "m4Tz8", "image": "…/m4Tz8.png"}

PUT /codes/m4Tz8       {"url": "https://lunacafe.example/winter-menu"}

GET /m4Tz8             (a phone scans the code)
  → 302 Found          Location: https://lunacafe.example/winter-menu

The numbers

Say 10 million new codes a month, and a printed code is never deleted. A code's record is about 500 bytes; its image about 10 KB.

quantity             estimate
codes in 10 years    10 million × 12 × 10 = 1.2 billion
their records        1.2 billion × 500 bytes = 600 GB
their images         1.2 billion × 10 KB = 12 TB

The common slips: counting one year only (60 GB), or giving each record the image's size (12 TB). The records fit on one database server, so keeping every code forever, in 3 copies, is cheap. Scans are the busy path, and they come in bursts: a code in a TV ad can get about 20,000 scans a second.

Decision 1: what the code holds

The printed picture should hold your short link, qr.example/m4Tz8, not the destination. The picture can't change once printed, but the row behind m4Tz8 can: when the café's menu moves, the owner changes the row and every printed menu follows. Every scan also passes through your service, so it can be counted. These are dynamic codes; a code that holds the destination itself is a static one.

That only works with a 302, as in the URL shortener. A 301 tells the phone's browser the move is permanent, so it may skip your service next time: the changed destination never reaches it, and its repeat scans aren't counted. A tag on the destination (?src=qr) lets the café's site count visits, but still can't change where the code leads. A bonus: a short link makes a simpler picture, with fewer, bigger squares that scan more easily.

Decision 2: the image

The image holds only the short link, which never changes, so the image never changes either, even when the destination does. Draw it once, when the code is made (a PNG for screens, an SVG for print), keep it in object storage under the code, and send it through a CDN with a long cache life. Drawing it on every download wastes CPU on identical pictures, and drawing it again on every destination change makes the same picture twice.

Decision 3: changing a destination

Scans read the destination through a cache, as in the URL shortener. There a link never changed; here it does. When the owner changes a code, the service saves the database first, then deletes the cached copy; the next scan misses and reads the new address. The other order breaks: delete first, and a scan in between reads the old address from the database and caches it again. A short lifetime on cached codes is the safety net for anything that slips through.

Decision 4: counting scans

Adding 1 to the code's row inside the redirect makes every scan wait for a write, and a code in a TV ad sends 20,000 updates a second to a row that takes about 5,000 (Distributed Counter). Instead, the redirect puts each scan on a stream and answers at once; workers count per code and per day, as in Ad Click Aggregator, and the raw scans stay available for charts by hour. Counting image downloads is a trap: a scan never fetches the image, since the camera reads the printed picture.

Decision 5: codes that never break

A printed code can't be updated, so it must work for years. It depends on three things: your domain, its row, and a service that answers. That means:

  • No expiry: a poster may go up two years after it was printed; 600 GB for 10 years is cheap.
  • No reuse: a deleted code opens a "no longer active" page, never a new owner's link.
  • Keep the domain: if it lapses, every code ever printed dies at once.
  • Durable rows: several copies, in more than one data center, with backups; the redirect service runs in more than one region.

The images can move or be drawn again at any time: no scan needs them.

Same pattern as the URL shortener

  • A lookup by code and a redirect: the busy path is one cache lookup and a 302.
  • Codes from a counter: blocks of numbers from a shared counter, in base 62, so codes never clash.
  • Read-heavy: each code is made once and scanned many times; the cache takes the scans.

Summary

  1. Scope: the URL shortener's pattern, plus images, changing destinations, scan counts and codes that last.
  2. Data: code → destination, kept forever; each image drawn once, into object storage.
  3. API: the code holds your short link; a scan gets a 302 to the destination.
  4. Numbers: about 1.2 billion codes in 10 years, about 600 GB of records.
  5. Scale: images through a CDN; scans through a cache.
  6. Changes: save the database, then delete the cached copy.
  7. Scans: onto a stream, counted in the background.
  8. Never break: no expiry, no reuse, the domain kept, rows in several copies.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free