Back to Design Pattern Course

Design Pattern Course

Flyweight is a Structural Design Pattern.

The Flyweight pattern reduces memory usage and instantiation cost when your application needs to create a very large number of similar objects. Instead of creating a new instance every time, already-created instances are reused. New objects are only created when no suitable existing object can be found. This makes it especially relevant for memory-constrained environments (mobile, embedded) or for systems that need to work efficiently with hundreds of thousands or millions of fine-grained objects (characters in a text editor, particles in a game engine, tree objects on a map).

The key idea behind Flyweight is separating the object's state into two parts:

  • Intrinsic state: The part that is shared across many instances and does not change — e.g., the ticket type. This is stored in the flyweight object itself.
  • Extrinsic state: The part that is context-specific and varies from use to use — e.g., the passenger name and fare. This is passed in by the caller at the time of use, rather than stored in the flyweight.

The ticket example below has an important design issue worth understanding: name and fare are being stored as mutable fields on the RailwayTicket object and the same object is being returned to multiple callers from the cache. This means that if two callers hold a reference to the INFANT ticket object and one of them calls setName("Julia"), the other caller's reference now also points to a ticket named "Julia" — the whole point of the shared cache breaks down, because the shared objects are mutable. True Flyweight either makes the shared object immutable (intrinsic state only, no setters) and requires callers to pass extrinsic state at the time of the operation, or separates the two parts structurally. The example below is corrected to reflect this.

package com.praveen.designpatterns.structural.flyweight; /** * Flyweight — holds intrinsic state only (ticket type, which is shared). * Extrinsic state (name, fare) is passed in at the time of use. */ public class RailwayTicket { private final String type; // intrinsic — shared, immutable public RailwayTicket(String type) { this.type = type; } public void printTicket(String name, int fare) { // extrinsic — passed in System.out.println("--------TICKET--------"); System.out.println("Name: " + name + "\nTicket Type: " + type + "\nFare: " + fare); } }
package com.praveen.designpatterns.structural.flyweight; import java.util.HashMap; import java.util.Map; public class TicketFactory { private static final Map<String, RailwayTicket> ticketMap = new HashMap<>(); private TicketFactory() { } public static RailwayTicket getTicket(String type) { return ticketMap.computeIfAbsent(type, RailwayTicket::new); } }
package com.praveen.designpatterns.structural.flyweight; public class TicketBookingSystem { public static void main(String[] args) { // Extrinsic state is passed directly — the shared ticket object only holds the type RailwayTicket infantTicket = TicketFactory.getTicket("INFANT"); infantTicket.printTicket("Jack", 100); infantTicket.printTicket("Adams", 100); infantTicket.printTicket("Julia", 100); RailwayTicket adultTicket = TicketFactory.getTicket("ADULT"); adultTicket.printTicket("George", 500); adultTicket.printTicket("Rex", 500); // Only 2 RailwayTicket objects were created, not 5 System.out.println("Total ticket objects in pool: " + 2); } }

Output

--------TICKET--------
Name: Jack
Ticket Type: INFANT
Fare: 100
--------TICKET--------
Name: Adams
Ticket Type: INFANT
Fare: 100
--------TICKET--------
Name: Julia
Ticket Type: INFANT
Fare: 100
--------TICKET--------
Name: George
Ticket Type: ADULT
Fare: 500
--------TICKET--------
Name: Rex
Ticket Type: ADULT
Fare: 500
Total ticket objects in pool: 2

The claim in the original that "Flyweight is the same as Factory pattern but provides a basic caching mechanism" is inaccurate — they are different patterns with different purposes. Factory's purpose is to abstract/centralize object creation and remove new from client code. Flyweight's purpose is object reuse to reduce memory by sharing instances across many clients. A Flyweight implementation typically does use a factory-like lookup internally, but the intent and the intrinsic/extrinsic state separation are what define Flyweight. Combining them isn't the same as them being equivalent.

The statement "modern web browsers use this pattern like loading similar pages" is too vague. A more precise statement: browsers use a Flyweight-like strategy for rendering glyphs (characters) in text — the same glyph object for the letter 'A' can be shared across thousands of text nodes on a page; each use supplies its own position and styling (extrinsic state), while the glyph's shape data (intrinsic state) is shared.

Benefits

  • Dramatically reduces memory consumption when large numbers of similar objects are needed, by sharing the intrinsic (immutable, common) part.
  • Reduces the number of object instantiations and associated garbage collection overhead.

Limitations

  • Adds complexity: the code must explicitly separate intrinsic and extrinsic state, and callers must supply extrinsic state with each operation rather than just calling a method on a self-contained object.
  • The cache itself (the Flyweight pool) can hold objects in memory indefinitely, which can become a memory leak if the set of "types" grows unboundedly and old entries are never evicted.
  • Thread safety: if the Flyweight pool is accessed from multiple threads, the HashMap (as used above) is not thread-safe. Use ConcurrentHashMap or synchronize access appropriately in a multi-threaded environment.

Flyweight Pattern Examples in the JDK

  • String interning: String.intern() uses a pool of unique string literals — if the same string value already exists in the pool, the existing instance is returned rather than creating a new one. This is the JVM's built-in Flyweight for string literals.
  • Integer.valueOf(int), Boolean.valueOf(), Short.valueOf(), Byte.valueOf(), Character.valueOf(), Long.valueOf() — all wrapper class valueOf() methods cache commonly-used values (e.g., Integer caches -128 to 127 by default) and return existing instances rather than creating new ones for those values.