Saying no to scope that would break the project
Picture the request: add real-time collaborative editing to an internal CRM. Three people editing the same customer record simultaneously, with cursors and presence indicators, scoped as a change request against an application that shipped six months earlier.
Say the application is a straightforward Rails monolith. PostgreSQL, a few Sidekiq workers, standard CRUD flows. It handles a 40-person sales team well. The collaborative editing request would mean introducing WebSockets, an Operational Transform or CRDT layer, conflict resolution logic, and a fundamentally different state model. Roughly three weeks of work to prototype, another four to make production-ready, plus ongoing maintenance complexity that touches every entity in the system.
This is the kind of request we say no to.
The conversation goes something like this: we explain what the feature actually requires under the hood, which is usually more than the client expects. We ask what problem they are trying to solve. In a case like this it tends to turn out that two sales reps edited the same record on the same afternoon and the second save overwrote the first. The data loss is minor — a phone number update — but it feels alarming to whoever leads the team.
The actual problem is concurrent overwrites, not collaboration. The fix is optimistic locking. Rails has `lock_version` built in; it takes half a day. When two people edit the same record, the second save gets a stale-object error and they see a diff screen. No cursors, no presence, no WebSocket infrastructure.
This is how most of these conversations go. The client asks for a specific implementation. We ask what triggered the request. The trigger is usually a real problem, but the proposed solution is often the heaviest possible fix for it.
We don't say no to everything. A request to add SSO via an Azure AD tenant instead of password auth is a reasonable one — an IT team wanting centralized access control, a standard OmniAuth flow, about a week of work, and it removes a whole class of support tickets. The scope fits the system.
The ones we decline share a pattern: they add a cross-cutting concern that re-architects how the application handles state, permissions, or data flow, and the payoff is disproportionate to the cost. Real-time collaboration on a CRUD app. A custom workflow engine when the client has three approval paths. Multi-tenant isolation retrofitted onto a single-tenant schema because one new customer asked for it.
The hardest conversations are when the client has already promised the feature to their own team. Imagine an operations head who has told their CEO they will have multi-currency support by quarter-end, and only then calls to figure out how to build it. If the company is genuinely expanding into another country, the currency work is legitimate, so it gets done. But the timeline needs pushing back on. Multi-currency in an invoicing system touches tax calculation, reporting, payment reconciliation, and historical data. It is six weeks of work, not the two that were promised.
When we decline, we try to come with an alternative. The collaborative editing request became optimistic locking. A request to build a custom notification system with per-user rules became a simpler model: event types that users can toggle on or off, no complex rule builder. A request to support arbitrary custom fields on every entity became a fixed set of extension columns on the two entities that actually needed them.
Sometimes the alternative is just naming the cost. Take a request for offline-first mobile sync. Write up what it would take: a local SQLite store, a sync engine, conflict resolution, and roughly 40% more maintenance burden on every future feature that touches synced data. Faced with that estimate, plenty of teams decide the field crew can work with periodic online sync instead. That is their call, made with real information.
The point is that declining scope is not about being conservative. It's about protecting the system we're going to maintain for the next two years. Every cross-cutting feature we add makes the next feature harder to build and harder to reason about. A small team can maintain a well-scoped application indefinitely. A small team maintaining an application with three layers of architectural debt cannot ship anything new without something else breaking.