diff --git a/kwant/builder.py b/kwant/builder.py
index b5841f5f7564007f6c6ecddd986b8bebeed45466..bd94cb12b11dd0f65f76b97941af2fec9a2518c1 100644
--- a/kwant/builder.py
+++ b/kwant/builder.py
@@ -1286,7 +1286,8 @@ class Builder:
             If the symmetry of the target isn't a subgroup of the template
             symmetry.
         RuntimeError
-            If more than `max_sites` sites are added.
+            If more than `max_sites` sites are to be added.  The target builder
+            will be left in an unusable state.
 
         Notes
         -----
@@ -1297,11 +1298,12 @@ class Builder:
         if not max_sites > 0:
             raise ValueError("max_sites must be positive.")
 
-        self_to_fd = self.symmetry.to_fd
-        self_H = self.H
+        to_fd = self.symmetry.to_fd
+        H = self.H
+        templ_sym = template.symmetry
 
         # Check that symmetries are commensurate.
-        if not self.symmetry <= template.symmetry:
+        if not self.symmetry <= templ_sym:
             raise ValueError("Builder symmetry is not a subgroup of the "
                              "template symmetry")
 
@@ -1312,20 +1314,20 @@ class Builder:
             if start and not isinstance(start[0], Site):
                 start = [template.closest(start)]
 
-        # Pending sites are sites (mapped to the target's FD) that have been
-        # verified to lie inside the shape, but have not yet been added to the
-        # target, but yet without their hoppings.
-        pending = []
+        # "Active" are sites (mapped to the target's FD) that have been
+        # verified to lie inside the shape, have been added to the target (with
+        # `None` as value), but yet without their hoppings.
+        active = set()
         congested = True
         for s in start:
-            s = self_to_fd(s)
-            if overwrite or s not in self_H:
+            s = to_fd(s)
+            if overwrite or s not in H:
                 congested = False
                 if shape(s):
-                    pending.append(s)
-                    self._set_site(s, template[s])
+                    active.add(s)
+                    H.setdefault(s, [s, None])
 
-        if not pending:
+        if not active:
             if congested:
                 warnings.warn("fill(): The target builder already contains all "
                               "starting sites.", RuntimeWarning, stacklevel=2)
@@ -1334,35 +1336,66 @@ class Builder:
                               "desired shape", RuntimeWarning, stacklevel=2)
             return []
 
-        # "Done" sites are sites whose surrounding hoppings have been added to
-        # the system.
-        done = set()
-
-        # Flood-fill
-        while pending:
-            site = pending.pop()
-            if len(done) > max_sites:
-                raise RuntimeError("Maximal number of sites (max_sites "
-                                   "parameter of fill()) added.")
-
-            for neighbor in template.neighbors(site):
-                neighbor_fd = self_to_fd(neighbor)
-
-                if neighbor_fd in done or not shape(neighbor_fd):
-                    # Nothing to do for site neighbor_fd: Either it has been
-                    # already treated (including surrounding hoppings), or it
-                    # is outside the shape.
-                    continue
-
-                if overwrite or neighbor_fd not in self_H:
-                    pending.append(neighbor_fd)
-                    self._set_site(neighbor_fd, template[neighbor_fd])
+        done = []
+        old_active = set()
+        new_active = set()
+
+        # Flood-fill on the graph.  We work site by site, writing all the
+        # outgoing edges.
+        while active:
+            old_active.update(active)
+
+            for tail in active:
+                done.append(tail)
+                if len(done) > max_sites:
+                    # The graph has unbalanced edges: delete it.
+                    del self.H
+                    raise RuntimeError("Maximal number of sites (max_sites "
+                                       "parameter of fill()) added.")
+
+                # Make an iterator over head-value-pairs.
+                shift = templ_sym.which(tail)
+                templ_hvhv = template.H[templ_sym.act(-shift, tail)]
+                templ_hvhv = iter(templ_hvhv)
+                templ_hvhv = iter(zip(templ_hvhv, templ_hvhv))
+
+                hvhv = H[tail]
+                hvhv[1] = next(templ_hvhv)[1]
+                old_heads = hvhv[2::2]
+
+                # The remaining pairs are the heads and their associated values.
+                for head, value in templ_hvhv:
+                    head = templ_sym.act(shift, head)
+                    head_fd = to_fd(head)
+
+                    if head_fd not in old_active and head_fd not in new_active:
+                        # The 'head' site has not been filled yet.
+                        if not shape(head_fd):
+                            continue
+
+                        if overwrite or head_fd not in H:
+                            # Fill 'head' site.
+                            new_active.add(head_fd)
+                            H.setdefault(head_fd, [head_fd, None])
+                        else:
+                            # The 'head' site exists and won't be visited: fill
+                            # the incoming edge as well to balance the hopping.
+                            other_value = template._get_edge(
+                                *templ_sym.to_fd(head, tail))
+                            self._set_edge(*to_fd(head, tail) + (other_value,))
+
+                    # Fill the outgoing edge.
+                    if head in old_heads:
+                        i = 2 + 2 * old_heads.index(head)
+                        hvhv[i] = head
+                        hvhv[i + 1] = value
+                    else:
+                        hvhv.extend((head, value))
 
-                self._set_hopping((site, neighbor),
-                                  template[site, neighbor])
-            done.add(site)
+            old_active, active, new_active = active, new_active, old_active
+            new_active.clear()
 
-        return list(done)
+        return done
 
     def attach_lead(self, lead_builder, origin=None, add_cells=0):
         """Attach a lead to the builder, possibly adding missing sites.