A QR code generation service must handle encoding, error correction, output formats, and at scale — bulk generation for campaigns, dynamic codes for editable content, and caching to avoid redundant work. Here is the full low level design.
QR Code Encoding Pipeline
Encoding a QR code follows a defined sequence of steps:
- Input analysis — classify data as numeric, alphanumeric, byte, or kanji mode. Numeric mode is most compact; byte mode handles arbitrary UTF-8.
- Data encoding — convert input to a bitstream using the selected mode. Alphanumeric mode encodes pairs of characters into 11-bit values; byte mode encodes each byte as 8 bits.
- Reed-Solomon error correction — append error correction codewords to the data codewords. The number of correction codewords is determined by the error correction level and QR version (size).
- Module placement — arrange data and correction codewords in the QR matrix, interleaved across blocks for burst-error resilience.
- Structural patterns — add finder patterns (three 7×7 corner squares), separator modules, timing patterns, alignment patterns (for versions 2+), and format/version information.
- Mask pattern selection — apply each of 8 mask patterns, score each against penalty rules (runs, 2×2 blocks, finder-like patterns, dark/light balance), select the lowest-penalty mask.
Error Correction Levels
QR codes support four error correction levels, each trading capacity for damage tolerance:
- L (Low) — recovers up to 7% of codewords. Maximum data capacity. Best for clean environments.
- M (Medium) — recovers up to 15%. Good default for most use cases.
- Q (Quartile) — recovers up to 25%. Use when the code may be partially obscured — e.g., printed on packaging.
- H (High) — recovers up to 30%. Required when embedding a logo overlay, since the logo occludes modules. Reduces data capacity significantly.
The service should expose the error correction level as an API parameter and default to M for plain codes and H when branding options are enabled.
Static vs Dynamic QR Codes
Static QR codes encode the final destination URL or data directly into the matrix. Once printed, the content cannot change. They have no server-side dependency after generation.
Dynamic QR codes encode a short redirect URL (e.g., https://qr.example.com/abc123). The redirect target is stored in a database and can be updated without reprinting. This enables:
- Post-print content editing
- A/B testing destination URLs
- Campaign lifecycle management
- Scan analytics via the redirect layer
Dynamic codes should use short slugs (6-8 alphanumeric chars) to keep the encoded URL short, which allows a smaller QR version and lower module density.
Scan Tracking
Each scan of a dynamic code hits the redirect service. Log the following per scan event: timestamp, IP address (hashed for privacy), user agent, country (from IP geolocation), referrer if present. Store in a time-series table or push to an analytics pipeline (Kafka → ClickHouse). Expose an API endpoint for per-code scan counts, time-series breakdown, and geographic distribution.
Custom Branding
Branding options must stay within QR spec constraints to remain scannable:
- Logo overlay — place a logo image in the center, sized to occlude no more than 30% of modules. Requires error correction level H. Composite the logo after mask selection.
- Color customization — dark modules can be any dark color; light modules any light color. Ensure sufficient contrast ratio (minimum 4:1). Avoid reversing colors (light-on-dark requires scanner support).
- Rounded module shapes — replace square modules with rounded rects or dots. Applied as SVG path operations. Does not affect decoding as long as module boundaries remain distinct.
Output Formats and Caching
Support PNG and SVG output. PNG: render at a base module size (e.g., 10px per module) with configurable quiet zone, then resize if a target dimension is specified. SVG: emit a <rect> per dark module or use path compression to merge adjacent modules into a single path element — reduces SVG file size by 60-80%.
Cache generated codes by a hash of all input parameters (data, format, EC level, size, branding config). Store in object storage (S3) with a CDN in front. On a cache hit, return the stored object URL directly without re-encoding. Cache TTL should be long (30+ days) since the same inputs always produce the same output.
Bulk Generation API
For large campaigns (thousands of codes), expose a bulk endpoint: accept a JSON array of code specifications, enqueue each as a job in a task queue (Celery, Bull, or similar), process in parallel workers, and return a job ID. When complete, upload a ZIP of all generated files to object storage and notify via webhook or provide a download URL. Rate-limit the bulk endpoint per API key and enforce a max batch size (e.g., 10,000 codes per request).
Key Data Models
qr_codes table: id, slug (for dynamic), destination_url, ec_level, type (static/dynamic), branding_config (JSON), created_at, owner_id.
scan_events table: id, code_id, scanned_at, country_code, ip_hash, user_agent_hash.
bulk_jobs table: id, status (pending/processing/done/failed), total_count, completed_count, result_url, created_at.
Frequently Asked Questions
What is a QR code generation service in system design?
A QR code generation service accepts a payload (URL, text, or structured data), encodes it into a QR code image, and returns the image or a hosted URL to it. Core responsibilities include encoding the payload per the QR standard (ISO 18004), applying the requested error correction level, rendering the matrix to a raster or vector image format, optionally storing the QR code for dynamic updates, and serving images at scale with low latency via a CDN. Analytics tracking for scan events is a common additional requirement.
What is the difference between static and dynamic QR codes?
A static QR code encodes the final destination payload directly; once printed it cannot be changed. A dynamic QR code encodes a short redirect URL that resolves to the actual destination stored server-side. This means the destination can be updated at any time without reprinting. Dynamic codes also enable scan analytics (count, location, device) because every scan passes through the redirect service. The trade-off is that dynamic codes depend on the redirect service remaining available, whereas static codes work offline.
How do error correction levels affect QR code scanning reliability?
The QR standard defines four error correction levels — L (7%), M (15%), Q (25%), H (30%) — indicating the percentage of codewords that can be restored if the code is damaged. Higher levels add more redundant Reed-Solomon codewords, making the code more resilient to dirt, tearing, or logo overlays, but also increasing the density of the matrix, which requires a larger printed size or higher camera resolution to scan reliably. Level M is the common default; Level H is used when the code will be printed on surfaces prone to damage or when a logo is embedded in the center.
How do you track QR code scans for analytics?
Each dynamic QR code encodes a unique short URL that routes through an analytics redirect endpoint. When a device scans the code, the redirect service logs an event containing the code ID, timestamp, IP-derived geolocation, and user-agent (device type, OS). The event is written to a message queue (e.g., Kafka) for async processing, then aggregated into an OLAP store or time-series database. A dashboard reads aggregated metrics per code or campaign. To preserve privacy, IP addresses are truncated or hashed before storage, and no personally identifiable information is retained beyond what is needed for aggregate analytics.
See also: Shopify Interview Guide
See also: Stripe Interview Guide 2026: Process, Bug Bash Round, and Payment Systems
See also: Scale AI Interview Guide 2026: Data Infrastructure, RLHF Pipelines, and ML Engineering