function update(data) {
    const update = svg.selectAll('circle')
        .data(data, d => d)
    const enter = update.enter()
        .append('circle')
    const exit = update.exit()
    update.style('fill', 'black')
    enter.style('fill', 'green')
    exit.style('fill', 'red')
        .remove()
    update.merge(enter)
        .call(pulse)
}

.selectAll()

First, we select all the circles that exist. We do this even when we know for certain there are none because we need an empty selection inside of which to create circles.

.data()

Now we tell D3 what circles we want to exist by giving it our data, an array of letters between A and H that were randomly selected. Two things to note here: 1. The second argument is a key function, which tells D3 how to recognize that two items are the same between update() calls; in this case, we're using a simple equality check, but we might typically need to provide D3 with a unique identifier, as other data variables might change. 2. The selection is now different; only the circles that already existed and will continue to exist are selected. These circles are what are assigned to the "update" variable (our update selection).

.enter()

Although we're showing letters in the top left to illustrate that D3 knows about them, circles that correspond to these letters don't yet exist. By calling .enter() on the update selection, we select these non-existent circles, so that…

.append()

…we can create each one. This is our enter selection, and it contains any circle that didn't exist when we called .data().

.exit()

Just as there is an enter selection, we can create an exit selection by calling .exit() on the update selection. These are all the circles that do exist but are no longer found in the data, so we'll want to get rid of them. By using these selections, we keep what is shown in the browser consistent with the data, and have a handy means of transitioning elements in and out.

.style()

Each of these selections can be manipulated, either separately or in combination. Let's help identify those circles that already existed by coloring them black.

.style()

We'll make the newly entered circles green.

.style()

And the circles that are about to exit will be turned red.

.remove()

Those red circles can't be permitted to remain, so we call .remove() to delete them.

.merge()

Note that we don't have to operate on these selections independently. By using .merge(), we can combine two selections. This lets us perform an operation on all of the remaining circles, both those that already existed and those that were just added.

.call(pulse)

For example, we'll call a custom function that will make both the enter and update selections gently pulse. When we're ready, we can start the whole process over again with new data.