Structural Pattern

Flyweight

Shares common state between multiple objects to save memory.

Flyweight Pattern: A Complete Guide with Examples in 11 Programming Languages

The Flyweight Pattern is a structural design pattern that minimizes memory usage by sharing as much data as possible with similar objects. Instead of storing all data in every object instance, the Flyweight separates intrinsic state (shared, immutable data) from extrinsic state (context-specific data passed at runtime). When your application needs to instantiate massive numbers of similar objects — think millions of particles in a game engine, thousands of glyphs in a text renderer, or enormous datasets of map markers — the Flyweight Pattern can slash memory consumption by orders of magnitude.

In this comprehensive guide, we'll explore the Flyweight Pattern with real-world examples in Python, TypeScript, Java, JavaScript, C#, PHP, Go, Rust, Dart, Swift, and Kotlin — complete with language-specific best practices, common pitfalls, and when to use this powerful pattern.

Table of Contents

What is the Flyweight Pattern?

The Flyweight Pattern reduces memory usage by sharing common state among many fine-grained objects. Rather than each object storing all its data, shared (intrinsic) data is stored once in a flyweight object and reused across all instances. Context-specific (extrinsic) data is passed in by the caller at the moment it is needed.

Think of a forest in a video game. You need to render one million trees. Each tree has a species (oak, pine, birch), a texture, and a 3D mesh — all identical for trees of the same species. What differs is each tree's position and size. Without Flyweight, you'd store the texture and mesh in every one of a million objects. With Flyweight, you store the texture and mesh once per species and pass position/size in at render time. That's potentially gigabytes saved.

Why Use the Flyweight Pattern?

The Flyweight Pattern offers several compelling benefits:

  1. Dramatic Memory Reduction: Shared intrinsic state is stored once instead of in every instance
  2. Performance: Fewer allocations mean less garbage collection pressure
  3. Scalability: Enables systems to handle millions of objects that would otherwise be impossible
  4. Separation of Concerns: Cleanly separates what an object is (intrinsic) from where/how it is used (extrinsic)
  5. Cache Friendliness: Shared objects can reside in CPU cache, improving access patterns
  6. Explicit State Management: Forces you to consciously identify and separate shared vs. unique state

Flyweight Pattern Comparison

Let's compare Flyweight with related patterns:

PatternGoalSharingState ManagementUse Case
FlyweightMinimize memory for many fine-grained objectsShares intrinsic state across instancesIntrinsic (shared) + Extrinsic (passed in)Millions of similar objects
SingletonOne instance globallyOne shared instanceAll state in one objectGlobal resource/config
PrototypeFast object creation by cloningClones are independentEach clone has its own full stateExpensive initialization
Object PoolReuse expensive-to-create objectsTemporarily borrows instancesFull state per pooled objectDB connections, threads
ProxyControl access to an objectNo sharingWraps single objectLazy loading, access control

Key Distinction: Flyweight shares state across instances that remain in use simultaneously. Object Pool recycles instances that are used one at a time.

Flyweight Pattern Explained

The Flyweight Pattern typically involves:

Core Components:

  1. Flyweight Interface: Declares the method(s) that accept extrinsic state and perform the operation (e.g., render(x, y)).
  2. Concrete Flyweight: Stores intrinsic (shared, immutable) state. Must be thread-safe since it is shared across contexts.
  3. Flyweight Factory: Creates and caches flyweight objects. Returns an existing flyweight if one already exists for the given intrinsic state, or creates a new one.
  4. Client / Context: Stores extrinsic state and holds a reference to a flyweight. Passes extrinsic state to the flyweight when calling its methods.

Intrinsic vs. Extrinsic State:

  • Intrinsic State: Independent of context, identical across many objects. Stored inside the flyweight. Examples: a tree's mesh and texture, a character's glyph bitmap, a particle's color and shape.
  • Extrinsic State: Depends on context and changes per instance. Passed in by the client. Examples: a tree's X/Y position, a character's position on screen, a particle's current velocity and location.

Common Variations:

  • Pure Flyweight: Flyweight objects have no state at all; everything is extrinsic.
  • Shared Flyweight: The most common variant — intrinsic state is shared, extrinsic is passed in.
  • Unshared Flyweight: Some flyweight implementations allow non-shared instances for cases where sharing isn't possible.
  • Flyweight + Factory: Nearly always used together — the factory is the heart of the pattern.

