Case studyEnterprise Product Engineering2023
Community Platform for 500K+ Educators
A discussion and collaboration platform used by more than 500,000 educators across a US state, with the administration and moderation tools a public-sector deployment that size requires.
Moderation queue
Pending reviewInterface recreated for portfolio purposes.
Problem to outcome
The complete case study in seven parts. The sections below provide the engineering detail behind this summary.
Problem
Context
Constraints
- One person, several roles at once
- Moderation must be reversible and auditable
- Sessions last all day
My role
Approach
Solution
Outcome
- educators on the platform
- products sharing one codebase and one permission model
- more posts after the discussion work shipped
- faster page loads
What is the community platform?
A community platform for public-sector educators across a US state. Teachers and school staff post questions, share classroom resources and follow topics. District and school administrators manage membership and content. A moderation team works through reported posts.
It is really three products sharing one codebase: a discussion feed, an administration console, and a moderation queue, all built on the same data and the same permission system.
The moderation loop
01
Content reported
Any member can flag a post
02
Enters the review queue
With the thread around it
03
Moderator reviews
In context, not in isolation
04
Action taken
Reversible, never a hard delete
05
Result announced
Including to screen readers
06
Recorded
Auditable after the fact
07
Bulk actions
The same path, applied to many
What it does
- Permission layer
- One place that resolves a user's roles into what they can see and do, used by both page guards and individual controls.
- Discussion experience
- Threaded posts, groups and topic categorisation, built to stay fast as threads grow long.
- Rich-text posting
- An editor supporting code blocks and embedded media, with content cleaned once as it enters the application.
- Moderation tooling
- Reporting, content flagging, an admin review queue and bulk actions, all reversible and auditable.
- Administration
- Membership and content management across platform, district and school scopes.
- Server-data layer
- Caching, invalidation, and instant-feeling interactions that roll back correctly when a request fails.
Why the problem was difficult
A discussion feed is a solved problem. A discussion feed that is simultaneously an administration console and a moderation queue, for half a million people across a state, is not.
The difficulty is that one person can be a platform administrator, a district lead and a member of a single school at the same time. Those roles are additive and overlapping, and every screen, every button on every screen, has to reach the same conclusion about what they permit.
When each component worked that out for itself, screens contradicted each other, and every new rule meant re-checking the whole application. Meanwhile server data was being copied into local state to make things feel fast, so the interface and the server drifted apart in a second, separate way.
How do you let one person hold three roles at once, and still have every screen agree about what they can do?
Contribution and ownership
Architecture
How is the application organised?
- How data and state are organised across the whole application
- The permission layer, and the single question features ask it
- Module boundaries features are built inside
- Response shapes the cache could keep in sync, agreed with backend
Product surfaces
What was delivered end to end?
- The discussion experience: threaded posts, groups, topics
- A rich-text editor with code blocks and embedded media
- Moderation and support tooling, including the review queue
- Administration across platform, district and school scopes
Data layer
Who owns the server's truth?
- Caching and invalidation via RTK Query
- Optimistic interactions and their rollback
- Keeping long sessions honest against a live server
Quality
What kept it from regressing?
- The testing strategy across unit, component, integration and end-to-end
- The accessibility bar, and reviewing against it
- Performance work: lazy loading, memoisation, code splitting
What isn’t mine
Backend engineers owned the services, APIs, and data model; the lead designer owned the visual design; and the product team set direction. Frontend ownership covered the architecture, interfaces, and response-shape collaboration needed to keep cached data in sync. The $2M renewal was a shared outcome across product, design, backend, and frontend engineering.
The constraints shaped the product
One person, several roles at once
The permission model has three overlapping scopes (platform, district and school), and a single user can hold roles in several of them simultaneously. The roles are additive, not a hierarchy you can collapse into a rank.
Permissions had to be resolved once into a full set, rather than checked as a role comparison at each call site.
Moderation must be reversible and auditable
This is public-sector infrastructure holding content written by teachers. A moderation action that cannot be undone or explained afterwards is not acceptable, regardless of how much simpler it would be to build.
No destructive shortcuts anywhere in the moderation path, and every action leaves a record.
Sessions last all day
People leave the tab open from morning to afternoon. Anything cached at login is stale by lunchtime, and a user acting on stale permissions is a security problem rather than a refresh annoyance.
Cached data revalidates in the background rather than trusting what was fetched at session start.
Accessibility is a requirement, not a pass at the end
A public-sector platform has an accessibility bar to meet on every screen, including the administration and moderation tooling that only staff ever see.
Semantic HTML first, automated axe checks inside component tests, so regressions fail in CI rather than in an audit.
The team was going to grow
Anything too clever to hand over is a liability on a platform that will outlive whoever wrote it. The architecture had to be explainable to someone who joined a year later.
Code grouped by feature with small public surfaces, and shared abstractions only once a second feature genuinely needed one.
How it fits together
Every screen needs to know what this particular person may view and do. Rather than each component working that out for itself (which is how screens start disagreeing with each other), permissions are resolved once, above the features, and everything below asks the same question.
User
Educator, school, district or platform staff
Interface
DiscussionsModeration queueAdministration
Permission check
Can this user do this, here?
Data layer
RTK Query cacheInvalidationOptimistic updates
Backend APIs
Owned by the backend team
Decisions that shaped the product
Each of these solved a real problem and cost something in return. The trade-off is stated alongside the decision, because a decision without one was not really a decision.
Decision 01
Making interactions feel instant without lying to the user
Problem
Decision
Trade-off
Result
Decision 02
Deciding permissions in one place instead of everywhere
Problem
Decision
Trade-off
Result
Decision 03
Cleaning user-written content once, at the edge
Problem
Decision
Trade-off
Result
Decision 04
Organising code by feature, not by file type
Problem
Decision
Trade-off
Result
One permission model for four kinds of user
Most permission bugs are not logic errors. They are two places in an application disagreeing about the same question, because both were allowed to answer it.
The fix was to remove the second answer. A user's roles are resolved into a complete permission set once, when the session starts, and every feature asks the same typed question of that one result: can this person do this, here.
- Resolve once
- Roles across platform, district and school collapse into a single permission set at session start, rather than being re-derived per component.
- One question
- Features ask a single typed question. There is no second way to phrase it, so there is no second answer to drift.
- Guards and controls agree
- The route guard protecting a page and the button inside it read the same result, which is what stops a screen offering an action it will then refuse.
- Additive, not ranked
- Roles combine rather than override. Treating them as a hierarchy would have been simpler and wrong the moment someone held two at once.
- Reviewable in one place
- A permission change is a one-file review. That is the property that survived a year of new rules.
- Typed at the boundary
- The question and its answer are typed, so a rule that does not exist fails at compile time rather than silently returning false.
Frontend architecture
The organising question was not which components exist, but which kind of state is allowed to own which fact, because the previous structure had let three of them own the same one.
UI state
Dialogs, selection, local interaction, what is expanded
Component-local and ephemeral. It never leaves the component that owns it.
Permission state
The resolved permission set for the session
Read-only to features. Derived once, above them, so nothing below can reach a different conclusion.
Server state
Posts, users, groups, the moderation queue
RTK Query owns it outright. Nothing is copied into local state. That copy was the original bug.
Feature modules
Discussions, moderation, administration
Small public surfaces, so a change stays local and a new engineer can work in one area without reading the rest.
Deep dive: optimistic updates that roll back
Reactions, follows and replies have to land in a single frame. The naive version updates the screen immediately and lies whenever the request fails. The honest version needs the undo designed at the same time as the update.
User acts: reacts, follows, replies, then Cache updated immediately, then Request sent, reaching the question “Did the server agree?”. On failure: Change rolled back automatically. On success: Optimistic state kept.
- UI
- The interaction lands in one frame. No spinner, and no intermediate state the user has to interpret.
- State
- The update and its undo live together in one place, so there is no second copy of the truth that can drift from the first.
- Failure
- A failed request reverses itself cleanly rather than leaving the screen showing something that was never saved.
- Cost
- Each action has to know which parts of the cache it touches, so adding a new list means revisiting them. Real coupling, but visible and testable.
Trade-offs I had to make
Instant feedback vs truthful state
Updating the screen immediately is what makes a discussion feel alive, and it is also how an interface ends up showing something the server never accepted.
ChoiceOptimistic updates with automatic rollback, with the update and its undo written together. The coupling that creates is real, but it is visible and testable. The alternative was neither.
Designing the permission model up front vs adding rules as they arrive
A model designed before the rules are all known is slower for the first rule and constrains the shape of later ones.
ChoiceDesign it up front anyway. Slower for the first rule, considerably safer by the fiftieth, and it kept permission changes to a one-file review a year later.
Feature modules vs strictly shared code
Grouping by feature leaves some duplication alive longer than a strictly DRY codebase would tolerate.
ChoiceAccept the duplication and only promote to shared once a second feature genuinely needs it. Duplication is cheaper than the wrong shared abstraction.
Where it is now
The frontend became something a growing team could work in. Permission logic has one home, server data has one owner, and features are added inside module boundaries rather than across them.
On the product side, the discussion work shipped alongside a measured 15% increase in posts, and the performance work produced a 30% improvement in load times. The platform also contributed to a major customer renewal worth roughly $2M. That was a team outcome across product, design, backend and engineering, of which the frontend was one input.
- Owned
- The frontend architecture of a platform serving 500K+ educators
- Built
- Discussion, moderation and administration end to end
- Designed
- One permission layer covering four kinds of user
- Improved
- Load times by 30%, tracked against LCP, INP and CLS
- Shipped
- Discussion work that came with a measured 15% increase in posts
What this experience changed
Most permission bugs are two places disagreeing
Not a wrong rule, but a second place allowed to answer the same question. Removing the second answer fixed a category of bug rather than instances of it.
Copying server data into local state is the bug
It always starts as a performance shortcut and always ends as drift between the screen and the server. Giving server data exactly one owner removed the whole class.
Structure that looks like over-engineering ages well
Resolving permissions above the features looked excessive the week it landed. It is the reason permission changes were still a one-file review a year later.
Solve problems on the right side of the wire
We absorbed the complexity of page-number pagination on a live feed in the client, when it should have been fixed at the API boundary. Client-side cleverness papering over a server-side shape is a cost that compounds.
What I'd improve next
- 01Extract a small typed helper for optimistic updates, since the pattern repeated itself about two actions past the point it should have been pulled out
- 02Push earlier for cursor-based pagination on the feed, which interacts badly with page numbers and cached pages
- 03Permission changes surfaced in the UI as an explanation, not just an absence of buttons
- 04A moderation audit view built for the people reviewing decisions, rather than reconstructed from records
- 05Tighter revalidation on very long sessions, where the current background refresh is more conservative than it needs to be
05 / Contact
Have a complex product problem worth solving?
Available for senior frontend, product engineering, and AI product engineering opportunities.
- 01
- 02
LinkedIn
View professional profile - 03
Location
Bangalore, India · Remote