Good Engineers Make Problems Visible

Why good software delivery depends on realistic estimates, early communication, visible blockers, and ownership beyond writing code.

An unfinished bridge secured by a central keystone leading across project blockers toward delivery An unfinished bridge secured by a central keystone leading across project blockers toward delivery

Writing code is only one part of software engineering.

A feature can be technically excellent and still be badly delivered. It can arrive late without warning, work in isolation but fail during integration, sit blocked for days because nobody followed up, or be called “done” while testing and deployment are still pending.

After leading engineering work across different projects, one pattern keeps showing up: most delivery problems are visible much earlier than the point where they become serious.

The problem is usually not that nobody could have predicted them. The problem is that nobody made them visible.

An assumption stayed inside someone’s head. A blocker was mentioned once and forgotten. A task remained “almost done” for three days. An EOD commitment quietly became tomorrow morning. A project board stopped representing reality.

Individually, none of these things looks disastrous. Together, they make a team impossible to plan around.

Estimates Don’t Need to Be Perfect

I don’t expect developers to predict the future. I do expect them to make a reasonable attempt at understanding the work and communicating what they know.

“I’ve never done this before, so I can’t estimate it” is usually not enough.

If the work is unfamiliar, estimate the investigation first.

I haven’t worked with this integration before. Give me two hours to investigate it and I’ll provide a better implementation estimate after that.

That is a perfectly valid estimate.

The point of estimation is not to produce a magical number that can never change. It is to make the team’s current understanding of effort, risk, and uncertainty visible.

Large tasks are also much easier to estimate once they stop being treated as one giant block.

“Build the payment feature” is vague.

Database work, API contracts, third-party integration, webhook handling, UI changes, testing, deployment, and monitoring are much easier to reason about separately.

Once the work is broken down, you can usually see which parts are straightforward and which parts actually contain uncertainty.

That uncertainty should be stated instead of hidden inside a confident number.

Write Down What the Estimate Assumes

A lot of arguments about estimates happen because two people agree to the same number while imagining different scopes.

If the requirement is not completely defined, make reasonable assumptions and write them down.

For example:

This estimate assumes one active configuration per workspace and does not include historical versioning.

Or:

This includes backend implementation and unit tests, but not frontend changes or migration of existing data.

That small amount of context makes the estimate much more useful.

An estimate is a forecast based on what we currently believe the work contains. If that understanding changes, the estimate can change too.

What matters is being able to explain why.

The estimate should also include the work people conveniently forget: learning, debugging, tests, integration, review feedback, migrations, deployment validation, and fixing whatever behaves differently outside the local environment.

The first working implementation is not the completed task.

If your estimate assumes everything works on the first attempt, it probably isn’t a realistic estimate.

Delays Are Normal. Hiding Them Isn’t.

Tasks slip. APIs behave differently from their documentation. Requirements change. Bugs appear. Dependencies arrive late.

None of that bothers me nearly as much as finding out at the deadline that a task stopped being realistic two days ago.

If you know the original timeline is unlikely to hold, say it.

You do not need to wait for stand-up. You do not need to wait until somebody asks. You definitely do not need to keep saying “still working on it” while hoping one last attempt magically fixes everything.

A useful update is simple:

The API is behaving differently from the documented contract. I’m testing another approach, but the original target is no longer realistic. I’ll update the estimate once I’ve validated it.

That tells me what changed and why the timeline is at risk.

That is infinitely more useful than “still working on it.”

When a timeline moves, I generally want three things: what we originally expected, what changed, and what the new expectation is.

It does not need to become a formal incident report. The point is simply to give everyone else enough information to adjust.

Communicate Anything That Changes Someone Else’s Plan

Developers sometimes avoid sending updates because they think the issue is small or they will probably recover the lost time.

The problem is that somebody else may already be planning QA, a deployment, a client update, another task, or another developer’s work around the original timeline.

You don’t always know who is depending on the information you have.

That is why I generally prefer slightly too much communication over silence.

Not every thought needs to be broadcast into a team channel. But if something can affect scope, dependencies, QA, deployment, product decisions, or timelines, it should probably be visible.

The purpose of communication is not to report failure.

It is to keep the shared understanding of the project accurate.

Silence allows outdated assumptions to survive.

Important Decisions Should Survive the Meeting

Calls are useful because they are fast. They are also terrible places to permanently store engineering decisions.

If a meeting changes the scope, architecture, estimate, ownership, or dependency of something important, somebody should write down what changed.

It doesn’t need to become documentation theatre.

A short update in the relevant task, PR, or team channel is usually enough:

  • What was decided.
  • Why.
  • Who owns the next action.
  • Whether scope or timeline changed.
  • What is still unresolved.

The same applies to complicated debugging or architectural work. Keep enough evidence to explain important decisions later: logs, API responses, benchmark results, screenshots, useful AI conversations, documentation, or a short explanation in the task.

The goal is not to collect evidence against people.

It is to avoid losing engineering context.

“Because it worked on my machine” is not much use six weeks later when somebody needs to understand why the system was built that way.

“EOD” Is Usually a Bad Estimate

I’ve become increasingly suspicious of “by EOD.”

It sounds precise, but very often it means:

I hope I finish this before I stop working today.

Then EOD becomes late evening. Late evening becomes tomorrow morning. Tomorrow morning becomes “one small issue left.”

I’d much rather hear:

The API implementation will be ready by 3 PM. Integration testing will continue tomorrow.

Or:

I’ll have the first working version by 5 PM, but it won’t be ready for deployment yet.

That is much more useful because it separates implementation from actual completion.

Which leads to another common problem.

“Done” Needs to Mean Something

Different people use the word “done” very differently.

For one developer, it means the code exists.

For another, it means the code compiles.

For QA, it might mean the feature is deployed to a test environment.

For product, it might mean the expected behaviour has actually been validated.

Those are completely different stages.

A status update should describe the real state of the work.

Implementation complete. Tests pending.

Backend ready. Frontend integration blocked.

Deployed to QA. Validation pending.

Ready for release.

Released and verified.

That is much more useful than calling everything “done.”

Code existing is not the same thing as a feature being delivered.

Ownership Goes Beyond Your Layer

This is probably the biggest difference I see between developers who simply complete tickets and developers who genuinely own work.

If you build the API but nobody can integrate it, the feature is not done.

If the frontend works with mocked data but fails against the actual backend, the feature is not done.

If somebody else needs to provide a design, credential, API, approval, or review before the feature can move, that dependency is now part of the delivery problem.

Ownership does not mean one developer must personally write the frontend, backend, infrastructure, tests, mobile implementation, and documentation.

It means somebody cares about the complete outcome.

You can collaborate with five people and still own a feature. You understand what needs to happen, know who owns each dependency, follow up when something is blocked, verify that the pieces work together, and make sure the feature reaches its actual definition of done.

Ownership is responsibility, not isolation.

Being Blocked Is Not the Same as Waiting

If your task depends on somebody else, you may not control when that dependency is resolved.

You still control whether anybody knows about it.

Leaving one comment and silently waiting for three days is not ownership.

If something important is blocked, make sure the right person knows, explain the impact, follow up when appropriate, and keep the team aware if the timeline is affected.

Sometimes there genuinely is nothing else you can do.

That’s fine.

But the blocker should never become invisible.

Keep the Board Honest

The same principle applies to project boards.

A board that gets updated five minutes before a status meeting is not a tracking system. It is a presentation.

I should be able to look at the board and get a reasonably accurate understanding of what is happening without individually messaging every developer.

It doesn’t need minute-by-minute updates, but it should reflect reality closely enough to be trusted.

If a task has been blocked for two days, the board should not still show it happily “In Progress” with an outdated deadline and no explanation.

The current status, meaningful progress, blockers, and next checkpoint should be visible.

A reliable board reduces meetings.

An unreliable board creates more of them because nobody trusts the information anymore.

Meetings Should End With Outcomes

I’ve been in enough meetings where everyone discussed useful things and then forgot half of them by the next day.

A meeting should leave behind something usable.

I usually care about three things:

Actions — someone needs to do something.

Deferred items — useful ideas that are explicitly not part of the current scope.

Dependencies — something external that can block progress.

Every action should ideally have an owner and a meaningful outcome. “Discuss authentication” is weak. “Document the proposed authentication flow and share it for review” is much clearer.

Deferred items matter because otherwise ideas either disappear completely or quietly sneak back into the current scope.

Dependencies matter because that is often where timelines die silently.

The meeting itself is temporary. The decisions and actions should not be.

Engineering Discipline Is Mostly Visibility

Good engineering management is not about demanding perfect estimates or punishing people whenever something takes longer than expected.

Software work contains uncertainty. That isn’t going away.

A developer can estimate something incorrectly and still be doing good work. An integration can fail unexpectedly. A dependency can arrive late. A feature can turn out to be harder than anybody expected.

The problem begins when the team continues operating on information that is no longer true.

Good engineers correct that information quickly.

They make their assumptions visible. They break uncertain work down. They include testing and debugging in estimates. They communicate delays before deadlines pass. They keep blockers visible. They document decisions that matter. They follow dependencies instead of waiting silently. They keep the board reasonably accurate, and they care about the complete outcome instead of only the code they personally wrote.

Software delivery will always contain uncertainty.

Engineering discipline doesn’t remove it.

It stops uncertainty from becoming confusion.