Class Diagram

Here's the UML class diagram showing the Flyweight structure:

Beginner-Friendly Example

Let's start with a classic example: rendering a forest. A game world contains thousands of trees. Each tree species shares the same mesh and texture. Only position and scale differ per tree instance.

from dataclasses import dataclass
from typing import Dict, List, Tuple


# Flyweight — stores intrinsic (shared) state
@dataclass(frozen=True)  # frozen=True ensures immutability
class TreeType:
    name: str
    color: str
    texture: str  # Imagine this is a large texture object

    def render(self, x: int, y: int, scale: float) -> None:
        print(f"Rendering {self.name} tree | color={self.color} | "
              f"texture={self.texture} | pos=({x},{y}) | scale={scale}")


# Flyweight Factory — creates and caches flyweights
class TreeTypeFactory:
    _cache: Dict[str, TreeType] = {}

    @classmethod
    def get_tree_type(cls, name: str, color: str, texture: str) -> TreeType:
        key = f"{name}_{color}_{texture}"
        if key not in cls._cache:
            cls._cache[key] = TreeType(name, color, texture)
            print(f"[Factory] Created new flyweight: {key}")
        return cls._cache[key]

    @classmethod
    def count(cls) -> int:
        return len(cls._cache)


# Context — stores extrinsic (unique) state; holds a reference to a flyweight
@dataclass
class Tree:
    x: int
    y: int
    scale: float
    tree_type: TreeType  # Shared flyweight

    def render(self) -> None:
        self.tree_type.render(self.x, self.y, self.scale)


# Forest — the client
class Forest:
    def __init__(self):
        self._trees: List[Tree] = []

    def plant_tree(self, x: int, y: int, scale: float,
                   name: str, color: str, texture: str) -> None:
        tree_type = TreeTypeFactory.get_tree_type(name, color, texture)
        self._trees.append(Tree(x, y, scale, tree_type))

    def render(self) -> None:
        for tree in self._trees:
            tree.render()

    @property
    def tree_count(self) -> int:
        return len(self._trees)


# Client code
if __name__ == "__main__":
    forest = Forest()

    # Plant thousands of trees — only 3 flyweight objects are created
    import random
    species = [
        ("Oak",   "dark green", "oak_texture.png"),
        ("Pine",  "green",      "pine_texture.png"),
        ("Birch", "light green","birch_texture.png"),
    ]

    for _ in range(1000):
        name, color, texture = random.choice(species)
        forest.plant_tree(
            x=random.randint(0, 5000),
            y=random.randint(0, 5000),
            scale=round(random.uniform(0.5, 2.0), 2),
            name=name, color=color, texture=texture
        )

    print(f"\nForest has {forest.tree_count} trees.")
    print(f"Unique flyweight objects: {TreeTypeFactory.count()}")
    print("\nRendering first 3 trees:")
    for tree in forest._trees[:3]:
        tree.render()

Production-Ready Example

Now let's tackle a realistic scenario: a text rendering engine. Rendering a large document requires tracking character glyphs (font, size, style — intrinsic) separately from their position on screen and color (extrinsic). A rich-text editor might have hundreds of thousands of characters, but only dozens of distinct glyph types.

from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple
from enum import Enum


class FontStyle(Enum):
    NORMAL = "normal"
    BOLD = "bold"
    ITALIC = "italic"
    BOLD_ITALIC = "bold_italic"


# Flyweight — intrinsic state
@dataclass(frozen=True)
class Glyph:
    character: str
    font_family: str
    font_size: int
    style: FontStyle

    def render(self, x: float, y: float, color: str) -> str:
        """Render this glyph at the given position with the given color."""
        return (f"Glyph('{self.character}' | {self.font_family} {self.font_size}pt "
                f"{self.style.value} | pos=({x:.1f},{y:.1f}) | color={color})")

    def measure_width(self) -> float:
        """Approximate character width (intrinsic — depends only on glyph properties)."""
        base = self.font_size * 0.6
        if self.style in (FontStyle.BOLD, FontStyle.BOLD_ITALIC):
            base *= 1.1
        if self.character in (' ', '\t'):
            base *= 0.4
        return round(base, 2)


