Skip to content

Pseudo-Links

A pseudo-link allows you to interpret any 40-bit index as a stateless, O(1)O(1) relation between a set of related entities. This means you can establish relationships between entities without storing any connection data - the relationship exists purely through shared bit patterns.

The key insight: by treating an index as structured coordinates (neighborhood, connector, island) rather than a scalar value, you can instantly navigate between related indices through simple bitwise operations. The indices can point to items in the same PseudoArray (same object type) or different arrays (like users and groups). When working with Pseudo-IDs, a pseudo-link operates on the 40-bit index component, while the world-seed and type-sequence remain unchanged.

In traditional systems, determining relationships requires database lookups or junction tables. With pseudo-links, you manipulate bits to calculate related indices - no database needed, no special encoding required. Any 40-bit value is valid; pseudo-links provide bitwise operations to construct, modify, and navigate through indices as if they were coordinates in a multi-dimensional space.

PseudoLink Bit Layout

A pseudo-link divides a 40-bit Index into three configurable zones:

Configurable Bit Layout:

  • Island (High bits, bits 23-39): Configurable width (default: 17 bits). You specify this in the constructor. These bits determine sharding - they remain unchanged during relationship calculations, ensuring related indices always hash to the same partition.
  • Neighborhood (Middle bits, bits 3-22): Configurable width (default: 20 bits). You specify this in the constructor. Indices sharing the same neighborhood and island bits are considered related.
  • Connector (Low bits, bits 0-2): Calculated automatically as remaining bits (default: 3 bits = 40 - 17 island - 20 neighborhood). This is the “slot” that gets modified to navigate between related indices. With 3 bits, you get 8 possible relationships per neighborhood.

The bit layout is configurable. You choose islandBits and neighborhoodBits in the constructor. The remaining bits (40 - islandBits - neighborhoodBits) automatically become the connector. Choose your layout based on your requirements: more connector bits = more relationships per neighborhood, more island bits = finer sharding granularity.

Why this bit order? Island occupies the high bits to enable simple range-based sharding (index(40islandBits)index \gg (40 - islandBits) or index/223\lfloor index / 2^{23} \rfloor). This creates contiguous shard ranges: shard 0 contains indices [0, 2232^{23}), shard 1 contains [2232^{23}, 2×2232 \times 2^{23}), etc. Connector sits on the low bits for efficient bit manipulation. Neighborhood uses the middle bits, grouping related entities together while preserving the island value.

Relationships are calculated by performing bitwise operations on the connector bits. The resolve() function replaces the connector segment with a new value, generating a related index that shares the same neighborhood and island bits.

For self-referencing relationships (where a model links to itself), use resolveWithTeleport() instead of resolve() to prevent entities from referencing themselves by XORing the neighborhood bits. See Same-Entity Relationships below.

The Interface tab shows the PseudoLink API reference. Examples throughout this document assume this interface.

The Implementation tab contains a complete reference implementation useful for porting to other languages.

/**
* Coordinate components of a 40-bit index
*/
interface LinkComponents {
island: number;
neighborhood: number;
connector: number;
}
/**
* PseudoLink
* A coordinate system for manipulating 40-bit indices.
* Provides O(1) navigation between related indices through bitwise operations.
* Operates on plain numbers - no special encoding required.
*/
declare class PseudoLink {
/**
* @param islandBits - Number of bits used for sharding (default: 17).
* @param neighborhoodBits - Number of bits for grouping (default: 20).
* The remaining bits (40 - islandBits - neighborhoodBits) become the connector.
* Choose your layout based on requirements: more connectorBits = more relationships.
*/
constructor(islandBits: number, neighborhoodBits: number);
/**
* Constructs a 40-bit index from coordinate components.
* @param island - Island value (0 to maxIsland()).
* @param neighborhood - Neighborhood value (0 to maxNeighborhood()).
* @param connector - Connector value (0 to maxConnector()).
* @returns A 40-bit index with the specified coordinates.
*/
public encode(island: number, neighborhood: number, connector: number): bigint;
/**
* Extracts coordinate components from a 40-bit index.
* @param index - Any 40-bit index value.
* @returns The island, neighborhood, and connector components.
*/
public decode(index: bigint | number): LinkComponents;
/**
* Calculates a related index by modifying the connector bits.
* @param sourceIndex - Any 40-bit index value.
* @param connector - The target connector value (0 to maxConnector()).
* @returns A related index sharing the same neighborhood and island bits.
*/
public resolve(sourceIndex: bigint | number, connector: number): bigint;
/**
* Resolves a relationship with neighborhood teleportation.
* Prevents self-references by XORing the neighborhood bits.
*
* @param sourceIndex - Source 40-bit index value.
* @param connector - The target connector value (0 to maxConnector()).
* @param distance - XOR distance for neighborhood teleportation (typically 1).
* @returns A related index in a parallel neighborhood (same island, different neighborhood).
*/
public resolveWithTeleport(sourceIndex: bigint | number, connector: number, distance: number): bigint;
/** Returns the maximum island value (2^islandBits - 1). */
public maxIsland(): number;
/** Returns the maximum neighborhood value (2^neighborhoodBits - 1). */
public maxNeighborhood(): number;
/** Returns the maximum connector value (2^connectorBits - 1). */
public maxConnector(): number;
}

Pseudo-links enable several relationship patterns through coordinate manipulation. Unlike traditional database relationships, these patterns are stateless and deterministic—the relationship exists purely through shared bit patterns.

Cross-Type Many-to-One Pattern

Linking Two Entities Diagram

Use resolve() to navigate from one entity type to another at a specific connector. Multiple entities in the same neighborhood and island will resolve to the same related entity at that connector, creating a many-to-one relationship.

Coordinate Structure:

  • Source entity at any index within a neighborhood (e.g., User at index 1000)
  • Use resolve(sourceIndex, connector) to find related entity
  • All entities in the same neighborhood and island resolve to the same target entities
  • Different connector values navigate to different related entities (e.g., connector 0 = primary address, connector 1 = billing address)

Characteristics:

  • Cardinality: Many-to-one (multiple Users → one Address at connector 0)
  • Navigation: Use resolve() with specific connector value, O(1)O(1) access
  • Shard Safety: Automatic (same island = same shard)
  • Sharing: All users in neighborhood N:1000 share the same addresses at each connector
  • Use Case: Shared resources (Users→Addresses, Orders→ShippingMethods)

Example: Users with the same island and neighborhood but different connectors all resolve to the same primary address when using connector 0.

import "github.com/pseudata/pseudata"
const worldSeed = int64(42)
users := pseudata.NewUserArray(worldSeed)
addresses := pseudata.NewAddressArray(worldSeed)
link := pseudata.NewPseudoLink(17, 20)
// Alice at explicit coordinates (I:1, N:1000, C:0)
aliceIdx := link.Encode(1, 1000, 0)
alice := users.At(aliceIdx)
// Find Alice's primary address at connector 0 (same I:1, N:1000)
addressIdx := link.Resolve(aliceIdx, 0)
address := addresses.At(addressIdx)

Within-Neighborhood Complete Graph Pattern

One-to-Many Relationship Diagram

Entities sharing the same island and neighborhood but different connector values form a complete graph—every entity relates to every other entity in that cell. With 3 connector bits, you get 8 related entities (connectors 0-7).

Coordinate Structure:

  • Fixed: island (e.g., 1) and neighborhood (e.g., 1000)
  • Variable: connector (0 to 2connectorBits12^{connectorBits} - 1)
  • Use resolve(index, c) to navigate between connectors

Characteristics:

  • Cardinality: Many-to-many (8 entities with default 3 connector bits)
  • Navigation: Change connector value, preserving island and neighborhood
  • Shard Safety: Guaranteed (all share same island bits)
  • Complete Graph: Every entity in the cell relates to every other entity
  • Use Case: Symmetric groups (Users in a chatroom, Students in a class section)

Mathematical Property: This creates KNK_N where N=2connectorBitsN = 2^{connectorBits}. With 3 connector bits, every neighborhood contains a complete graph of 8 vertices.

Limitation: Fixed cardinality—you cannot add more relationships than available connector slots. Increasing connector bits reduces neighborhood bits, changing the trade-off between relationship density and grouping capacity.

