Why You Should Stop Using Void
If your method returns void, I need you to sit down for a second and ask yourself a question: why do you hate the people who have to read your code?
I'm not being dramatic. Okay, I'm being a little dramatic. But void is the "I'll explain later" of return types, except later never comes. It's a method signature that actively refuses to tell you anything useful. And somehow it's the default that most developers reach for without a second thought, like it's the null of design decisions. Which, honestly, it kind of is.
Void Is a Black Hole
You call a void method. What happened? Nobody knows. Did it succeed? Maybe. Did it fail silently? Also maybe. Did it mutate some object three layers deep that you didn't even know existed? Definitely maybe.
The method signature gives you nothing. Zero information. You have to crack the thing open and read every line of the implementation just to understand what it does, let alone whether it worked. That's not a contract between caller and method — that's a trust fall. In production.
"Trust me, bro" is not an acceptable return type.
Side Effects Without a Paper Trail
Here's what void methods actually do: stuff. They mutate objects. Write to databases. Fire off notifications. Send emails to people who didn't ask for them. And the caller gets... nothing back. No receipt. No confirmation. Just vibes.
You're supposed to just know that calling UpdateUser(user) silently rewrote half the object's fields. You're supposed to just know that ProcessPayment() hit three external APIs and swallowed two exceptions. How? Institutional knowledge? Telepathy? A Slack message from six months ago that nobody pinned?
When every method in a chain returns void, debugging isn't engineering. It's archaeology. You're three hours deep in a debugger at 11 PM trying to figure out which of the twelve void methods in the pipeline decided to silently break state and tell absolutely no one about it. There's no trail. No breadcrumbs. Just side effects all the way down, like some kind of cursed lasagna.
A Real Example
Let's say you've got a user registration method. It looks like this:
public void RegisterUser(string email, string password)
{
Validate(email, password); // throws? returns? shrugs?
CreateAccount(email, password); // hope this worked lol
SendWelcomeEmail(email); // fire and forget baby
}
Beautiful. A masterpiece of ambiguity. If validation fails, does it throw? Return early? Log a warning and barrel forward like nothing happened? If CreateAccount blows up but SendWelcomeEmail still fires, congratulations — you just welcomed a user that doesn't exist. The caller has absolutely no idea what happened unless they go read three separate method bodies and piece together the truth like a detective in a noir film.
Now look at this:
public RegistrationResult RegisterUser(string email, string password)
{
var validation = Validate(email, password);
if (!validation.Success) return validation; // oh look, information
var account = CreateAccount(email, password);
if (!account.Success) return account; // wow, a signal
var emailResult = SendWelcomeEmail(account.UserId);
return new RegistrationResult(account, emailResult); // the caller knows things now
}
Every step communicates. Every step hands something back. The caller gets a RegistrationResult that tells them exactly what happened. Error handling is explicit. Flow is obvious. Nobody needs a debugger, a ouija board, or a 45-minute Zoom call to figure out what went wrong.
What to Return Instead
You don't need a PhD in Haskell to fix this. You just need to stop reflexively typing void like it owes you money.
Result types. A simple Result or Either type with a success/failure state and an optional error message. This is the single biggest improvement you can make. It forces errors into the open and makes the caller deal with them instead of pretending everything's fine while the house burns down.
The object you changed. If your method modifies something, return it. UpdateUser(user) returning the updated User makes the output visible. Radical concept, I know — showing people what your code did.
Fluent APIs. Return this and let callers chain. Instead of five void calls in a row where you're just praying each one worked, you get a readable pipeline. It's like method calls, but for people who respect themselves.
Task<T> instead of Task. If you're writing async code, Task with no type parameter is just async void wearing a trench coat. It's not fooling anyone. Return Task<T> with a meaningful result. Give the awaiter something to work with.
Use ref if you absolutely must stay void. If a method truly can't return something and it's mutating state, at least mark the parameter as ref. A signature like void UpdateUser(ref User user) is still void, but it's honest — it tells the caller upfront that the object is going to change. A void method that silently mutates its arguments without ref is lying by omission. And lying is rude.
"But What About..."
Yes, fine. There are cases where void is acceptable. Event handlers. Logging. Fire-and-forget cleanup. I'll give you those.
But those are exceptions. The problem isn't that void exists — it's that developers treat it as the default. Like it's the path of least resistance. Which it is, and that's exactly why it's dangerous. void should be a conscious, deliberate choice you make after considering the alternatives and deciding the caller truly doesn't need anything back. Spoiler: that happens way less often than your codebase suggests.
The Culture Problem
The worst thing about void isn't any single method. It's the culture it breeds. When void is the default, developers stop thinking about method contracts. They stop asking "what does the caller need to know?" Error handling becomes an afterthought. State mutations go undocumented. The codebase turns into a trust-based economy, and trust-based economies work great until someone pushes to main on a Friday.
Return something. Literally anything. A bool. A result. The object you changed. A heartfelt apology. Just stop making your methods into black holes that swallow information and give nothing back.
Your future self — and everyone else on your team — will thank you.