# Flyweight Factory
class GlyphFactory:
    _cache: Dict[Tuple, Glyph] = {}
    _hits: int = 0
    _misses: int = 0

    @classmethod
    def get_glyph(cls, character: str, font_family: str,
                  font_size: int, style: FontStyle = FontStyle.NORMAL) -> Glyph:
        key = (character, font_family, font_size, style)
        if key in cls._cache:
            cls._hits += 1
            return cls._cache[key]
        cls._misses += 1
        glyph = Glyph(character, font_family, font_size, style)
        cls._cache[key] = glyph
        return glyph

    @classmethod
    def stats(cls) -> dict:
        return {
            "cached_glyphs": len(cls._cache),
            "cache_hits": cls._hits,
            "cache_misses": cls._misses,
            "hit_ratio": f"{cls._hits / max(1, cls._hits + cls._misses) * 100:.1f}%"
        }


# Context — extrinsic state per character in the document
@dataclass
class CharacterContext:
    x: float
    y: float
    color: str
    glyph: Glyph  # Reference to shared flyweight

    def render(self) -> str:
        return self.glyph.render(self.x, self.y, self.color)


# Span — a run of text with the same formatting
@dataclass
class TextSpan:
    text: str
    font_family: str
    font_size: int
    style: FontStyle
    color: str


# Document renderer — the client
class DocumentRenderer:
    def __init__(self, page_width: float = 800.0, line_height: float = 20.0):
        self._page_width = page_width
        self._line_height = line_height
        self._characters: List[CharacterContext] = []

    def add_span(self, span: TextSpan, start_x: float = 0.0, start_y: float = 0.0) -> None:
        x, y = start_x, start_y

        for char in span.text:
            if char == '\n':
                x = 0.0
                y += self._line_height
                continue

            glyph = GlyphFactory.get_glyph(
                char, span.font_family, span.font_size, span.style
            )

            if x + glyph.measure_width() > self._page_width:
                x = 0.0
                y += self._line_height

            self._characters.append(
                CharacterContext(x=x, y=y, color=span.color, glyph=glyph)
            )
            x += glyph.measure_width() + 1.0

    def render_page(self, max_chars: Optional[int] = None) -> List[str]:
        chars = self._characters[:max_chars] if max_chars else self._characters
        return [ctx.render() for ctx in chars]

    @property
    def character_count(self) -> int:
        return len(self._characters)


# Client code
if __name__ == "__main__":
    renderer = DocumentRenderer(page_width=600.0)

    # Simulate adding a rich-text document
    spans = [
        TextSpan("Hello, World! ", "Arial", 12, FontStyle.NORMAL, "#000000"),
        TextSpan("This is bold text. ", "Arial", 12, FontStyle.BOLD, "#000000"),
        TextSpan("And this is italic. ", "Arial", 12, FontStyle.ITALIC, "#333333"),
        TextSpan("Python " * 200, "Courier New", 10, FontStyle.NORMAL, "#0000FF"),
    ]

    y = 0.0
    for span in spans:
        renderer.add_span(span, start_x=0.0, start_y=y)
        y += 25.0

    print(f"Document has {renderer.character_count} characters")
    print(f"\nFirst 3 rendered characters:")
    for line in renderer.render_page(max_chars=3):
        print(f"  {line}")

    print(f"\nGlyph Factory Stats:")
    for k, v in GlyphFactory.stats().items():
        print(f"  {k}: {v}")

Real-World Use Cases

1. Game Engines — Particles and Sprites

A particle system can spawn millions of particles per second. Particle appearance (texture, shape, color palette) is shared; position and velocity are extrinsic.

class ParticleType:  # Flyweight
    def __init__(self, texture: str, shape: str, base_color: str):
        self.texture = texture
        self.shape = shape
        self.base_color = base_color

    def update(self, x: float, y: float, vx: float, vy: float, alpha: float) -> None:
        # Render particle at (x, y) with velocity and transparency
        ...

class Particle:  # Context
    def __init__(self, x, y, vx, vy, alpha, particle_type: ParticleType):
        self.x, self.y = x, y
        self.vx, self.vy = vx, vy
        self.alpha = alpha
        self.type = particle_type  # Shared flyweight

2. Text Rendering Engines

A text editor or PDF renderer stores one glyph object per unique (character, font, size, style) combination rather than per character occurrence. A 100,000-word document might have 600,000 characters but only ~200 unique glyphs.

