Skip to content
HomeAt Splunk, that means pairing React product work with Python services, operational data, permissions, evaluation, and release controls. Earlier at Deloitte, I built extraction, storage, API, and visualization systems; two removed more than 36,000 hours of annual manual work and cut investment-processing time by 66%.WritingTechnical writing on software engineering, AI engineering, system design, and the trade-offs behind production systems.ProjectsA growing collection of systems and products I’ve built—from current infrastructure work to earlier projects that shaped how I build today.Abdul AhadI’m an AI full-stack engineer with more than five years of experience building data-intensive enterprise software and production AI systems. At Splunk, I drove AI Service and KPI Discovery from an early prototype through technical design, validation, and production delivery. The capability analyzes operational data and recommends service models and health signals within Splunk IT Service Intelligence.Let’s talk.If you’re hiring, building something interesting, or want to compare notes on a difficult engineering problem, send me a message. A few lines is plenty.ColophonHow this portfolio handles static publishing, search, contact delivery, analytics, and releases.Capacity Planning Before Architecture DiagramsA practical method for turning workload assumptions into request, storage, bandwidth, concurrency, and failure-capacity budgets before choosing an architecture.Design Modules Around Change, Not LayersA practical guide to information hiding, volatile decisions, coupling, cohesion, dependency direction, and avoiding abstractions that preserve the wrong boundaries.From Model Call to Reliable AI WorkflowHow one model request grows into retrieval, tools, deterministic workflows, agents, evaluation, observability, and human escalation.Queues, Backpressure, and Idempotency: Designing for FailureHow bounded queues, admission control, idempotency keys, retry budgets, and dead-letter handling turn asynchronous delivery into a controlled reliability system.Refactoring a Legacy Backend Without a Big-Bang RewriteA staged method for understanding, testing, isolating, replacing, observing, and finally deleting legacy backend behavior without one irreversible cutover.MCP in Production: Trust Boundaries, Permissions, and Tool DesignA production-focused guide to MCP hosts, clients, servers, capability discovery, authorization, consent, token audiences, and safely designed tools.How this portfolio worksA walkthrough of how an edit becomes a published page, what happens after someone presses Send, and how the site avoids losing or saving the same message twice.ThreadsPosts, nested replies, profiles, and communities share the same records. This follows one conversation from creation to deletion.Prompt NexusGoogle sign-in leads to a local user, then one saved prompt powers the feed, profile, search, and edit screens.SeedEditors publish articles in HyGraph, readers receive prepared pages, and new comments remain private until they are approved.CactaOne published video creates a media asset, a post record, and temporary browser state. This follows that upload across the product.System DesignCapacity, queues, failure handling, and distributed-system trade-offs.AI EngineeringLLM workflows, tools, evaluation, permissions, and operations.Software EngineeringModules, testing, refactoring, interfaces, and maintainability.System Design from First PrinciplesCapacity planning, queues, backpressure, retries, and failure handling.Production AI EngineeringModel calls, workflows, tools, evaluation, permissions, and operations.

← Back to Projects

2023 · Previous work6 min read

Threads

Posts, nested replies, profiles, and communities share the same records. This follows one conversation from creation to deletion.

2023Previous work6 min read

Represented both posts and replies with one record shape, allowing a reply to contain further replies.

  • TypeScript
  • Next.js
  • React
  • Tailwind CSS
  • MongoDB
  • Clerk
  • UploadThing
Threads discussion application interface
On this page

Threads is a community discussion application. A person signs in, publishes a post, replies to another person, joins a community, and finds conversations through feeds, profiles, or search.

After the first post is saved, it belongs to an author, may belong to a community, can receive replies, and appears on several screens. This walkthrough follows one conversation from creation to deletion, then covers the supporting identity, image, and search flows around it.

Creating a post and its first reply#

A new top-level post stores its text, author, creation time, and an optional community. The author and community are not copied into the post. The record stores their identifiers so the application can load the current name and image when it displays the conversation.

That stored identifier is a reference: one record points to another record that owns the related information. MongoDB holds these document-like records, and the server follows the required references when it prepares a feed or profile page.

A reply uses the same record shape as a post, with one addition: it stores the identifier of the post it answers. The parent also keeps the reply's identifier in its list of children. A top-level post has no parent; a reply does.

Using one shape means a reply can receive another reply without a separate comment model. The conversation becomes a tree: it begins at one root post and branches each time someone answers a post or an existing reply.

View diagram source
flowchart TB
    A["Author publishes a top-level post"] --> P["Post with no parent"]
    P --> R1["Reply with P as its parent"]
    P --> R2["Another reply with P as its parent"]
    R1 --> R3["Reply with R1 as its parent"]

    P -. "also appears on" .-> F["Home feed"]
    P -. "also appears on" .-> U["Author profile"]
    P -. "also appears on" .-> C["Community page"]

Showing the same conversation on several screens#

After a post is created, its identifier is added to the author's thread list and, when relevant, the community's thread list. Those reverse lists make common screens direct to load: a profile already knows which posts belong to that person, and a community already knows which posts belong inside it.

The full post text still lives in the post record. The profile and community keep identifiers, not separate copies of the conversation. When a screen is requested, the server loads the referenced posts and the author details needed to display them.

This is convenient for reading, but it creates more work when data changes. The same post identifier may now exist in the post tree, the author's list, and a community's list. All three must continue to describe the same reality.

What deletion must clean up#

Suppose an author deletes the root post in the diagram above. Removing only that one record would leave three replies whose parent no longer exists. The author's and community's thread lists would also continue pointing at a missing post.

The delete path first walks through every child below the selected post. It uses recursion, which means the same deletion logic runs for a child and then for that child's children until there is nothing further to visit. That matches the shape of the conversation: every reply can be treated as the root of a smaller reply tree.

After removing the descendants, the code removes the root identifier from the author and community records. Several database writes have to agree for the deletion to be complete.

This version performs those writes in application code without wrapping the entire operation in one database transaction. A failure halfway through can therefore leave stale identifiers or an incomplete tree. A production rebuild would either move the related writes into a transaction or store fewer reverse lists and derive more of them from one source.

View diagram source
flowchart TB
    D["Delete the root post"] --> L["Load its direct replies"]
    L --> W["Repeat the same walk for every reply"]
    W --> X["Delete descendants"]
    X --> R["Delete the root post"]
    R --> A["Remove its ID from the author"]
    A --> C["Remove its ID from the community"]

Updating membership when nobody is on the site#

Threads used Clerk for sign-in and organizations. In the interface, a Clerk organization acted as a community. A person could join or leave one through Clerk, so the local MongoDB records needed to receive that change even if no Threads page was open at the time.

Clerk reported those events to a server route using a webhook—an HTTP request one system sends to another when something changes. Threads handled community creation, edits, deletion, and membership changes, then updated the matching local community and user records.

An internet request is not trustworthy merely because it reaches the correct route. Clerk's webhook requests include a cryptographic signature. The route verified that signature through Svix before changing any membership data.

This version does not record processed event identifiers or periodically compare Clerk with the local database. A stronger version would add both so a repeated webhook is harmless and a missed event can be repaired.

Images, search, and pagination#

Profiles and communities can include images. The browser uploaded those files through UploadThing, while MongoDB stored only the returned URL. This keeps large file bytes out of the records used for feeds and conversations. The upload route also required a signed-in user before accepting a file.

Search queried user names or community text with case-insensitive matching. Results were returned in pages rather than as one unbounded list. Pagination means requesting a limited slice—such as the next twenty results—so the response does not grow with the entire collection.

For this project size, that query kept search small and direct. With more data, I would add indexes based on measured query patterns before considering a separate search service.

Before adding more features#

The core conversation shape can stay: one record for posts and replies, parent-child links for nesting, and references for authors and communities. The work before adding more features would be at the boundaries.

Every create, edit, and delete route should verify permission on the server. Multi-record changes should be transactional or have a repair path. Webhook processing should tolerate duplicates and missed delivery. Deleting a user or community needs an explicit policy for its posts, replies, memberships, and uploaded files. Public discussion also needs reporting, moderation, and abuse controls.

The resulting lifecycle is concrete: creation adds links, reading follows them, outside identity events update them, and deletion has to remove them without leaving the database in disagreement.