Skip to content

Lexicographic sorting broken in version 3.1.0? [PR created] #45

@mmakaay

Description

@mmakaay

After upgrading to 3.1.0, I am getting some unexpected output from the following test code:

$ uv pip list | grep ulid
python-ulid        3.1.0

$ python3
Python 3.13.5 (main, Jun 11 2025, 15:36:57) [Clang 19.1.7 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from ulid import ULID
>>> miss = 0
>>> prev = ""
>>> miss=0
... for i in range(5000):
...     n = str(ULID())
...     if n < prev:
...         miss+=1
...         print(f"next {n} < prev {prev} !! {miss}")
...     prev=n
...
next 01K37AJA5QDYD4BP0K7WP36YBR < prev 01K37AJA5QJR2P2YEZV244F2WQ !! 1
next 01K37AJA5XHK1QAT1VKWG2AMX6 < prev 01K37AJA5XXC3CV5Z9M7T9WHR1 !! 2
>>>

This shows that the IDs are not following monotonic sort order.

Given the fact that for the first miss case, the IDs both start with 01K37AJA5Q (i.e. the timestamp), I suspect that the issue is somewhere in the new feature as mentioned in the 3.1.0 changelog:

When generating ULIDs within the same millisecond, the library will ensure monotonic sort order by incrementing the randomness component by 1 bit.

Looking as some context around the miss

When printing out the generated IDs, there is a clear jump in the output:
(the number at the start is the miss counter)

0 01K37BHXZ8REXFPRZW7KW6NDGC
0 01K37BHXZ8REXFPRZW7KW6NDGD
0 01K37BHXZ8REXFPRZW7KW6NDGE
1 01K37BHXZ8CQKKDCNFZ8EH5SXV
1 01K37BHXZ9CQKKDCNFZ8EH5SXW
1 01K37BHXZ9CQKKDCNFZ8EH5SXX

These all carry the same timestamp. Before and after the jump, there is incrementing going on on the random part, so that looks like the new feature in action.

In the same run, I also got:

1 01K37BHXZ9CQKKDCNFZ8EH5SYT
1 01K37BHXZ9CQKKDCNFZ8EH5SYV
1 01K37BHXZ9CQKKDCNFZ8EH5SYW
2 01K37BHXZ98VYMQHC24B0TNB9V
2 01K37BHXZA8VYMQHC24B0TNB9W
2 01K37BHXZA8VYMQHC24B0TNB9X
--------8<--------
2 01K37BHY08YGRGZNCG5B264WRG
2 01K37BHY08YGRGZNCG5B264WRH
2 01K37BHY08YGRGZNCG5B264WRJ
3 01K37BHY08HV9Y6H8VHCFNTQRM
3 01K37BHY09HV9Y6H8VHCFNTQRN
3 01K37BHY09HV9Y6H8VHCFNTQRP

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions