Art of Codeartofcode
LLeeaarrnniinngg
BBlloogg
TToooollss
GGaammeess AArreennaa
JJoobbss
LLiivvee CCoouurrsseess

Courses

  • System DesignHigh-level system design, one everyday scene at a time: 30 concepts and 15 interview rounds.712 lessons
All coursesTutorialsMy Learning

Categories

  • 01AI & Tools
  • 02Tutorials
  • 03Engineering
  • 04Reviews
  • 05Build Logs
  • 06Tales of Misfits
  • 07Opinion
  • 08Career
  • 09News

Read & write

All storiesEvery post, newest first.Write for usPublish your stories and earn from what you write.Writer leaderboardsWho is writing the most read posts.

Categories

  • PDF Tools25
  • Development09
  • Utilities10
  • Design04
  • Converters04
  • Generators02

Featured

JSON FormatterFormat, validate and minify JSON.Regex TesterTest patterns against live text.Merge PDFCombine PDFs in your browser.JWT DecoderRead a token’s header and claims.Password GeneratorStrong passwords, generated locally.Gradient GeneratorBuild CSS gradients visually.
All 53 tools

Play a round

DevdleOne developer word a day, six guesses.Python RushClassic snake, with coding icons to eat.Blink or BreakA quick test of your reflexes.
Games Arena

Learn with us

Live CoursesInstructor-led courses. Join a waitlist and be first in when one starts.LearningStructured programming courses with AI assistance and certificates.

System Design

10. Unique ID Generation

Lesson 14 of 16

10. Unique ID Generation

1
The picture to keep in your head
2
10.1 What makes a good ID?
3
10.2 What's wrong with auto-increment?
4
10.3 Why does auto-increment break across many servers?
5
10.4 What are UUIDs, and which version should you use?
6
10.5 How do Snowflake IDs work?
7
10.6 What are ticket servers and ID ranges?
8
10.7 Why does clock skew matter for time-based IDs?
9
10.8 How do you make short IDs with base62?
10
10.9 Should IDs be guessable or unguessable?
11
Memory card and where the analogy breaks
12
Practice: five levels
13
Interview questions
14
Solutions
15
Sources
16
Code lab
10. Unique ID Generation

Solutions

3 min read+10 XP

Solutions

Level 1

reveal

Level 2

reveal

Level 3

reveal

Level 4

reveal

Level 5 (sketch)

reveal

No tracks available

Lofi Player

artofcode

artofcode.in

The den of devs.

Stories, tools, games, and gear for developers.

Read the StoriesEExxpplloorree TToooollss
Art of Codeartofcode

Platform

  • BBlloogg
  • TToooollss
  • GGaammeess AArreennaa
  • CCoouurrsseess
  • LLeeaarrnniinngg
  • CCaammppuuss

Company

  • AAbboouutt UUss
  • JJoobbss && IInntteerrnnsshhiippss
  • CCoonnttaacctt

Legal

  • TTeerrmmss ooff SSeerrvviiccee
  • PPrriivvaaccyy PPoolliiccyy
  • RReeffuunndd PPoolliiccyy

Copyright © 2026 artofcode.in, All Rights Reserved.

IST·--:--

1Show the Level 1 solution
2**A.**
31. **Auto-increment.** One database; simplest and fastest.
42. **Snowflake-style or UUID v7.** Very high write rate, no central counter, time-ordered.
53. **Long random token** (at least 128 bits from a secure random generator), single-use and expiring.
64. **Counter + ranges + base62** (optionally scrambled), or random codes with a uniqueness check (Chapter 31).
75. **Snowflake-style IDs** (fit in BIGINT, roughly time-ordered, no per-ID coordination), optionally with the shard ID inside.
8
9**B.**
101. **False.** Always check authorisation. IDs leak through logs, emails and shared links.
112. **False.** Different machines' clocks differ by a few milliseconds, so the order is only approximate.
123. **False.** Rolled-back transactions and crashes leave gaps; count rows with `COUNT(*)` or a counter.
Show the Level 2 solution
1. 2⁴¹ ms ÷ (1,000 × 86,400 × 365.25) ≈ **69.7 years**. With 39 bits: a quarter of that, about **17.4 years**.
2. 2¹² = **4,096 per millisecond** per machine → **4,096,000 per second** per machine. With 1,024 machines: about **4.2 billion a second** in total.
3. 62¹⁰ ≈ 8.4 × 10¹⁷ is less than 2⁶⁴ ≈ 1.8 × 10¹⁹, and 62¹¹ ≈ 5.2 × 10¹⁹ is more, so **11 characters**. For 6 billion links: 62⁵ ≈ 0.9 billion is too few, 62⁶ ≈ 56.8 billion is enough, so **6 characters**.
4. (10¹²)² ÷ (2 × 2¹²²) ≈ **9.4 × 10⁻¹⁴**, about 1 in 10 trillion.
1Show the Level 3 solution
2Tested solution (Python 3), the key parts:
3
4```python
5EPOCH_MS = 1_767_225_600_000   # 2026-01-01 00:00:00 UTC
6MACHINE_BITS, SEQ_BITS = 10, 12
7MAX_MACHINE, MAX_SEQ = (1 << MACHINE_BITS) - 1, (1 << SEQ_BITS) - 1
8
9
10class Snowflake:
11    def __init__(self, machine_id, clock_ms):
12        self.machine_id, self.clock_ms = machine_id, clock_ms
13        self.last_ms, self.seq = -1, 0
14
15    def next_id(self):
16        now = self.clock_ms()
17        if now < self.last_ms:
18            raise ClockMovedBackwards(f"clock went back {self.last_ms - now} ms")
19        if now == self.last_ms:
20            self.seq = (self.seq + 1) & MAX_SEQ
21            if self.seq == 0:                     # 4,096 used this millisecond: wait
22                while now <= self.last_ms:
23                    now = self.clock_ms()
24        else:
25            self.seq = 0
26        self.last_ms = now
27        return ((now - EPOCH_MS) << (MACHINE_BITS + SEQ_BITS)) | (self.machine_id << SEQ_BITS) | self.seq
28
29
30def uuid7(ms):
31    rand = int.from_bytes(os.urandom(10), "big")
32    value = (ms & ((1 << 48) - 1)) << 80          # 48-bit timestamp
33    value |= 0x7 << 76                            # version 7
34    value |= ((rand >> 62) & 0xFFF) << 64         # 12 random bits
35    value |= 0b10 << 62                           # variant
36    value |= rand & ((1 << 62) - 1)               # 62 random bits
37    return uuid.UUID(int=value)
38```
39
40All checks pass. A sample ID from the test run, `4198518784`, decodes to machine 5, sequence 0, one second after the epoch, and is `4A8ywU` in base62. The full file is in `code/ch10_ids.py`.
1Show the Level 4 solution
2**The bug:** when the clock steps **backwards**, `now != last_ms`, so the code treats it as a fresh millisecond and **resets the sequence to 0**.
3
41. At 500 ms, the server creates ID (500, machine 7, seq 0).
52. NTP corrects the clock back to 499 ms. The next ID is (499, machine 7, seq 0), with the sequence reset.
63. The clock reaches 500 ms **again**. `now (500) != last_ms (499)`, so the sequence resets to 0 again.
74. The ID is (500, machine 7, seq 0): **identical to step 1**. The insert fails with a duplicate key, or worse, if IDs are used outside the database, two things share one ID.
8
9**The fix:** never go back in time. Remember the last timestamp; if the clock is behind it, wait (small steps) or refuse (big steps):
10
11```python
12def next_id(self):
13    now = self.clock_ms()
14    if now < self.last_ms:
15        if self.last_ms - now <= 5:          # small step back: wait it out
16            while now < self.last_ms:
17                now = self.clock_ms()
18        else:
19            raise ClockMovedBackwards(self.last_ms - now)
20    if now == self.last_ms:
21        self.seq = (self.seq + 1) & MAX_SEQ
22        if self.seq == 0:
23            while now <= self.last_ms:
24                now = self.clock_ms()
25    else:
26        self.seq = 0
27    self.last_ms = now
28    return ((now - EPOCH_MS) << 22) | (self.machine_id << 12) | self.seq
29```
30
31Also fix the cause: configure time sync to slew rather than step where possible, and alert on large clock corrections.
1Show the Level 5 (sketch) solution
21. **Order IDs:** Snowflake-style 64-bit: 41 bits of time from a 2026 epoch, **3 bits of shard** (8 shards; leave room by using more bits, say 5 for up to 32), the remaining bits split between generator ID and sequence. Fits BIGINT, roughly time-ordered, and the shard can be read from the ID.
32. **Generator IDs:** leased at startup from a coordinator (ZooKeeper or etcd) with a time-limited lease, so a crashed server's ID can be reused only after it's surely gone; or set per deployment in configuration and checked for duplicates.
43. **Clock backwards:** wait for small steps, refuse and alert for large ones; time sync set to slew; monitor clock offset on every generator.
54. **Wishlist links:** a separate **random** public token (UUID v4 or 16 random bytes in base62, about 22 characters), stored next to the internal ID, revocable by the owner.
65. **Existing IDs:** keep them. Start the new scheme's values well **above** 40 million (any Snowflake ID with a real timestamp is already in the trillions), so old and new never collide and both fit the same BIGINT column.