1. Previous: Chapter 14
  2. Next: Chapter 16
Kotlin: An Illustrated Guide • Chapter 15

Data Classes and Destructuring

Chapter cover image

At the end of the last chapter, we saw how all objects in Kotlin inherit three functions from an open class called Any. Those functions are equals(), hashCode(), and toString(). In this chapter, we’re going to learn about data classes, which are super-powered classes that are especially helpful when you’ve got an immutable class that mainly just holds properties.

In order to best understand data classes, let’s first visit each of the three functions above, and see what’s involved when we override them.

Overriding equals()

Reference Equality

Two fathers were crossing paths at the local park, with their daughters in tow, when each accidentally let go of his daughter’s hand.

Fathers accidentally letting go of their respective daughters' hand as the girls are distracted by the wildlife.

Both of these girls were named Fiona. However, despite having the same name, they were two different girls, so when the fathers turned around, it was important that each collected his own daughter, not just the first girl he saw who was named Fiona!

The girls crossed paths, and the fathers look on.

Similarly, in Kotlin, there are times when we want to check to see if an object is the instance that we want. To do this, we can use the equality operator, which is two adjacent equal signs ==. Let’s demonstrate this by creating a class to represent the two girls, and instantiate an object for each.

class Child(val name: String)

val fiona1 = Child("Fiona")
val fiona2 = Child("Fiona")

println(fiona1 == fiona2) // false
val fiona1 = (one girl named Fiona); val fiona2 = (another girl named Fiona)

When using the equality operator on these two objects, we see that they are not equal. In other words, they’re two different Child objects. That’s exactly what we want! After all, these two girls are different children, even though they both have the same name.

Of course, if we assign the one Child object to two different variables, then the equality operator will indicate that those two variables are equal, because they both refer to the same exact object instance.

val fiona1 = Child("Fiona")
val fiona2 = fiona1

println(fiona1 == fiona2) // true
val fiona1 = (one girl named Fiona); val fiona2 = (the same girl named Fiona)

In the code above, fiona1 and fiona2 are both assigned the same object, so they’re equal.

This kind of equality is sometimes called reference equality,1 because as long as the two variables refer to the same object instance, then they will be considered equal. By default, Kotlin uses reference equality when we use the equality operator to compare two objects.

Value Equality

The next day, the two fathers were again crossing paths in the park. This time, they each accidentally let go of a five-dollar bill at the same time.

Fathers in the park again, both accidentally dropping a five dollar bill.

When they turned around, they looked at the dollar bills on the ground, but they weren’t sure which bill belonged to which person.

Fathers looking at the five-dollar bills on the ground, unsure which bill came from which person.

However, each bill is worth the same amount - five dollars. Unlike the previous day when it was important for each father to pick up his own exact daughter, in this case, it doesn’t matter whether each person picks up the exact bill that he dropped. As long as each of them picks up one of the five-dollar bills, it’s fine, because the two bills are equal to one another. In other words, some things are interchangeable as long as they have certain characteristics that are the same.

Two girls, both named Fiona, but they're each unique. Two dollar bills, both in the amount of five dollars, and they're interchangeable.

Similarly, in Kotlin, when objects are considered equal based on their property values rather than their identity, it’s often called value equality.2

Since we would consider two five-dollar bills to be equal to each other, we would probably want a DollarBill class to have value equality rather than reference equality. The following code is roughly the same as Listing 15.1 above, but with a DollarBill class instead of a Child class.

class DollarBill(val amount: Int)

val bill1 = DollarBill(5)
val bill2 = DollarBill(5)

// We want this to be true!
println(bill1 == bill2) // false

As you can see, when we run this, we get false. How can we get this code to print true?

Under the hood, when we use the equality operator, it calls into the equals() function to determine whether the two objects are considered equal. The implementation of equals() that is handed down from the Any class will check to see if the two variables refer to the same object, which is why reference equality is the default. So if we want the equality operator to act differently for a particular class, we’ll need to override the equals() function in that class.

In the case of this DollarBill class, one simple way to achieve this is to manually delegate the equals() call to the amount property. Let’s try overriding this function in the DollarBill class. Note that in the Any class, this function has a parameter of type Any?, and a return type of Boolean, so we’ll use the same types in our equals() function here.3

class DollarBill(val amount: Int) {
    override fun equals(other: Any?) = 
        amount.equals(other.amount)
}
Error