import "github.com/pseudata/pseudata"
const worldSeed = int64(42)
users := pseudata.NewUserArray(worldSeed)
groups := pseudata.NewGroupArray(worldSeed)
link := pseudata.NewPseudoLink(17, 20)
// Alice at explicit coordinates (I:1, N:1000, C:0)
aliceIdx := link.Encode(1, 1000, 0)
alice := users.At(aliceIdx)
// Find all groups Alice belongs to (iterate through all connector slots)
userGroups := []Group{}
for slot := 0; slot <= link.MaxConnector(); slot++ {
groupIdx := link.Resolve(aliceIdx, slot)
userGroups = append(userGroups, groups.At(groupIdx))
}

Single Connector Selection Pattern

Many-to-One Relationship Diagram

A specialization of the one-to-many pattern where you use a single connector value by convention. Multiple entities in the same neighborhood and island but different connector positions all resolve to the same target entity at a specific connector, creating a many-to-one relationship.

Coordinate Structure:

  • Source entities in the same neighborhood and island (different connectors)
  • Fixed target connector by convention (e.g., always use connector 0 for “primary”)
  • All source entities resolve to the same target entity at that connector

Characteristics:

  • Cardinality: Many-to-one (multiple Users → one Group at connector 0)
  • Navigation: Use resolve() with fixed connector value
  • Shard Safety: Maintained through same island
  • Convention: Application defines semantic meaning (e.g., connector 0 = primary group)
  • Use Case: Primary associations (Users→PrimaryGroup, Orders→DefaultWarehouse)

Example: Users at different connector positions within the same neighborhood (C:0, C:1, C:2, etc.) all use connector 0 to find their shared primary group.

import "github.com/pseudata/pseudata"
const worldSeed = int64(42)
users := pseudata.NewUserArray(worldSeed)
groups := pseudata.NewGroupArray(worldSeed)
link := pseudata.NewPseudoLink(17, 20)
// Alice at explicit coordinates (I:1, N:1000, C:0)
aliceIdx := link.Encode(1, 1000, 0)
alice := users.At(aliceIdx)
// Find Alice's primary group (connector 0)
primaryGroupIdx := link.Resolve(aliceIdx, 0)
primaryGroup := groups.At(primaryGroupIdx)

Neighborhood Teleportation Pattern

Same-Entity Relationship Diagram

When a model links to itself (e.g., User → User for manager relationships), naive approaches can create self-references where an entity points to itself. Pseudata solves this with neighborhood teleportation using the resolveWithTeleport() method.

The Problem:

Without teleportation:
Source: Island=1, Neighborhood=1000, Connector=0
Target: Island=1, Neighborhood=1000, Connector=0
❌ Same entity! (self-reference)

The Solution:

With distance=1 teleportation:
Source: Island=1, Neighborhood=1000, Connector=0
Target: Island=1, Neighborhood=1001, Connector=0 (1000 XOR 1 = 1001)
✅ Different entity! (no self-reference)

How It Works:

  • The distance parameter is XORed with the neighborhood bits
  • distance: 1 teleports from neighborhood 1000 to 1001
  • Same island (shard-safe), different neighborhood = different entity
  • Mathematical guarantee: NdistanceN( when distance0)N \oplus \text{distance} \neq N (\text{ when } \text{distance} \neq 0)

Characteristics:

  • Cardinality: Prevents self-references automatically
  • Navigation: Use resolveWithTeleport(sourceIndex, connector, distance)
  • Shard Safety: Maintained (same island value)
  • Use Case: Organizational hierarchies (Employee→Manager), mentorship, buddy systems

Pattern Example: Manager Hierarchy

Employee at N:1000 → Manager at N:1001 (distance=1)
Manager navigates back to find all reports at N:1000
import "github.com/pseudata/pseudata"
users := pseudata.NewUserArray(42)
link := users.LinkAt(1000)
// Forward: Find manager (teleport to parallel neighborhood)
manager := link.Manager() // Uses ResolveWithTeleport(index, 0, 1)
fmt.Printf("%s reports to %s\n", link.Me().Name, manager.Name)
// Reverse: Find direct reports (teleport back)
fmt.Printf("%s manages:\n", link.Me().Name)
for report := range link.Directs() {
fmt.Printf(" - %s\n", report.Name)
}

Multiple Self-Relations:

Use different distance values to teleport to unique parallel neighborhoods:

Employee at Neighborhood 1000:
- manager() with distance=1 → Neighborhood 1001
- mentor() with distance=3 → Neighborhood 1003
- buddy() with distance=5 → Neighborhood 1005

Each relationship uses resolveWithTeleport() with a different distance to guarantee no collisions between relationship types.

Direct Usage Example:

const link = new PseudoLink(17, 20);
// Employee at index with neighborhood 1000
const employeeIdx = link.encode(1, 1000, 0);
// Find manager in parallel neighborhood (1000 XOR 1 = 1001)
const managerIdx = link.resolveWithTeleport(employeeIdx, 0, 1);
// Find mentor in different parallel neighborhood (1000 XOR 3 = 1003)
const mentorIdx = link.resolveWithTeleport(employeeIdx, 1, 3);

See the Using Relations guide for complete examples with the @link decorator and generated Link classes.


Symmetric and Convention-Based Patterns

Bidirectional Relationship Diagram

Bidirectional relationships depend on the pattern used:

Within-Neighborhood (Automatic Symmetry):

  • Given index at (I:1, N:1000, C:0), resolve to C:3 → related entity
  • From that entity at (I:1, N:1000, C:3), resolve back to C:0 → original entity
  • Symmetry is mathematical—both directions use the same coordinate system
  • No storage or convention needed

Cross-Neighborhood (Convention-Based):

  • Relationship is unidirectional by default (Alice → Bob)
  • Bidirectional requires conventions or stored mappings
  • Forward: Alice (N:1000) → Bob (N:2000) via construction
  • Reverse: Bob (N:2000) → Alice (N:1000) via coordinate convention or lookup table
  • More flexible but requires application-level logic

Design Choice: Use within-neighborhood for automatic symmetric relationships (friendships, group membership). Use cross-neighborhood when direction matters or cardinality needs control.

import "github.com/pseudata/pseudata"
const worldSeed = int64(42)
users := pseudata.NewUserArray(worldSeed)
link := pseudata.NewPseudoLink(17, 20)
// Alice in neighborhood 1000
aliceIdx := link.Encode(1, 1000, 0)
alice := users.At(aliceIdx)
// Bob (mentor) in neighborhood 2000
mentorIdx := link.Encode(1, 2000, 0)
mentor := users.At(mentorIdx)
// Navigate BACK: Given mentor's index, construct mentee's index
// Using convention: mentee = N:1000, mentor = N:2000
menteeIdx := link.Encode(1, 1000, 0) // Same as aliceIdx
mentee := users.At(menteeIdx)
fmt.Printf("Bidirectional: %s's mentor is %s\n", alice.Name, mentor.Name)
fmt.Printf("And %s's mentee is %s\n", mentor.Name, mentee.Name)

Configuration Impact on Relationship Capacity

The bit allocation determines relationship capacity:

Default Configuration (17 island, 3 connector, 20 neighborhood):

  • Islands: 217=131,0722^{17} = 131,072 (sharding granularity)
  • Connectors: 23=82^3 = 8 (relationships per neighborhood)
  • Neighborhoods: 220=1,048,5762^{20} = 1,048,576 (distinct groups)

Increasing Connector Bits (17 island, 5 connector, 18 neighborhood):

  • More relationships per neighborhood: 25=322^5 = 32
  • Fewer distinct neighborhoods: 218=262,1442^{18} = 262,144
  • Trade-off: Deeper relationships vs. fewer groups

Increasing Island Bits (20 island, 3 connector, 17 neighborhood):

  • Finer sharding: 220=1,048,5762^{20} = 1,048,576 shards
  • Fewer neighborhoods: 217=131,0722^{17} = 131,072
  • Trade-off: Better distribution vs. grouping capacity

The configuration choice depends on your scenario’s relationship density, shard count, and grouping requirements.

Pseudo-links are designed for creating deterministic, stateless relationships in test data and mock scenarios.

Ideal for:

  • Many-to-many relationships: Users-Groups, Posts-Tags, Students-Courses
  • Symmetric relationships: Friendships, peer connections, bidirectional links
  • Asymmetric relationships: Follow/Follower patterns using slot conventions (e.g., connector 0 = following, connector 1 = followers)
  • Cross-service testing: Microservices that need consistent relationship data without shared databases
  • Distributed systems: Testing sharding logic with guaranteed shard-safe relationships
  • Load testing: Generate billions of related entities without coordination overhead
  • Deterministic demos: Consistent relationship patterns across all environments and languages

Not suitable for:

  • Production databases: Use real foreign keys and junction tables for production data
  • Dynamic relationships: Relationships that change frequently or unpredictably over time
  • Complex graph queries: Deep traversal or pathfinding (use graph databases)
  • Arbitrary cardinality: When you need unlimited relationships per entity (limited by connector bits)

Key advantage: Zero storage overhead with O(1)O(1) navigation - perfect for test scenarios where you need realistic, deterministic relationships without database complexity.

This section details the bitwise mechanics that guarantee O(1)O(1) performance and sharding safety.

Key Principle: Pseudo-links perform pure bit manipulation on valid 40-bit integers. There is no special “encoding” - the functions simply read and write specific bit positions. Any 40-bit value (0 to 2^40-1) is valid input and produces valid output. The “relationship” exists because related indices share specific bit patterns (neighborhood and island bits).

Let an index IdxIdx be defined as the concatenation of three bit-segments: Island (II), Neighborhood (NN), and Connector (CC).

Idx=INCIdx = I || N || C

The pseudo-link resolution function f(Idx,k)f(Idx, k) treats the index as three segments and produces a new index by replacing the Connector segment CC with a target value kk. This is pure bit manipulation - reading and writing specific bit positions.

f(Idx,k)=INkf(Idx, k) = I || N || k

Since II and NN are preserved during the transformation, any two indices Idx1Idx_1 and Idx2Idx_2 are part of the same pseudo-link cell if and only if their Island and Neighborhood segments match:

I1=I2I_1 = I_2 and N1=N2N_1 = N_2

This creates a Complete Graph KNK_N within every neighborhood, where N=2connectorBitsN = 2^{connectorBits}. All entities in the same pseudo-link cell are related to each other.

Distributed systems often shard data by extracting the high-order bits. In a pseudo-link, given islandBits=17islandBits = 17:

ShardID=Idx/223=Idx23ShardID = \lfloor Idx / 2^{23} \rfloor = Idx \gg 23

Because the resolution function f(Idx,k)f(Idx, k) only modifies bits at positions <23< 23 (the Connector and Neighborhood bits), the upper 17 bits (Island) are invariant:

ShardID(Idx)=ShardID(f(Idx,k))ShardID(Idx) = ShardID(f(Idx, k))


A pseudo-link operates on the 40-bit index component of Pseudo-IDs. When you create a PseudoID with encodeId(worldSeed, typeSeq, index), the index parameter is just a 40-bit number. PseudoLink provides a coordinate system for manipulating those bits to create relationships.

Key points:

  • Pseudo-links work with the index component (40 bits) of PseudoIDs
  • The world-seed and type-sequence remain unchanged
  • Any 40-bit index value is valid - pseudo-links manipulate them through bitwise operations
  • Enables stateless O(1)O(1) relationships between sets of entities
  • Works across different types (e.g., User at index X relates to Group at related index Y)
import { encodeId, PseudoLink } from '@pseudata/core';
const worldSeed = 42n;
const link = new PseudoLink(17, 20);
// Start with coordinates for entity A (e.g., User)
const indexA = link.encode(1, 1000, 0);
const userID = encodeId(worldSeed, 101, indexA); // Type 101 = Users
// Calculate related index for entity B (e.g., Group)
const indexB = link.resolve(indexA, 2); // Same island & neighborhood, connector 2
const groupID = encodeId(worldSeed, 102, indexB); // Type 102 = Groups
// The relationship exists through shared bit patterns in the indices
  • Pseudo-IDs - Learn about the complete pseudo-id structure and how the 40-bit index component works with pseudo-links
  • Pseudo-Arrays - Understand how pseudo-arrays use indices to provide O(1)O(1) access to deterministic mock data
  • Primitives - Learn about the generator functions that can use indices constructed with pseudo-links