Alex Vakhitov

software engineering2 min read

Side effects and I/O: containing the parts that touch the world

By Alex Vakhitov

Ancient stone columns and entablature against a clear blue sky

Side effects and input/output (I/O) are where a program reaches outside its own memory: files, databases, networks, logs. Developers often treat them as a nuisance because they bring unpredictability. They're also the reason software is useful at all, so the job isn't to remove them but to contain them.

Side effects are unavoidable, and necessary

Side effects aren't just by-products; they're how software interacts with the world. When you write to a file, update a database or even print a log message, you're performing a side effect. These actions reach beyond the running program and affect other systems or the future state of the application.

Could we remove them? Only by writing software that does nothing useful. What we can do is push them to the edges, so that most of the code is predictable and the parts that touch the world are few and easy to find.

Side effects and I/O are hard to control

As soon as you leave your program's memory, you're in unpredictable territory. Network latency, file permissions and hardware failures are things you can't control but have to plan for. This is where idempotence matters. An idempotent operation produces the same result however many times it runs, so it can be retried safely. A database write that sets a record to a given value, for example, gives the same result whether it runs once or three times.

Good practice

There are many good practices for dealing with side effects and I/O, but three matter most:

  1. Isolate them. Keep I/O and side effects behind clear boundaries so that the rest of the code can be tested without them.
  2. Make operations idempotent wherever you can. Then they can be retried safely without changing the result, which makes the application more reliable.
  3. Watch them. Use proper logging and monitoring to keep track of these operations.

Functional vs object-oriented programming

Functional programming (FP) and object-oriented programming (OOP) handle side effects differently.

FP tends to treat side effects as something to keep apart. They're usually isolated and pushed to the edges of the system, with explicit rules for when and how code may step outside pure functions.

OOP usually keeps side effects inside methods that change an object's state or perform I/O, so they sit alongside the object's other behaviour.

Both approaches have strengths and weaknesses. FP offers a more predictable, mathematical model, which makes code easier to reason about. OOP maps naturally onto many real-world problems, but it can make a system harder to understand when side effects are poorly managed. (For one FP answer to this problem, see Monads 101, which covers the IO monad.)

Key takeaways

  • Side effects and I/O are essential for software to do anything useful.
  • They're inherently unpredictable, so they need good practice: isolation, idempotence and monitoring.
  • Functional and object-oriented programming deal with side effects in different ways, each with pros and cons.

Understanding side effects and I/O, and managing them well, is what turns the unpredictable parts of a system into manageable ones, so that software can do its real job: solving problems reliably.