Well, that didn’t work. The problem is that the other parameter has a type of Any?, so that any two objects in Kotlin can be compared to each other, even if their types don’t match. Since other might not actually be a DollarBill object, it won’t necessarily have a property named amount.

To fix this, we’ll need to first check whether the type of other is DollarBill.

  • If it is, then we can just use a smart cast to compare the amount values.
  • If it isn’t, then we can just return false.
class DollarBill(val amount: Int) {
    override fun equals(other: Any?) =
        if (other is DollarBill) amount.equals(other.amount) else false
}

With this change, two DollarBill objects will be equal to one another as long as…

  1. They’re both instances of DollarBill.
  2. They both have the same value for the amount property.

When we run the code again, we’ll see that bill and bill2 are now considered equal!

val bill1 = DollarBill(5)
val bill2 = DollarBill(5)

println(bill1 == bill2) // true

So, by overriding the equals() function, we were able to give our DollarBill class value equality instead of reference equality! However, there’s another problem that shows up when we try to use this class with certain collection types. This brings us to the hashCode() function.

Overriding hashCode()

Little Fiona is starting a collection of contemporary US dollar bills. In order to complete her collection, she’s going to need one bill of each of the seven denominations: $1, $2, $5, $10, $20, $50, and $100.

Seven different denominations of US dollar bills.

As you recall from Chapter 8, multiple objects can be stored in a collection type called a Set, which guarantees that each of its elements is unique. In other words, if you try to add an object to a set that it already contains, nothing will change.

We can use a Set to keep track of Fiona’s dollar bills.

val denominations = mutableSetOf<DollarBill>()

Fiona has collected bills of three different denominations so far: $1, $2, and $5. We can add those to her collection, and print out the size, just to make sure it’s got three unique items in it.

val denominations = mutableSetOf<DollarBill>()

denominations.add(DollarBill(1))
denominations.add(DollarBill(2))
denominations.add(DollarBill(5))

println(denominations.size) // 3

Perfect!

One day, Fiona finds a one-dollar bill, but can’t remember if she already collected that one. What happens when we try to add that second one-dollar bill to the set?

val denominations = mutableSetOf<DollarBill>()

denominations.add(DollarBill(1))
denominations.add(DollarBill(2))
denominations.add(DollarBill(5))
denominations.add(DollarBill(1)) // duplicate entry!

println(denominations.size) // 4

Yikes! Instead of rejecting the duplicate, the denominations set happily included it as a fourth element! Why did this happen? After all, we overrode the equals() function in the DollarBill class back in Listing 15.5!

Set itself is just an interface, and when we call mutableSetOf() it returns an implementation of Set, called LinkedHashSet.

A LinkedHashSet does not primarily use the equals() function to determine whether it already contains an object. Instead, it starts with the hashCode() function, and only calls equals() when an object with the same hash code already exists in that set.4

This is why we’re supposed to override hashCode() any time we override equals(). In our case, since we’re already delegating to the amount property for equals(), we can just do the same for hashCode().

class DollarBill(val amount: Int) {
    override fun equals(other: Any?) =
        if (other is DollarBill) amount.equals(other.amount) else false

    override fun hashCode() = amount.hashCode()
}

Easy! With this change, when we run Listing 15.9 again, the set will correctly disregard the duplicate one-dollar bill, resulting in a size of 3 instead of 4.

val denominations = mutableSetOf<DollarBill>()

denominations.add(DollarBill(1))
denominations.add(DollarBill(2))
denominations.add(DollarBill(5))
denominations.add(DollarBill(1)) // duplicate entry!

println(denominations.size) // 3 - Success!

Well, our DollarBill class is almost doing everything we want. Before we see how data classes can make our lives easier, let’s override that third and final function from the Any class, toString().

Overriding toString()

Although we’ve used the println() function frequently, we haven’t examined it closely yet. As you know, we can pass an argument to this function, and it prints it out to the screen. We’ve often passed it a string, like this:

println("Hello, Kotlin!")

However, we aren’t limited to passing strings; we can pass literally any object to this function! For example, we can send it an instance of our DollarBill class.

println(DollarBill(100))

When we run this, we’ll see this printed out:

DollarBill@64

When we pass an object to the println() function, it will call into the toString() function on that object. By default, this function returns a string that looks like the one above. This string has three parts to it:

  1. The name of the class
  2. An @ sign
  3. The hash code of the object, in hexadecimal5
DollarBill@64. DollarBill is the name of the class, and 64 is the hash code of the object, in hexadecimal.DollarBill@64Class Name@ SymbolHash Codein Hex

As you recall, the DollarBill class is delegating the hashCode() call to the amount integer in Listing 15.10. For an integer, the hash code is simply the value of the integer itself. So, if we assign the value 100 to an integer variable, its hash code is also 100. However, since toString() converts that hash code to hexadecimal, it appears as 64.

The toString() function in this class would be much more helpful if it were to show the name of the class and the value of the amount property in decimal rather than hexadecimal. While we’re at it, it’d be helpful to include a label indicating what that number represents (i.e., “amount”).

Let’s override the toString() function and return a string that includes those things.

class DollarBill(val amount: Int) {
    override fun equals(other: Any?) =
        if (other is DollarBill) amount.equals(other.amount) else false

    override fun hashCode() = amount.hashCode()

    override fun toString() = "DollarBill(amount=$amount)"
}

After making this change, when we call println() with a DollarBill object, we’ll get more helpful output.

println(DollarBill(100))
DollarBill(amount=100)

Fantastic!

Well, we’ve made a lot of changes to our DollarBill class now. Let’s review everything that we did.

  1. We made it so that two instances of DollarBill are equal to each other, as long as they have the same amount values. In other words, we gave it value equality instead of reference equality.
  2. We made it so that they are also treated as equal in sets and maps.
  3. We created a more helpful implementation of toString().

We achieved all of these things by overriding the three functions from the Any class - equals(), hashCode(), and toString(). Now that we understand why we might want to override these functions, it’s finally time to see how data classes can make our lives much easier!

Introduction to Data Classes

After all the changes we made, here’s the code that we ended up with:

class DollarBill(val amount: Int) {
    override fun equals(other: Any?) =
        if (other is DollarBill) amount.equals(other.amount) else false

    override fun hashCode() = amount.hashCode()
    override fun toString() = "DollarBill(amount=$amount)"
}

The DollarBill example has been rather simple. After all, it only has a single property. What would happen if we tried to achieve the same things for a class that includes multiple properties? Implementing toString() would be straightforward. However, equals() would be a bit more difficult, and hashCode() would be even more involved!

The good news is that whether our class has a single property or a dozen, Kotlin can automatically accomplish everything that we’ve already done in this chapter - overriding equals(), hashCode(), and toString() - and all we have to do is declare our class to be a data class. To do this, we just add the keyword data before the class declaration, like this:

data class DollarBill(val amount: Int)

In this code, we haven’t provided an override for equals(), hashCode(), or toString(). In fact, this DollarBill doesn’t even have a class body at all! And yet, in just one line, this class does everything that Listing 15.16 does!

val bill1 = DollarBill(100)
val bill2 = DollarBill(100)

bill1 == bill2                  // true
mutableSetOf(bill1, bill2).size // 1
println(bill1)                  // DollarBill(amount=100)

Again, the DollarBill class only includes a single property, but data classes can easily provide structural equality and a nice toString() result for classes that have multiple properties. For example, here’s an Address class with three properties.

data class Address(
    val street: String,
    val city: String,
    val postalCode: String
)

As this next code listing demonstrates, instances of Address use value equality, and they print out nicely.

val address1 = Address("123 Maple Ave", "Berrytown", "56789")
val address2 = Address("123 Maple Ave", "Berrytown", "56789")

address1 == address2                  // true
mutableSetOf(address1, address2).size // 1
println(address1)                     // Address(street=123 Maple Ave, city=Berrytown, postalCode=56789)

We’ve seen how data classes give us a lot of power, automatically generating useful implementations of equals(), hashCode(), and toString(). The superpowers don’t stop there, though! Data classes also include a function called copy(), which we’ll look at next.

Copying Data Classes

The properties of a data class can be declared with either val or var, but Kotlin developers tend to use data classes primarily for immutable data. In other words, it’s common to use only val properties in a data class.

As you know, when a class has a mutable property, we can simply assign it a new value. For example, here we have a data class that represents a book. Its title property is read-only, but its price property is mutable.

data class Book(val title: String, var price: Int)

When the price of the book increases, we can just set its new value.

val book = Book("The Malt Shop Caper", 18)

// The price just went up!
book.price = 20

It’s easy enough to change the value of the price property when it’s declared with var. But what about properties that are declared with val?

Naturally, we can’t change the value of a val property, but instead, we can create a copy of the entire object, substituting the new value in that copy. This approach is similar to the one we used in Chapter 8 when we created new lists by adding an element to an existing list with the plus operator.

Let’s update the Book class so that the price property is declared with val.

data class Book(val title: String, val price: Int)

With this change, when the price goes up, we can create a new variable called newBook, which has the same title as the original, but with the new price.

val book = Book("The Malt Shop Caper", 18)

// The price just went up!
val newBook = Book(book.title, 20)

Now, this is easy enough to do with just two properties, but the more properties we add, the more tedious it becomes to make a copy. To demonstrate this, let’s add four more properties to the Book class.

data class Book(
    val title: String,
    val price: Int,
    val author: String,
    val width: Int,
    val height: Int,
    val isbn: String,
)

val book = Book("The Malt Shop Caper", 18, "Slim Chancery", 6, 9, "020516918K")

// The price just went up!
val newBook = Book(book.title, 20, book.author, book.height, book.width, book.isbn)

This is a lot of boilerplate, and it’s easy to accidentally get something mixed up when relaying the values from the old object to the new one. In the code above, did you notice that the height and width values got swapped?

To avoid all that boilerplate and reduce the likelihood of these kinds of errors, data classes have a powerful function called copy(). For the book example above, instead of manually relaying each property to the Book constructor, we can just call copy() on the original book, and give it a new value for price, like this.

val newBook = book.copy(price = 20)

The copy() function has a parameter for each property in the data class. Since our Book data class has 6 properties, its copy() function has a total of 6 parameters, each of which defaults to the current value of the property.

Code for `Book` and code for a call to copy(), with arrows between them, showing that the parameters and properties correspond to each other in the order that they were declared in the constructor.dataclassBook(valtitle: String,valprice: Int,valauthor: String,valwidth: Int,valheight: Int,valisbn: String)book.copy(title: String = ,price: Int = ,author: String = ,width: Int = ,height: Int = ,isbn: String =)

When calling the copy() function, simply include named arguments for any properties that you want to change, and omit arguments for any properties that you want to stay the same. In Listing 15.26, we only provided the price parameter, so newBook will have a price of 20, but all other properties will have the same values that they had in the original book object.

As you can see, the copy() function is an incredibly powerful way to work with immutable classes, allowing our code to effectively change data, without actually mutating the individual properties!

There’s one more feature that data classes provide, which is the ability to destructure its properties. Let’s dive in!

Destructuring

When we’ve got a lot of values that all pertain to some concept, it usually makes sense to put them together. For example, if we’ve got a title, price, author, width, height, and an ISBN, we usually don’t want to deal with them as individual variables. Instead, we want them grouped all together in one structure, such as a Book class.

Pulling disparate variables together into a single class.val title: Stringval price: Intval author: Stringval width: Intval height: Intval isbn: StringBook+ title: String+ price: Int+ author: String+ width: Int+ height: Int+ isbn: String

Here’s the Book data class that we used earlier, which groups together all of the variables mentioned above.

data class Book(
    val title: String,
    val price: Int,
    val author: String,
    val width: Int,
    val height: Int,
    val isbn: String,
)

When we assemble values into a single class like this, it’s clear that these different values are all associated with one another and that, taken together, they represent the concept of a book. This usually makes it easier for developers to understand - for example, it’s clear that the title represents the title of a book, not the title of a movie. This also makes it convenient to pass all of these properties from one function to another - instead of passing each property as a separate parameter, we can just pass the Book object as a whole.6

So again, it often makes sense to put associated values together into a structure.

Sometimes, however, it makes sense to separate the individual values back out of the structure, so that they’re stored in individual variables.

Pulling the properties back out of a class, into individual variables, which is called 'destructuring'.val title: Stringval price: Intval author: Stringval width: Intval height: Intval isbn: StringBook+ title: String+ price: Int+ author: String+ width: Int+ height: Int+ isbn: String

This is called destructuring. Of course, we could do this by hand. For example, in the following code, we pull out all six properties from the book object into separate variables, some of which have names that differ from the original properties.

val title = book.title
val cost = book.price
val author = book.author
val widthInInches = book.width
val heightInInches = book.height
val isbn = book.isbn

When working with a data class, rather than manually extracting each property to a variable, we can use a destructuring assignment to pull out each property to a variable automatically. To do this, rather than declaring a single variable name, declare multiple variable names with commas, and put them all inside parentheses, like this:

val (title, cost, author, widthInInches, heightInInches, isbn) = book

This does the same thing as the code in Listing 15.28, but all in a single assignment statement. Note that the values will be assigned based on the order that the properties appear in the primary constructor of the data class. In the case of Listing 15.29 above, this means the title variable will be assigned the value of book.title, the cost variable will be assigned the value of book.price, and so forth.

Arrows between the properties of the Book class and the individual variables of the destructuring assignment. This shows how destructuring is applied in the same order that the properties are declared in the constructor of the class.Destructuring componentsare in the order theproperties aredeclared.dataclassBook(valtitle: String,valprice: Int,valauthor: String,valwidth: Int,valheight: Int,valisbn: String)val(title, cost, author, width, height, isbn) = book

Each of the values that come out of a destructuring assignment - title, price, author, and so on - is called a component.7

Finally, note that we don’t have to assign all of the components when using a destructuring assignment. For example, if we only need to pull the title and price out of a book object, we can choose to only include two variable names within the parentheses.

val (title, cost) = book

Destructuring and the Standard Library

Destructuring doesn’t only apply to our own data classes. Some of the types in Kotlin’s standard library can also be destructured. For example, back in Chapter 9, we used a class called Pair, which can be used with destructuring assignments.

val association = "Nail" to "Hammer"
val (hardware, tool) = association

One of the most common ways to use destructuring is with lambda parameters. For example, instead of an individual association, let’s say we’ve got a map of the hardware and tools.

val toolbox = mapOf(
    "Nail" to "Hammer",
    "Bolt" to "Wrench",
    "Screw" to "Screwdriver"
)

When we loop over these tools, the lambda parameter has a type called Map.Entry, which has two properties - a key and a value. Here’s how we used it way back in Listing 9.20.

toolbox.forEach { entry ->
    println("Use a ${entry.value} on a ${entry.key}")
}

A Map.Entry object can be destructured, so instead of using entry as a parameter for this lambda, we can use parentheses and two variable names, like this:

toolbox.forEach { (hardware, tool) ->
    println("Use a $tool on a $hardware")
}

When we do this, the argument to this lambda will get destructured into two different variables - the first is the entry’s key, and the second is the entry’s value. In the code above, the key will be assigned to a variable named hardware and the value will be assigned to a variable named tool.

Destructuring here is a great idea, because hardware and tool are terms related to the problem that our code solves. We might say that these terms are in the problem domain or business domain. Contrast these terms with those like entry, key, and value, which are more focused on the data structures that we’re using to implement a solution. We might say that those terms are in the technical domain.

Now let’s consider the case where we want to print out only the tools in the toolbox, but not the corresponding hardware. Naturally, we could just do this:

toolbox.forEach { (hardware, tool) ->
    println("Found a $tool")
}

Here, the hardware variable is irrelevant to the lambda. A value is assigned to it, but we’re not using it, so it’s just noise. In other words, it’s something we have to read when looking at this code, but it doesn’t affect the way the code works. In cases like this, where one of the components isn’t needed, we can substitute an underscore _ for the name of the irrelevant variable, like this:

toolbox.forEach { (_, tool) ->
    println("Found a $tool")
}

With that change, we no longer have to concern ourselves with a hardware variable that isn’t used.

Destructuring Non-Data Classes

Destructuring isn’t limited to data classes. Any object can be destructured, as long as its class includes the right functions. The secret is to add functions called component1(), component2(), component3(), and so on. These are called componentN() functions. For example, here’s the Child class from Listing 15.1 at the beginning of this chapter, but this one adds a new property for the child’s age.

class Child(val name: String, val age: Int)

If we want to add the ability to destructure this class, we don’t have to convert it to a data class. Instead, we can simply add functions called component1() and component2(), where each one returns one of the properties.

class Child(val name: String, val age: Int) {
    operator fun component1() = name
    operator fun component2() = age
}

With this, we can now use destructuring to pull out the child’s name and age.

val children = listOf(
    Child("Fiona", 5),
    Child("Jack", 7)
)

children.forEach { (name, age) -> 
    println("$name is $age years old.")
}

Note that we had to include the operator modifier on the two functions in Listing 15.38. This is the first time we’ve created an operator function. When a function includes the operator modifier, it can still be called like any other function, but it also serves some special purpose - and the particular purpose that it serves depends upon the name of the function. When a function is named as they are in Listing 15.38 (i.e., like componentN()), that special purpose is that the function will be used when the object is destructured.

We can even create an extension function to add destructuring to a class for which we don’t have the source code. For example, with a little ingenuity, we can create our own extension functions to destructure a Double into its integer and fractional parts.

operator fun Double.component1() = toString().split(".").first().toInt()
operator fun Double.component2() = toString().split(".").last().toInt()

val (integral, fractional) = 108.245
println(integral)   // 108
println(fractional) // 245

So, destructuring can be helpful in certain situations, and data classes are one way to easily add a destructuring capability to our classes!

Limitations of Data Classes

As we’ve seen in this chapter, data classes give us a lot of convenience!

  • They create equals() and hashCode() functions for value equality.
  • They create nice-looking toString() output.
  • They make it easy to create copies of an object.
  • They can be used with destructuring assignments.

However, we also give up a few things when we declare a class to be a data class, so let’s take a quick look at those.

Data Classes and Inheritance

The first and most significant disadvantage of a data class is that it cannot be extended by another class. In other words, you cannot add the abstract or open modifier to a data class. However, the data class itself can extend another class.

Two UML class diagrams showing how inheritance cannot and can be used with data classes. Product + id: String data Book + title: String data Product + id: String Book + title: String

Even though a data class can extend another class, if that superclass requires constructor arguments, things get complicated quickly, because of the second disadvantage, which relates to constructor parameters.

Constructor Parameters

The second disadvantage is that all of the constructor parameters in a data class must be property parameters. In other words, each one must be declared with either val or var. This means it’s not possible to add a constructor parameter that is only relayed to a superclass constructor.

open class Product(val id: String)

data class Book(id: String, val title: String) : Product(id)
Error

Also, a data class must have at least one parameter in its primary constructor.

Finally, while a data class may have properties that are not part of its constructor, they will not be regarded in any of the functions that are generated. For example, in the following code, the serialNumber property is not in the primary constructor.

data class DollarBill(val amount: Int) {
    var serialNumber: String? = null
}

A property like serialNumber won’t be considered in equals(), hashCode(), or toString(), because it’s declared in the body of the class, but not in the primary constructor. It won’t be possible to call copy() with it, and it won’t be used for destructuring assignments. To demonstrate this, the following code shows how two DollarBill objects are considered equal, even though they have different values for the serialNumber.

val bill1 = DollarBill(5).apply { serialNumber = "QB12345678T" }
val bill2 = DollarBill(5).apply { serialNumber = "IE87654321C" } 

println(bill1 == bill2) // true, despite different serial numbers

Even with these disadvantages, data classes are tremendously helpful, especially when dealing with immutable classes that are primarily meant to hold properties.

Summary

Enjoying this book?
Pick up the Leanpub edition today!

Kotlin: An Illustrated Guide is now available on Leanpub See the book on Leanpub

In this chapter, we learned all about data classes and destructuring, including:

In the next chapter, we’ll introduce another class modifier, which will enable us to account for every possible subclass of an interface or abstract class. See you then!

Thanks to James Lorenzen for reviewing this chapter!


  1. This is sometimes also called identity equality↩︎

  2. You might also hear this referred to as content equality or structural equality↩︎

  3. When overriding a function, we often use the same parameter types and return types that are specified by the same function in the superclass. However, Kotlin also allows you to specify a more general parameter type or a more specific return type. This feature is called variance, we’ll learn more about this in Chapter 19 when we look at how variance works for generics. ↩︎

  4. Note that this same problem also applies to maps, because mutableMapOf() returns a hash-based implementation called HashMap↩︎

  5. Most of us have grown up using numbers where each digit has one of ten possible values - 0 through 9. Since there are ten possible values, this is sometimes called the Base-10 or decimal numeral system. Hexadecimal is a numbering system where each digit can have up to 16 values, so it’s also called Base-16. After 0-9 come A, B, C, D, E, and F. For example, the number “C” in hexadecimal represents the number 12 in decimal, and the number 10 in hexadecimal represents decimal 16. ↩︎

  6. Although this is convenient, it’s also worth considering how many of the object’s values are actually needed in the function. When you pass more values than a function needs, it’s called stamp coupling, and it can limit the reusability of the function. For example, if the function only needs a title, it might be best to pass only the title rather than the whole book object, because then it could work for more than just book titles - perhaps it will also work for movie and song titles. ↩︎

  7. The term “component” is broad and can refer to many things within the disciplines of software development and system architecture. Here, we’re just using the term in the very narrow context of destructuring. ↩︎

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit