DEV Community

Cover image for Before You Write Code, Ask Better Questions
Anik Sikder
Anik Sikder

Posted on Originally published at aniksikder.me

Before You Write Code, Ask Better Questions

Imagine you're building a SaaS platform.

Nothing unusual.

Just an employee management system.

The client says:

"We need employee management."

A developer says:

"Sure. I'll build employee CRUD."

So the implementation begins.

Employee
   ↓
Create
Read
Update
Delete
Enter fullscreen mode Exit fullscreen mode

Simple.

Until the real requirements appear.

A few weeks later:

  • HR wants approval workflows.
  • Managers shouldn't see salary information.
  • Employees can belong to multiple departments.
  • Former employees must remain in historical reports.
  • The organization has multiple branches.
  • Some actions require approval.
  • Sensitive changes must be audited.
  • Another system needs an API.
  • Finance needs historical payroll data.

The developer didn't necessarily write bad code.

The problem happened before the code existed.

The team misunderstood the problem.

That's one of the most important lessons in software engineering:

Great software starts with great questions, not great code.

Requirement engineering is the process of turning:

Business Goals
      ↓
User Problems
      ↓
Requirements
      ↓
Business Rules
      ↓
Data
      ↓
Workflows
      ↓
Architecture
      ↓
Implementation
      ↓
Validation
Enter fullscreen mode Exit fullscreen mode

When this chain breaks, even excellent developers can build the wrong system extremely well.


The Real Problem With Requirements

Consider this requirement:

"Managers can manage employees."

What does manage mean?

Can a manager:

  • create an employee?
  • edit personal information?
  • change salary?
  • change department?
  • deactivate an employee?
  • delete an employee?
  • invite an employee?
  • approve leave?
  • transfer an employee?
  • view payroll?
  • restore a former employee?

Every answer changes the software.

It can affect:

  • database structure
  • authorization
  • API design
  • service logic
  • UI workflows
  • audit requirements
  • testing
  • security
  • operational cost

The ambiguity existed before implementation.

The architecture simply exposes it later.

This is why requirements matter.

The earlier ambiguity is discovered, the cheaper it usually is to correct.


Requirements Are Not Features

One of the most important distinctions is between a business problem, a requirement, and a feature.

A client says:

"We need a dashboard."

That's a proposed solution.

Ask:

"Why?"

Maybe the real problem is:

"Finance managers cannot quickly identify overdue customer payments."

Now the requirement becomes:

"Authorized finance users must be able to identify overdue customer balances and prioritize collection actions."

The eventual feature could be:

Payment Aging Dashboard
        +
Overdue Invoice Table
        +
Filtering
        +
Notifications
        +
Collection Workflow
Enter fullscreen mode Exit fullscreen mode

The feature came after understanding the requirement.

This distinction matters because stakeholders often describe what they think should be built.

Engineers need to discover why it needs to exist.

A useful mental model is:

Business Problem
      ↓
Desired Outcome
      ↓
Requirement
      ↓
Feature
      ↓
Implementation
Enter fullscreen mode Exit fullscreen mode

Don't start at the bottom.


The Three Questions Behind Every Requirement

When someone gives you a requirement, don't immediately ask:

"How do we build this?"

Ask three questions first.

1. Why?

What business problem are we solving?

2. What?

What capability does the system need?

3. Under What Conditions?

What rules, permissions, constraints, and exceptions apply?

Consider:

"Users should be able to transfer organization ownership."

Why?

The current owner is leaving the company.

What?

Ownership must be transferred to another eligible member.

Under what conditions?

Perhaps:

  • the target must belong to the organization
  • the owner cannot transfer ownership to themselves
  • only one active transfer request can exist
  • the target must accept the request
  • the request expires after 72 hours
  • the operation must be transactional
  • the transfer must be audited
  • notifications must be sent

Now we have something engineers can actually design.

The original sentence was only the beginning.


How Engineers Discover Requirements

Requirements rarely arrive in a perfectly structured document.

They are usually discovered through conversations, observation, existing systems, documents, and prototypes.

Ask People to Explain the Workflow

A weak interview asks:

"What features do you want?"

A better question is:

"Walk me through what happens today."

Suppose you're building an expense management system.

Don't only ask:

"Do you need expense approval?"

Ask:

"What happens when an employee submits a $500 expense?"

You may discover:

Employee submits expense
        ↓
Manager reviews
        ↓
Large expenses require additional approval
        ↓
Finance validates receipt
        ↓
Approved expense goes to payroll
        ↓
Employee is reimbursed
        ↓
Audit history is preserved
Enter fullscreen mode Exit fullscreen mode

The requirement wasn't simply:

Expense → Approved
Enter fullscreen mode Exit fullscreen mode

It was a business workflow.

Observe the Work

Users sometimes cannot explain everything they do because much of their workflow has become habitual.

An accountant might say:

"I just reconcile the transaction."

But observation might reveal:

Export bank statement
        ↓
Normalize dates
        ↓
Match transaction IDs
        ↓
Search invoice numbers
        ↓
Compare amounts
        ↓
Resolve discrepancies
        ↓
Send exceptions to finance
Enter fullscreen mode Exit fullscreen mode

The real requirement may be:

"Reduce manual reconciliation effort while preserving traceable exception handling."

Observation exposes implicit requirements.

Bring Stakeholders Together

Different departments often have different expectations.

HR might say:

"Managers should edit employee information."

Finance might say:

"Managers shouldn't edit salary information."

Security might say:

"Sensitive changes must be audited."

The requirement becomes more precise:

Managers
   ↓
Can edit permitted employee data
   ↓
Compensation changes require additional authorization
   ↓
Sensitive changes are audited
Enter fullscreen mode Exit fullscreen mode

Workshops are useful because contradictions become visible before implementation.

Study Existing Systems

If you're replacing an old system, don't assume it has nothing useful to teach you.

Look at:

  • existing workflows
  • spreadsheets
  • reports
  • APIs
  • database schemas
  • approval forms
  • policies
  • integrations
  • historical behavior

Existing software often contains years of accumulated business rules.

Sometimes the best requirements are hiding inside:

what the current system already does.

Use Prototypes

People often discover missing requirements when they see something concrete.

Imagine showing:

Expense #1042

Employee: John
Amount: $1,450
Category: Travel

[Approve] [Reject] [Request Changes]
Enter fullscreen mode Exit fullscreen mode

The stakeholder says:

"Managers shouldn't approve anything above $1,000."

Excellent.

The prototype just revealed a business rule.

Prototypes aren't only design tools.

They're requirement discovery tools.


Ask About Failure, Not Just Success

Most people naturally explain the happy path.

For example:

"A customer places an order and we ship it."

But production systems need answers to:

  • What if payment fails?
  • What if payment succeeds but order creation fails?
  • What if inventory disappears?
  • What if the customer cancels?
  • What if the address is invalid?
  • What if the order is partially shipped?
  • What if the payment provider sends the same webhook twice?
  • What if the external service is unavailable?

The happy path is usually easy.

The exceptions define the real system.

This is why one of the most valuable questions in requirement engineering is:

"What happens when things go wrong?"

And an even stronger one is:

"What must never happen?"

For example:

Payments
→ Customer must never be charged twice.

IAM
→ A user must never access another tenant's data.

Payroll
→ An employee must never be paid incorrectly.

Inventory
→ Stock must not become negative unless explicitly allowed.

Ownership
→ Two people must never become owners simultaneously.
Enter fullscreen mode Exit fullscreen mode

These statements reveal business invariants.

And invariants are extremely valuable architectural information.


Functional vs Non-Functional Requirements

Another important distinction is between functional and non-functional requirements.

Functional Requirements

These describe what the system does.

Examples:

  • Users can create invoices.
  • Managers can approve expenses.
  • Employees can submit leave requests.
  • Administrators can deactivate users.
  • Customers can reset passwords.

Non-Functional Requirements

These describe how the system should behave or what constraints it must satisfy.

Examples:

  • APIs should meet a defined latency target.
  • Sensitive data must be encrypted.
  • Protected resources require authentication.
  • The system must support a defined number of concurrent users.
  • Audit records must be retained for a specified period.
  • The service must meet an agreed availability target.

A simple mental model:

Functional
=
Capability

Non-Functional
=
Quality + Constraint + Operational Expectation
Enter fullscreen mode Exit fullscreen mode

Both can change architecture.

Imagine:

System A
50 internal users
Single region
Business hours
Enter fullscreen mode Exit fullscreen mode

versus:

System B
5 million users
Multiple regions
24/7 operation
High availability
Enter fullscreen mode Exit fullscreen mode

Both could have:

"Users can create profiles."

But System B may require:

  • caching
  • replication
  • partitioning
  • queues
  • rate limiting
  • CDN
  • horizontal scaling
  • observability
  • disaster recovery

The feature didn't change.

The constraints did.


Requirements Shape Architecture

A useful mental model is:

Business Goal
      ↓
Requirements
      ↓
Constraints
      ↓
Architecture
      ↓
Implementation
Enter fullscreen mode Exit fullscreen mode

Consider:

"Every financial change must be traceable."

That requirement might influence:

  • database design
  • transaction boundaries
  • audit tables
  • event logging
  • authorization
  • API design
  • service boundaries
  • data retention

Another example:

"A payment must only be completed once."

That can lead to:

  • idempotency keys
  • unique constraints
  • event IDs
  • transaction boundaries
  • webhook verification
  • retry handling

Another:

"An organization can only access its own data."

That can influence:

  • authentication
  • authorization
  • database queries
  • service-layer rules
  • background jobs
  • caching
  • testing

This is why requirement engineering is not separate from architecture.

Requirements are constraints on architecture.


Requirements Create Domain Models

Consider:

"An organization has members with different roles."

Simple?

Not for long.

You need to ask:

  • Is a role global or organization-specific?
  • Can one member have multiple roles?
  • Who can assign roles?
  • Can roles be removed?
  • Are permissions inherited?
  • Can roles change over time?
  • Should role changes be audited?

You may eventually discover:

Organization
      ↓
Membership
      ↓
Role
      ↓
Permission
Enter fullscreen mode Exit fullscreen mode

The requirement didn't simply create a UI.

It created a domain model.

The same thing happens with:

  • orders
  • payments
  • invoices
  • employees
  • subscriptions
  • ownership
  • verification
  • inventory

A requirement often contains the seeds of the domain model.


Current State Isn't Always Enough

Suppose:

"John is a manager."

That's current state.

But six months later someone asks:

"Was John a manager when he approved expense #10042?"

Now you need historical state.

This is why enterprise systems often need to answer:

"What was true when this event happened?"

That can affect:

  • audit design
  • financial records
  • reporting
  • authorization history
  • compliance
  • event storage

Consider an employee who leaves the company.

You might not want:

DELETE Employee
Enter fullscreen mode Exit fullscreen mode

Instead:

Employee.status = inactive
Enter fullscreen mode Exit fullscreen mode

Why?

Because historical payroll, expenses, approvals, and audit records may still depend on that employee.

This leads to an important principle:

Business history is often more important than current state.


A Ticket Is Not a Requirement

A ticket says:

"Add employee deletion."

Don't immediately write:

def delete_employee():
    ...
Enter fullscreen mode Exit fullscreen mode

Ask:

"What does delete mean in this business?"

It could mean:

Hard Delete

The record disappears.

Soft Delete

The record remains but is marked inactive.

Deactivation

The employee loses access but historical records remain.

Archival

The employee moves into a historical state.

Termination

A business event occurs and triggers downstream workflows.

These are completely different behaviors.

The ticket gave you a verb.

Your job is to discover the domain meaning behind the verb.


User Stories Are Useful — But Not Enough

Consider:

As a manager, I want to approve an expense so that employees can be reimbursed.

That's useful.

But you still need to know:

Preconditions

Expense exists
Expense is submitted
Manager has permission
Enter fullscreen mode Exit fullscreen mode

Rules

Manager cannot approve their own expense
Large expenses require additional approval
Enter fullscreen mode Exit fullscreen mode

State Transition

Submitted → Approved
Enter fullscreen mode Exit fullscreen mode

Failure Cases

Already approved
Already rejected
Permission removed
Enter fullscreen mode Exit fullscreen mode

Audit

Who?
When?
What changed?
Enter fullscreen mode Exit fullscreen mode

A user story describes intent.

It doesn't necessarily describe the complete system behavior.


Think in State Machines

Many business processes are really state machines.

For example:

Draft
  ↓
Submitted
  ↓
Under Review
  ↓
Approved
  ↓
Completed
Enter fullscreen mode Exit fullscreen mode

Not every transition is valid.

For example:

Draft → Completed
Enter fullscreen mode Exit fullscreen mode

may be forbidden.

And:

Approved → Rejected
Enter fullscreen mode Exit fullscreen mode

may be impossible.

Once you recognize the state machine, the requirement becomes much clearer.

You can define:

State
Transition
Actor
Permission
Condition
Side Effect
Enter fullscreen mode Exit fullscreen mode

This model is useful for:

  • orders
  • payments
  • invoices
  • expenses
  • leave requests
  • ownership transfers
  • verification
  • onboarding
  • procurement
  • support tickets

A workflow isn't just a sequence of screens.

It's a set of valid state transitions.


Requirements Can Define Transactions

Consider ownership transfer:

Validate target
      ↓
Accept request
      ↓
Remove old owner
      ↓
Assign new owner
      ↓
Create audit event
Enter fullscreen mode Exit fullscreen mode

What happens if the old owner is removed but the new owner isn't assigned?

The organization could become invalid.

So the requirement may be:

"Ownership transfer must happen atomically."

That's not merely a coding preference.

It's a business integrity requirement.

It can lead directly to:

  • database transactions
  • consistency rules
  • locking
  • failure handling
  • service boundaries

Business requirements can become database requirements.


Requirements Can Define Security Boundaries

Consider:

"Organizations can manage their own users."

That sentence contains a security boundary.

The system must guarantee:

Organization A
      ↓
Authorized Users
Enter fullscreen mode Exit fullscreen mode

cannot access:

Organization B
      ↓
Private Users
Enter fullscreen mode Exit fullscreen mode

This affects more than the UI.

It affects:

  • queries
  • services
  • APIs
  • authentication
  • authorization
  • background jobs
  • caching
  • logging
  • tests

For multi-tenant systems, tenant isolation isn't just an implementation detail.

It's a requirement.


Requirements Should Be Testable

Consider:

"The application should be fast."

Everyone agrees.

But how do you test it?

A better requirement might be:

"95% of API requests under the expected workload should complete within 300ms."

Now it is measurable.

The same principle applies to business behavior.

Instead of:

"Managers can approve expenses."

Use something closer to:

Given an authorized manager and a submitted expense, when the manager approves it, the expense transitions to Approved, the approver and timestamp are recorded, and an audit event is created.

Now:

  • the developer knows what to build
  • QA knows what to test
  • the buyer knows what to expect

That's the value of acceptance criteria.


Requirement Validation

After discovering requirements, validate them.

Ask:

Is it correct?

Does it represent the real business need?

Is it complete?

Are important cases missing?

Is it consistent?

Does it conflict with another requirement?

Is it feasible?

Can the system realistically satisfy it?

Is it testable?

Can we objectively determine whether it works?

Is it traceable?

Can we connect it to the business goal, implementation, and tests?

A requirement such as:

"The system should be secure."

is too broad.

Break it into observable guarantees:

Protected endpoints require authentication.
Users cannot access another tenant's resources.
Sensitive actions are audited.
Reset tokens expire.
Authorization is enforced server-side.
Payment confirmations are verified server-side.
Enter fullscreen mode Exit fullscreen mode

Now the team can actually build and test against it.


Ambiguity Is Expensive

Imagine a requirement misunderstanding happens during week one.

The team discovers it before implementation.

Maybe the solution is a conversation and a revised workflow.

Now imagine the same misunderstanding is discovered after production.

You may need:

  • database migrations
  • API changes
  • frontend changes
  • data migration
  • regression testing
  • security review
  • deployment changes
  • customer communication

The exact cost varies.

But the principle is consistent:

The later ambiguity is discovered, the more expensive it becomes.

This is why requirement engineering isn't bureaucracy.

It's risk management.


Estimation Starts With Understanding

Developers are often asked:

"How long will this take?"

But estimating an ambiguous requirement is mostly guessing.

Compare:

Requirement A

"Build reporting."

Requirement B

"Finance managers need monthly revenue reports filtered by organization, branch, customer, product category, payment status, and date range. Reports must support CSV export and preserve historical transaction values."

Requirement B is much easier to estimate.

Not because it's necessarily smaller.

Because uncertainty has been reduced.

Better requirements don't make work magically disappear.

They make the work visible.


Prioritization Is Also Risk Management

Not every requirement deserves the same priority.

A practical model:

Must Have

The system cannot provide its core value without it.

Should Have

Important, but not essential for the first release.

Could Have

Useful, but optional.

Won't Have Now

Explicitly outside the current scope.

But business priority isn't the only consideration.

Technical risk matters too.

Consider:

"Integrate with the bank's payment system."

It sounds like one feature.

But it may involve:

  • authentication
  • webhook verification
  • retries
  • idempotency
  • reconciliation
  • settlement
  • refunds
  • duplicate events
  • failure recovery

That requirement may deserve early investigation even if it isn't the most visible feature.

A useful prioritization model considers:

Business Value
+
Technical Risk
+
Dependencies
+
Cost
+
Security Impact
Enter fullscreen mode Exit fullscreen mode

Requirement Changes Need Impact Analysis

Requirements don't stop changing when development begins.

Businesses change.

Customers change.

Regulations change.

New information appears.

Suppose the buyer says:

"Managers should also be able to approve payroll."

Don't simply add a permission.

Ask:

"What else changes?"

Potentially:

Authorization
     ↓
Workflow
     ↓
Payroll Data Access
     ↓
Audit
     ↓
Notifications
     ↓
Reporting
     ↓
Compliance
     ↓
UI
     ↓
Tests
Enter fullscreen mode Exit fullscreen mode

This is why:

"It's just a small change."

can sometimes become a very expensive sentence.

A requirement change can have architectural consequences.


What Great Engineers Actually Do

Great engineers don't just translate tickets into code.

They ask:

Is this the real problem?

What assumption are we making?

What happens when it fails?

Who is allowed to do this?

What happens to historical data?

What if the request is duplicated?

What if two users act simultaneously?

What if an external service is unavailable?

What happens when the business grows?

What must never happen?

These questions reveal the system's true boundaries.

That's engineering judgment.


The Difference Between Working Software and Correct Software

A system can technically work.

The API returns 200.

The UI looks beautiful.

The database is normalized.

The tests pass.

And the product can still be wrong.

Why?

Because:

Implementation correctness does not guarantee business correctness.

You can implement the specification perfectly and still fail if the specification misunderstood the business.

That's why one of the most important questions isn't:

"Did we build it correctly?"

It's:

"Did we define the right thing correctly?"


The Real Engineering Loop

A mature development process looks more like:

Understand
   ↓
Question
   ↓
Model
   ↓
Validate
   ↓
Design
   ↓
Implement
   ↓
Test
   ↓
Observe
   ↓
Learn
   ↓
Refine
Enter fullscreen mode Exit fullscreen mode

Not:

Ticket
  ↓
Code
  ↓
Deploy
Enter fullscreen mode Exit fullscreen mode

The second approach may feel faster.

The first approach is much more likely to produce software that survives real-world complexity.


For Buyers: Ask Better Questions

If you're buying serious software, don't ask only:

"How quickly can you build this?"

Ask:

"How will you make sure you're building the right thing?"

Before development begins, you should expect discussion around:

  • business goals
  • workflows
  • users
  • roles
  • permissions
  • data
  • integrations
  • exceptions
  • performance
  • security
  • scalability
  • reporting
  • operational requirements

A developer who asks difficult questions isn't necessarily slowing your project down.

They may be preventing expensive mistakes.


For Developers: Your Value Isn't Just Code

Frameworks change.

Libraries change.

Cloud services change.

Programming languages evolve.

But the ability to understand ambiguous problems remains valuable.

A developer who can:

  • discover requirements
  • challenge assumptions
  • model domains
  • identify invariants
  • understand workflows
  • design secure boundaries
  • reason about failure
  • preserve business history
  • evaluate tradeoffs

is solving a much bigger problem than implementing endpoints.

Your value isn't simply:

How fast can I write code?
Enter fullscreen mode Exit fullscreen mode

It's:

How accurately can I turn
an ambiguous business problem
into a reliable system?
Enter fullscreen mode Exit fullscreen mode

That's a much harder skill.

And a much more valuable one.


The Question Worth Remembering

Before writing the first line of code, ask:

"What do we need to know before we can confidently build this?"

That question may expose:

  • an unclear business rule
  • an undefined permission boundary
  • missing historical requirements
  • an unknown failure scenario
  • an architectural constraint
  • a security risk

And finding those things early is usually far cheaper than discovering them after production.

Because in software engineering:

A fast developer can build the wrong thing quickly.

A strong engineer makes sure the right thing is being built in the first place.


Key Takeaways

  • Great software starts with understanding the problem, not choosing a framework.
  • Requirements are not the same as features.
  • Ask why, what, and under what conditions.
  • Discover both the happy path and failure paths.
  • Functional requirements define capabilities.
  • Non-functional requirements define quality and constraints.
  • Requirements can directly shape architecture.
  • Historical data can be as important as current state.
  • Authorization and tenant isolation are requirement-level concerns.
  • Business invariants should be explicitly identified.
  • State transitions reveal hidden complexity.
  • Transactions and idempotency can emerge directly from business requirements.
  • Acceptance criteria turn vague expectations into observable behavior.
  • Requirement changes should trigger impact analysis.
  • Great engineers don't just implement tickets—they challenge assumptions.
  • Better questions reduce ambiguity, rework, cost, and architectural surprises.
  • Thinking is part of engineering.

Top comments (0)