diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Revision history for fcf-family
 
+[Current version](https://gitlab.com/lysxia/fcf-family/-/blob/main/fcf-family/CHANGELOG.md)
+
+## 0.2.0.0
+
+- Change `fcfify` to deduplicate calls with the same argument.
+- Add `fcfify'` with the old stateless behavior.
+- Add `fcfifySkip`.
+
 ## 0.1.0.0 -- 2023-01-23
 
 * First version. Released on an unsuspecting world.
diff --git a/fcf-family.cabal b/fcf-family.cabal
--- a/fcf-family.cabal
+++ b/fcf-family.cabal
@@ -1,6 +1,6 @@
 cabal-version:      3.0
 name:               fcf-family
-version:            0.1.0.0
+version:            0.2.0.0
 synopsis: Family of families: featherweight defunctionalization
 description:
   Promote regular type families to first-class,
diff --git a/src/Fcf/Family.hs b/src/Fcf/Family.hs
--- a/src/Fcf/Family.hs
+++ b/src/Fcf/Family.hs
@@ -15,13 +15,12 @@
 -- = The family of type families
 --
 -- /fcf-family/ promotes
--- regular type families to first-class families without
--- defining new symbols (which would either pollute the type namespace
--- or require a centralized organization to decide where the symbol
--- for each family should be defined).
+-- regular type families to first-class families without requiring new symbols
+-- that pollute the type namespace nor a centralized organization to decide
+-- where the symbol for each family should be defined.
 --
--- Every type family is uniquely identified by its qualified 'Name':
--- package, module, and base name.
+-- All we need is a single symbol 'Family' indexed by a qualified name that
+-- uniquely identifies an existing type family: package, module, and base name.
 --
 -- Example names:
 --
@@ -32,9 +31,6 @@
 -- -- Data.Type.Bool.If
 -- 'MkName' \"base\" \"Data.Type.Bool\" \"If\"
 -- @
---
--- Hence, a single symbol 'Family' indexed by names
--- is sufficient to promote all regular type families.
 --
 -- == Promoting a type family to first-class
 --
diff --git a/src/Fcf/Family/Meta.hs b/src/Fcf/Family/Meta.hs
--- a/src/Fcf/Family/Meta.hs
+++ b/src/Fcf/Family/Meta.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE DataKinds, PolyKinds, TemplateHaskell, TypeFamilies #-}
--- {-# OPTIONS_GHC -ddump-splices #-}
 
 -- | 'Eval' and friends as part of the family of families.
 module Fcf.Family.Meta () where
diff --git a/src/Fcf/Family/TH.hs b/src/Fcf/Family/TH.hs
--- a/src/Fcf/Family/TH.hs
+++ b/src/Fcf/Family/TH.hs
@@ -8,6 +8,8 @@
 module Fcf.Family.TH
   ( -- * Generate boilerplate
     fcfify
+  , fcfifySkip
+  , fcfify'
 
     -- * Using promoted families
   , promoteFamily
@@ -24,22 +26,65 @@
   ) where
 
 import Control.Applicative (liftA2)
+import Control.Monad (when)
 import Data.Function (on)
+import Data.Functor (($>))
 import Data.List (sort)
 import Data.Maybe (fromMaybe)
 import Data.Foldable (foldl')
 import Data.Traversable (for)
+import Data.Set (Set)
 import qualified Data.Set as Set
 import Language.Haskell.TH
+import Language.Haskell.TH.Syntax (getQ, putQ)
 
 import Fcf.Core
 import Fcf.Family hiding (Name)
 
 -- | Generate the boilerplate needed to promote a type family to first class.
 --
+-- Required extensions:
+--
+-- - @DataKinds@
+-- - @PolyKinds@
+-- - @TypeFamilies@
+--
+-- If 'fcfify' is called more than once with the same 'Name' in the same module,
+-- only the first invocation generates declarations; subsequent declarations
+-- return the empty list, avoiding duplicate declarations in the current module.
+--
+-- For a stateless variant, use 'fcfify''.
+--
 -- See "Fcf.Family" for details on the encoding.
 fcfify :: Name -> Q [Dec]
-fcfify name = reifyTyInfo name >>= fcfifyInfo
+fcfify name = do
+  check <- checkFcfified name
+  if check then pure [] else fcfify' name
+
+-- | Mark a type family as already fcifified.
+fcfifySkip :: Name -> Q [Dec]
+fcfifySkip name = checkFcfified name $> []
+
+-- | Store invocations of 'fcfify' to avoid generating duplicate instances
+-- in the current module (a minor performance optimization).
+newtype Fcfified = Fcfified (Set Name)
+
+-- | Check whether we've already seen this name.
+-- Add the name to the registered set.
+checkFcfified :: Name -> Q Bool
+checkFcfified name = do
+  Fcfified seen <- fromMaybe (Fcfified Set.empty) <$> getQ
+  let check = name `Set.member` seen
+  when (not check) (putQ (Fcfified (Set.insert name seen)))
+  pure check
+
+
+-- | Generate the boilerplate needed to promote a type family to first class.
+--
+-- Unlike 'fcfify', this always returns the same declarations for the same
+-- named type.
+fcfify' :: Name -> Q [Dec]
+fcfify' name = reifyTyInfo name >>= fcfifyInfo
   where
     ?funName = "fcfify"
     ?name = name