3. Map/GIS Applications

A city map renders millions of icons — restaurants, parks, bus stops. The icon image and style is shared (flyweight); GPS coordinates are extrinsic per marker instance.

class IconType:  # Flyweight
    def __init__(self, svg_data: str, label: str, color: str):
        self.svg_data = svg_data  # Large SVG blob — stored once
        self.label = label
        self.color = color

class MapMarker:  # Context
    def __init__(self, lat: float, lng: float, icon: IconType):
        self.lat = lat
        self.lng = lng
        self.icon = icon  # Shared flyweight

4. Database Connection Pools

A connection pool is a practical implementation of Flyweight thinking — the expensive connection object is shared and reused rather than recreated per request. (Usually implemented via Object Pool, but conceptually aligned.)

5. GUI Widget Styling

CSS in browsers uses Flyweight implicitly — a computed style object is shared among all elements that have the same computed style, rather than duplicating style data per DOM node.

6. String Interning

Languages like Java, Python, and Go intern (deduplicate) string literals — all occurrences of "hello" in a program point to the same object. This is Flyweight at the language level.

# Python string interning
a = "hello"
b = "hello"
assert a is b  # True — same object in memory (interned)

Language-Specific Mistakes and Anti-Patterns

❌ Mistake #1: Mutable Flyweight State

# BAD: Flyweight has mutable state — modifying one affects all users of this flyweight
class TreeType:
    def __init__(self, name, color, texture):
        self.name = name
        self.color = color  # Can be changed!
        self.texture = texture

factory_type = TreeTypeFactory.get_tree_type("Oak", "green", "oak.png")
factory_type.color = "purple"  # Accidentally changes color for ALL Oak trees!

# GOOD: Use frozen dataclass or __slots__ with properties
from dataclasses import dataclass

@dataclass(frozen=True)
class TreeType:
    name: str
    color: str
    texture: str

❌ Mistake #2: Storing Extrinsic State in the Flyweight

# BAD: Position is extrinsic — storing it in the flyweight breaks sharing
class TreeType:
    def __init__(self, name, color, texture, x, y):  # x, y don't belong here!
        self.name = name
        self.color = color
        self.texture = texture
        self.x = x  # Extrinsic state — breaks the pattern
        self.y = y

# GOOD: Keep position in the context object
class Tree:           # Context
    def __init__(self, x, y, scale, tree_type):
        self.x = x
        self.y = y
        self.scale = scale
        self.tree_type = tree_type  # Only a reference to the flyweight

❌ Mistake #3: Not Caching in the Factory

# BAD: Factory creates new objects every time — no sharing!
class TreeTypeFactory:
    @staticmethod
    def get_tree_type(name, color, texture):
        return TreeType(name, color, texture)  # New object every call!

# GOOD: Always cache
class TreeTypeFactory:
    _cache = {}

    @classmethod
    def get_tree_type(cls, name, color, texture):
        key = (name, color, texture)
        if key not in cls._cache:
            cls._cache[key] = TreeType(name, color, texture)
        return cls._cache[key]

Frequently Asked Questions

Q1: How do I identify intrinsic vs. extrinsic state?

Ask yourself: "Would this value be the same for all objects of this kind, regardless of where or when they are used?" If yes — it's intrinsic. "Would this value change depending on where or when the object is used?" — it's extrinsic.

For a tree: the texture and mesh are the same for every Oak tree anywhere in the world map → intrinsic. But a specific Oak's position on the map depends on the instance → extrinsic.

Q2: Can the Flyweight Pattern be combined with other patterns?

Yes — it is often combined with:

  • Factory Method / Abstract Factory: The Flyweight Factory is essentially a specialized factory with a cache.
  • Composite: A composite structure (e.g., a document tree) can use Flyweights for its leaf nodes (e.g., glyphs).
  • State: State objects are often stateless and can be shared as Flyweights.
  • Singleton: If there is only one valid intrinsic state (rare), the flyweight degenerates into a Singleton.

Q3: Is Python's string interning an example of the Flyweight Pattern?

Yes. Python automatically interns short strings and string literals, so "hello" is "hello" is True. Java does the same with its string pool. This is the language runtime implementing Flyweight transparently. The pattern isn't limited to explicit use in application code.

Q4: What is the cost of the Flyweight Pattern?

The benefits come at a cost:

  • Complexity: You must carefully identify and separate intrinsic from extrinsic state.
  • Runtime overhead: Extrinsic state must be passed at every operation call rather than stored once.
  • Context management: The client (or context objects) must manage more state.
  • Factory maintenance: The factory cache can grow unbounded if not managed. Consider adding eviction (LRU cache) for large key spaces.

Q5: When is the Flyweight Pattern NOT appropriate?

Avoid it when:

  • The number of objects is small — the complexity isn't worth it.
  • Objects don't share meaningful state — there's nothing to share.
  • Memory is not a concern — premature optimization applies here.
  • Extrinsic state is hard to extract — if nearly all state is context-specific, the flyweight stores almost nothing.
  • You're working with immutable value types — languages like Rust with value semantics handle this differently.

Q6: What's the difference between Flyweight and Object Pool?

Flyweight objects are used simultaneously by many clients. Multiple threads/contexts share the same flyweight object at the same time, each passing their own extrinsic state.

Object Pool objects are checked out by one client at a time, used, then returned. The pool owns the objects and manages availability.

A database connection pool is an Object Pool, not a Flyweight — each connection is exclusive to one user at a time.

Q7: How do I handle Flyweight in multi-threaded environments?

The Flyweight object itself must be immutable (thread-safe by default — no locks needed). The Flyweight Factory must be thread-safe for cache read/write:

# Python thread-safe factory using threading.Lock
import threading

class GlyphFactory:
    _cache = {}
    _lock = threading.Lock()

    @classmethod
    def get_glyph(cls, char, font, size):
        key = (char, font, size)
        if key in cls._cache:     # Fast path — no lock needed for reads in CPython
            return cls._cache[key]
        with cls._lock:           # Slow path — lock before writing
            if key not in cls._cache:
                cls._cache[key] = Glyph(char, font, size)
            return cls._cache[key]

Q8: How do I measure if the Flyweight Pattern is actually helping?

Before applying Flyweight, profile memory first:

import sys
import tracemalloc

# Before Flyweight
tracemalloc.start()
trees_naive = [NaiveTree("Oak", "green", "oak.png", x, y) for x, y in positions]
snapshot = tracemalloc.take_snapshot()
print(f"Naive: {sum(s.size for s in snapshot.statistics('filename'))} bytes")

# After Flyweight
tracemalloc.clear_traces()
forest = Forest()
for x, y in positions:
    forest.plant_tree(x, y, 1.0, "Oak", "green", "oak.png")
snapshot = tracemalloc.take_snapshot()
print(f"Flyweight: {sum(s.size for s in snapshot.statistics('filename'))} bytes")

Q9: Can I add cache eviction to the Flyweight Factory?

Yes, use a bounded cache (LRU) when the key space is very large and memory usage of the cache itself becomes a concern:

from functools import lru_cache

class GlyphFactory:
    @staticmethod
    @lru_cache(maxsize=512)  # Only keep 512 most recently used glyphs
    def get_glyph(char: str, font: str, size: int) -> 'Glyph':
        return Glyph(char, font, size)

Q10: How does Flyweight relate to value types in modern languages?

Languages with first-class value types (Rust structs, Swift structs, C# structs, Kotlin value classes) get some Flyweight benefits for free: small value objects are stored inline rather than heap-allocated. But for large shared objects (big textures, complex meshes), explicit Flyweight with Arc<T> (Rust), class (Swift/Kotlin), or similar reference sharing is still necessary.


Key Takeaways

🎯 Core Concept: The Flyweight Pattern saves memory by sharing intrinsic (immutable, context-independent) state across many fine-grained objects, while passing extrinsic (context-specific) state at runtime. The result is that N objects of the same type are backed by a single shared instance of their common data.

🔑 Key Benefits:

  • Memory efficiency: Potentially orders-of-magnitude reduction for large object populations
  • Performance: Fewer allocations mean less GC pressure and better cache locality
  • Scalability: Enables scenarios (millions of game entities, massive text corpora) that would otherwise exhaust memory
  • Explicit design: Forces clear thinking about what state is shared vs. unique

🌐 Language-Specific Highlights:

  • Python: Use @dataclass(frozen=True) for immutable flyweights; use @lru_cache for quick factory implementation
  • TypeScript: Use readonly properties and Object.freeze(); beware async race conditions in the factory
  • Java: Use final fields; use ConcurrentHashMap.computeIfAbsent for thread-safe factory; record is ideal for flyweights
  • JavaScript: Use Object.freeze() on flyweights; use private class fields (#) for the factory cache
  • C#: Use record types (immutable by default); double-checked locking with lock for thread safety
  • PHP: Never clone inside the factory; PHP object variables are already reference-like
  • Go: Return *TreeType (pointer), never value; use sync.RWMutex or sync.Map for concurrency
  • Rust: Use Arc<T> for shared ownership of flyweights; flyweight data must have no RefCell or other interior mutability
  • Dart: Use const constructors for compile-time flyweight deduplication; use putIfAbsent on Maps
  • Swift: Use struct for context objects (stack-allocated); class for flyweights (shared reference)
  • Kotlin: Use object for the factory only with a ConcurrentHashMap; data class is ideal for the flyweight itself

📋 When to Use Flyweight:

  • Your application creates a very large number (thousands to millions) of similar objects
  • Most object data is identical across instances (good intrinsic/extrinsic separation exists)
  • Memory pressure is causing performance issues or preventing scalability
  • Objects can be easily decomposed into shared and context-specific state
  • You're building a game, text renderer, map system, or any domain with massive object populations

⚠️ When NOT to Use Flyweight:

  • The number of objects is small — the added complexity isn't worth it
  • State can't be cleanly separated — most data is unique per instance
  • Memory isn't a concern — don't optimize prematurely
  • Flyweight objects need to be mutable — mutation must be handled very carefully and is usually a design smell
  • A simpler optimization (e.g., struct layout, packing, batching) can solve the problem

🛠️ Best Practices Across Languages:

  1. Make flyweights immutable: This is non-negotiable — mutable shared state causes subtle, hard-to-debug bugs
  2. Always use a factory: Never let clients create flyweight objects directly; the factory enforces sharing
  3. Use a good cache key: Be deterministic and efficient — tuple keys or concatenated strings are common approaches
  4. Keep the factory thread-safe: Use ConcurrentHashMap, sync.Map, Arc<Mutex<...>>, or equivalent
  5. Measure before applying: Profile memory usage first — Flyweight adds complexity that isn't always needed
  6. Consider cache eviction: For unbounded key spaces, use LRU or similar to prevent factory memory leaks
  7. Document the split clearly: Clearly annotate which state is intrinsic and which is extrinsic — this is crucial for future maintainers
  8. Never clone inside the factory: Cloning defeats the entire purpose of sharing

💡 Common Pitfalls:

  • Mutable flyweight: Changing intrinsic state in one context changes it for all contexts — catastrophic
  • Extrinsic state in flyweight: Putting per-instance data inside the shared object breaks sharing and correctness
  • No factory caching: A factory that creates new objects every call gives you the interface but none of the benefits
  • Thread-unsafe factory: Concurrent writes to a shared cache without synchronization cause races and duplicate flyweights
  • Unbounded cache growth: If the key space is large and cache is never evicted, the factory itself becomes a memory leak
  • Forgetting to pass extrinsic state: Calling flyweight.render() without passing position/context leads to incorrect or default output

🎁 Advanced Techniques:

  • Flyweight + lru_cache (Python): Decorate the factory method with @functools.lru_cache for zero-boilerplate caching
  • Flyweight + Composite: Use flyweights as leaf nodes in a composite tree (e.g., text glyphs in a document tree)
  • Hierarchical Flyweights: Group flyweights at multiple levels (e.g., per-font and per-glyph-within-font)
  • Lazy Loading in Factory: Only load heavy resources (textures, meshes) on first access, then cache
  • Flyweight Registry with Metrics: Track cache hit ratio, eviction rate, and object count for observability

The Flyweight Pattern is a surgical optimization for a specific problem: too many similar objects consuming too much memory. When that problem is real, Flyweight is one of the most impactful patterns you can apply. But it brings real complexity — always measure first, separate state cleanly, make flyweights immutable, and use a proper factory. Done right, it can make the difference between an application that crashes under load and one that scales effortlessly to millions of objects.


Want to dive deeper into design patterns? Check out our comprehensive guides on: