This is a cache of https://developer.ibm.com/tutorials/oo-v-functional-programming/. It is a snapshot of the page as it appeared on 2026-03-09T13:20:43.763+0000.
Object-oriented vs functional programming: Core concepts explained
IBM Developer

Tutorial

Object-oriented vs functional programming: Core concepts explained

Learn the essential principles of OOP and functional programming with Java and Python examples

By BJ Hargrave

All data processing applications are solutions to business problems that take information (the data) and do something useful with it (the processing).

Structured and other procedural programming languages stress data structures and the subroutines that act on them. There is no formal concept of visibility built into the language; any subroutine may act on any data structure or call any subroutine.

The concept of visibility is a key differentiator between structured programming and object-oriented programming languages. The ways in which objects may interact are limited (that is, through an object's interface only), so object-oriented (OO) programming makes writing maintainable code easier.

Since the interface is the only way to interact with the object (we say "the interface must be obeyed"), working with objects is more straightforward than working with data structures that have no interface.

But, let's face it, not every software system needs to be decomposed into a sea of objects. Imposing an object-oriented model onto a system that is better modeled using a function-oriented programming approach is a poor design choice and leads to design friction, bad implementations, or bugs.

Fundamental concepts for object-oriented programming languages

Before we can compare and contrast object-oriented and functional programming, let's review these fundamental concepts for object-oriented programming:

  • Objects and classes
  • Object hierarchies

Objects and classes

An object consists of state plus behavior.

  • State is comprised of the attributes of the object (the data) and is not exposed outside of the object. The object's data is visible only to the program logic within the object.

  • Behavior is the program logic (the code) that acts on that state through an externally visible interface. The object's interface consists of a select set of exposed program logic called methods that provides the only means of interacting with the object.

The object's state is not exposed outside of the object and this reduces the number of locations in the source code where the data is touched, resulting in fewer side effects and more maintainable code.

It's difficult to talk about objects without also talking about classes.

Consider construction blueprints: detailed designs for houses, buildings, and other complicated physical structures. A class is like the blueprint for an object. Just like a house is built from a set of blueprints, an object is built, or instantiated, from its class. An object is an instance of a class.

Object hierarchies

For the purposes of this discussion, consider two types of hierarchies:

  • Taxonomy is based on one or more shared intrinsic properties.
  • Collection is containment based on one or more extrinsic properties.

Taxonomy

Think about biological classification for a moment (which, by the way, is where taxonomies were first used). For example, human beings (Homo sapiens) are mammals, which are primates, which are vertebrates, and so on.

Taxonomies are ways of organizing living things that share similar characteristics in a hierarchy where the similarities become more and more general as you move "up" the hierarchy and more specific as you move "down." For example, cats and dogs are different (specific), but both are classified as mammals (general).

Our brains are really good at classifying things in taxonomies and some really smart people decided that data processing would be better if we had programming languages to support decomposing business problems using taxonomy.

Think of the relationship between constituents of a taxonomy as an "is a" relationship. For example, a dog "is a" mammal, a book "is a" publication, and a Tyrannosaurus "is a" reptile (although I suppose "was a" would better apply in the last example).

Collections

Collections are hierarchies of containment, organized by an arbitrary extrinsic rather than intrinsic set of properties in which one thing in the hierarchy contains one or more other things.

Consider a set of data on a disk, organized into directories and files. Directories can contain other directories and files. There is nothing intrinsic about the relationship; some agent (a person or a program) decided to organize them in a particular way. For example, you're free to arrange (or rearrange) the files on your computer any way you see fit.

Another example is a company, comprised of divisions, which are made up of departments, which have teams, which have employees. Again, there is nothing intrinsic about these associations other than some agreed upon convention.

Think of the relationship between constituents of a collection as "has a" relationships. For example, a division "has a" department (or departments), a directory "has a" file (or files), and a database table "has a" record (or records).

Object-oriented principles

At first, object-oriented analysis, design, and programming (collectively called OO) seem difficult to understand, rife with terms like "abstraction", "encapsulation", "polymorphism", and "bokononism" (okay, I made that last one up).

It might seem like objects just makes things harder, but in reality, thinking of the world (and software in particular) in terms of objects actually makes it easier. Let me explain.

The best way to talk about these principles is to define them in ordinary, everyday language with examples. After all, an object is really just a "thing" (and we're all familiar with things).

In a nutshell, OO involves these three principles:

  • Encapsulation in which an object hides (encapsulates) its internal details
  • Inheritance in which an object extends (via inheritance) the characteristics of ancestor objects to specialize its behavior
  • Polymorphism in which objects that implement the same interface behave differently (take different forms) depending on the underlying implementation

I won't talk about abstraction here. A good understanding of abstraction is necessary to do OO well, but not to understand the basics. Be sure you check out this link on abstraction if you want to learn more.

Encapsulation

Encapsulation means the object hides its internal details behind an interface. This concept contains two important aspects:

  • Information hiding
  • Restricted access

Information hiding

Recall that an object has state and behavior: its state is not visible outside of the object and its behavior constitutes its interface.

From an object-oriented perspective, the object's state is its information, hidden from the outside.

Since the object's state is not exposed to the outside, the number of state transitions the object can undergo is limited, leading to fewer side-effects and potential bugs. The code that can touch the object is limited to the code within the object, so bugs are less likely to be introduced and are much easier to track down when they are because there is a fewer number of places in the code where the bug can be.

Restricted access

Interaction with the object from outside is restricted to its interface. The object's behavior forms a sort of capsule, behind which the object's internal workings are sealed off from outside. We say it is encapsulated.

The object's behavior is determined by its interface, limiting the ways the object may be used. Since the object's interface alone determines the behavior of the object, developers who write code to use the object know what to expect, making the object easier to use, test, and debug.

Example: ATM

Think of an Automatic Teller Machine. As you can imagine, the machine itself includes complex circuitry, network communications technology, a currency reservoir, power connection, and so forth.

As a user, you have restricted access: a place to insert your ATM card, a keypad to enter your PIN, a touch screen with which to interact with the machine, a slot to retrieve currency, and so on, but the machine's internals are hidden from you.

Your interface to the ATM is the only way with which you are allowed to interact with the machine; its internals are encapsulated. As you can imagine, if this were not the case and a user required a deep understanding of how an ATM's internals worked to use one, it would be a useless consumer technology.

Inheritance

When an object extends the characteristics of another object to specialize its behavior, we say that the former inherits from the latter. Inheritance reduces the amount of code that has to be maintained.

Suppose an object's interface does not provide all of the behavior you need from the object? You extend the original (parent) class, creating a new, specialized (child) class. The specialized child class reuses (via inheritance) the parts of the parent class that already do what you need and you only need to modify or add those parts that require specialization. The result is less code. The concept is called code reuse.

Inheritance creates an "is a" relationship

The specialized object inherits the characteristics of its predecessor, much like a child inherits traits from its parents. Parent and child objects form a taxonomy-style hierarchy since the child object is a type of its parent, like a dog is a mammal and our old friend Tyrannosaurus is a reptile.

Example: Person

Imagine there is an object representing a person. We can create a specialization of person called Employee, which inherits the traits of a person (such as Name) and adds traits like Employee Id and Hire Date.

We can create a Manager object that inherits from Employee and a Senior VP object that inherits from Manager. And so on.

Polymorphism

Objects that implement the same interface behave differently (take different forms) depending on the underlying implementation. Let's look at a simple example.

Example: Shape

Imagine there is an interface called Shape with the single method draw. Now suppose there are several objects that implement this interface, hiding (encapsulating) the details behind their respective interfaces. Suppose one of those objects is Circle, another is Square, and a third is Triangle.

Now imagine that the software that uses these objects renders (draws) them on a display, and in the process is simply handed a Shape, unaware of the underlying Shape implementation.

Since all of the objects it deals with implement the Shape interface, the draw method can be invoked on each object, rendering the object according to its underlying implementation.

When a Circle is drawn, it looks very different from a Square, which looks different still from a Triangle.

This is polymorphism in action. Through a single interface -- Shape -- the behavior takes many (poly) forms (morphe), depending on the details of the underlying object.

Function-oriented principles

As mentioned earlier, not every software application can be decomposed into a set of objects. Sometimes, a function-oriented approach might be better. Let’s consider these fundamental principles of function-oriented programming:

  • Immutability: Functions don't change the state of the system
  • First-class functions: Functions are defined and used at the same level as classes
  • Functions are pure: Functions only take parameters, perform a computation, and return results

Let's look at these in more detail.

Immutability

An immutable object is one whose state cannot be changed. If the object needs to be modified, instead of modifying the object, a new object is created with a different state.

This has several benefits:

  • Immutable objects are thread-safe: An object cannot be modified by one thread and read in another (which can lead to race conditions, heap pollution, and other nasty problems that are hard to track down)
  • There are no side-effects: If an object is expected to behave a certain way based on its state and that state is changed, the change has introduced a side-effect because the object will now behave differently

First-class functions

A first-class function is one that can be passed as an argument to another function, returned from a function, and stored as a value.

In a functional programming language, functions are not just a construct of the language, but they can be stored and passed around just like any other object. This is sometimes called function as data or function as object.

First-class functions can also take functions as arguments, creating higher-order functions from lambda calculus in the process.

Functions are pure

When we say "function" in the context of functional programming, we mean function in the mathematical sense (not "block of code we can call").

Suppose we have some function f(x) defined as:

f(x) = x + 3

This function can be used to calculate a result for any value of x, independent of the state of the system. That is to say, for any given value of x this function produces the same result f(x).

For example, suppose we call this function for x = 2; the result would then be:

f(2) = 2 + 3 = 5

No matter how many times we call f(x), with the argument 2, it will return 5 so we say this function is a pure function.

In addition, a pure function does not modify the parameter values in any way, thus it has no side-effects.

Lambda functions

The term lambda function originates from Lambda calculus and it refers to a class of functions that are anonymous and can be passed around as values.

A lambda function, or expression, is a block of code that is declared like this in Java:

([params ...]) -> body

where params are optional parameters passed to the function, and the body implementing the function follows the arrow ->.

A simple lambda expression looks like this:

() -> LocalDate.now()

This lambda expression takes no parameters (()) and returns a java.time.LocalDate object when the function is invoked. Conversely, the following lambda expression takes three arguments (year, month, and day) and passes them to LocalDate.of() to create the LocalDate object.

(int year, int month, int day) -> LocalDate.of(year, month, day)

You can store a lambda expression in a variable:

Supplier<LocalDate> now = () -> LocalDate.now();

Use the following syntax to declare a function that takes a functional interface as a parameter:

String formatLocalDate(Supplier<LocalDate> localDateFactory) {
    return localDateFactory.get().format(DateTimeFormatter.ofPattern("E MM/dd/yyyy"));
}

The formatLocalDate() function takes one argument: localDateFactory, which says "A function that takes no arguments and returns a LocalDate object when invoked." This argument can be provided by a lambda expression:

var formattedLocalDate = formatLocalDate(() -> LocalDate.now());
System.out.println("The LocalDate is: " + formattedLocalDate);

You can use the now variable from earlier to refer to the lambda expression:

var formattedLocalDate = formatLocalDate(now);
System.out.println("The LocalDate is: " + formattedLocalDate);

Lambda expressions are great because they are usually little blocks of code you can store in variables and pass around as function arguments.

Be sure to check out this StackOverflow article and the Java documentation to learn more about lambda functions.

Conclusion

In this tutorial, you learned about:

  • How an object is made up of state plus behavior and that a class is a blueprint for an object
  • The two types of object hierarchies -- taxonomy and collection
  • The three fundamental concepts of OO -- inheritance, encapsulation, and polymorphism
  • The three fundamental concepts of functional programming -- immutability, first-class functions, and pure functions
  • How to define and use lambda expressions

Acknowledgements

This tutorial was originally written by J Steven Perry as part of a learning path on the Kotlin programming language. We created this tutorial from 2 of the 7 modules and updated it for the Java programming language.