Hash Table Inspector

Hash tables are easy to use and surprisingly difficult to see. A debugger can expose pointers and memory, but it rarely explains why a collision occurred, when a resize started, what moved during a rehash, or whether the structure still satisfies its invariants.

Hash Table Inspector turns those invisible transitions into a rewindable Android visualization backed by a real C11 hash table. The project began with a public clone of issue #26, a broader proposal for inspecting native data structures. For this submission, I deliberately narrowed that idea to one reliable vertical slice instead of attempting a premature generic reflection system or DSL.

What it does

The Android app operates on an actual native byte-key-to-int64_t hash table through a C-only JNI bridge. The C table—not Kotlin—is the only live source of truth.

Users can:

  • Insert, update, look up, and delete entries.
  • See buckets, collision chains, load factor, capacity, resize thresholds, chain-probe counts, and stable entry IDs.
  • Rewind through as many as 64 immutable full-table snapshots.
  • Run a deterministic demonstration containing a collision, lookup hit and miss, update, delete, and a 4-to-8 rehash.
  • Apply a controlled size-counter corruption to an isolated table state.
  • See independent invariant validation quarantine the invalid live session.
  • Inspect valid historical frames without mutating the live table.
  • Restore the clean state and validate it again.

The deterministic demo ends with a deliberately invalid live frame reporting a size mismatch. After restoration, the final frame reports size/count 4/4, capacity 8, and valid invariants.

How I built it

The native core is strict C11 with owned arbitrary byte keys, separate chaining, deterministic hashing, stable entry IDs, and a 3/4 growth threshold. Stable trace hooks capture real operation boundaries without allowing the presentation layer to simulate the table.

A reusable native inspection spine owns event envelopes and a bounded FIFO timeline. A hash-table-specific adapter captures every bucket, chain, entry, and copied key into independently owned immutable snapshots. It also performs invariant validation, controlled corruption, restoration, and versioned JSON encoding.

The Android layer uses Kotlin, JNI, and Jetpack Compose. Native results cross JNI as copied data, then become immutable Kotlin presentation state. Commands remain serialized, and InspectorViewModel owns the native session across Activity recreation.

Host-native builds intentionally use Bash and Clang directly with strict warnings, AddressSanitizer, and UndefinedBehaviorSanitizer. CMake is limited to the Android NDK integration boundary. The six deterministic native tests pass, the Android build succeeds, and the resulting 12 MB arm64-v8a APK was installed and physically validated on a Samsung Galaxy S25 Ultra running Android API 36.

Challenges and architectural decisions

The first challenge was observability without creating a second implementation. Kotlin could not maintain a shadow hash table or infer native behavior. Events therefore originate at stable points inside the real C operations, and every displayed frame is a copied snapshot of that same table.

Rehashing required explicit begin/end boundaries so the timeline could show capacity changes and moved entries without exposing a partially mutated structure as a stable frame.

Corruption also needed to be demonstrative without becoming reckless. The submission changes only the stored size counter in a controlled inspection build. Validation detects the mismatch, quarantines the session, and restoration returns to a clean owned state. Arbitrary pointer corruption and invalid memory access are intentionally out of scope.

Physical-device testing uncovered issues that a successful build could not. Edge-to-edge content initially passed beneath the status bar and gesture-navigation region, so the scrolling viewport was constrained with WindowInsets.safeDrawing.

As a colorblind user, I also found the original small “Live” chip too subdued to recognize reliably as an action. It became a full-width filled Material button labeled Return to live, using text, shape, contrast, semantics, and touch-target size rather than color alone.

Switching between system light and dark themes originally recreated the Activity and destroyed the inspection state. Native-session ownership moved into InspectorViewModel. Manual testing then confirmed that theme recreation preserved the native session, all 17 demo frames, the selected historical frame, and the timeline scroll position.

I retained the vertical newest-first timeline because it exposes the complete operation sequence directly. A graph and horizontal history remain deferred until their node, edge, navigation, and accessibility semantics can be defined deliberately.

What I learned

The most important lesson was that visualization boundaries are also ownership boundaries. A trustworthy inspector must distinguish the live object, copied observations, historical selection, and controlled mutation.

I also learned how valuable physical human testing remains even with strong automated validation. Sanitizers, builds, ADB health checks, and log inspection established correctness, but hands-on use revealed the inset, action-discoverability, color-vision, and lifecycle problems that shaped the final architecture.

Future work could add additional structure adapters, broader device and ABI coverage, process-death restoration, randomized and fuzz testing, more corruption modes, and graphical history views with deliberately designed accessible semantics.

How Codex and GPT-5.6 accelerated the build

I used GPT-5.6 Sol Max through Codex CLI as the primary implementation, research, verification, and documentation agent. I used ChatGPT alongside it as a design and review partner: translating Codex’s detailed reports, evaluating physical-device findings, preserving architectural decisions, and preparing the next bounded prompt.

I brought nearly two years of accumulated collaboration context and engineering preferences into the sprint. That meant we did not need to rediscover foundational choices such as a portable C11 core, explicit ownership, Bash/Clang host builds, minimal CMake only at the NDK boundary, copied cross-language messages, system-driven light/dark behavior, and avoiding Node.js tooling. GPT-5.6 could begin from those established constraints and concentrate on this project’s unique architecture.

Codex first inspected the source issue, empty repository, and available toolchain. It converted the broad introspection proposal into a concrete plan covering memory ownership, C APIs, JNI boundaries, risks, milestones, tests, and explicit non-goals.

I then directed Codex through small, evidence-backed checkpoints:

  1. Implement and sanitize-test the native hash table, inspection timeline, snapshots, invariants, corruption flow, and JSON boundary.
  2. Record a native-slice handoff before introducing Android.
  3. Build the pinned Gradle/JNI/Compose vertical slice without replacing or updating the installed Android toolchain.
  4. Install and cold-launch the APK on the authorized physical device, then inspect process state, focus, JNI loading, and fatal logs.
  5. Respond to my human findings about system-bar insets, color-vision accessibility, theme behavior, and lifecycle destruction.
  6. Rebuild, retest, validate on-device, create narrowly scoped Git checkpoints, and consolidate the evidence into the repository documentation.

I retained control of product scope, architectural tradeoffs, physical interaction, accessibility judgment, tool installation, command approvals, commits, and pushes. Codex accelerated implementation and verification, but it did not silently choose what the product should become.

This workflow made it possible to move from a broad issue and empty repository to a sanitizer-tested C11 core, Android/JNI/Compose application, physically validated APK, accessible lifecycle-aware UI, and documented submission in a highly compressed build window. The speed came from combining persistent design context, explicit human decisions, bounded Codex tasks, and validation at every checkpoint—not from skipping engineering discipline.

Built With

  • android
  • androidndk
  • c
  • c11
  • codex
  • gpt-5.6
  • jetpackcompose
  • jni
  • kotlin
Share this project:

Updates