Applications often need a large amount of resources in order to run completely and without errors. However, different modules, forms, etc. of an application usually need different sets of resources to be loaded and not all of them. While all resources can be loaded at once, it would sometimes cost a lot of time to the user. A common solution to this problem is the Lazy loading – a practice of loading resources once we need them and not before that.

Lazy Loading Methods

There are different ways to implement lazy loading. They may vary in their logic but the goal is the same – not to load an object before it’s needed. Let’s take a look at the most common ideas:

  • Lazy initialization: Initially, the object to be lazy loaded is set to null. Whenever the object is needed, the code checks if that object is still null and if it is, the object data is loaded. This method is very common and can also be implemented with non-object oriented programming languages. An example of lazy initialization using JavaScript:
var Pencil = (function() {

  // There will be differently colored pencils but at the beginning they are 
  // not initialized
  var colors = {};
  function Pencil() {};

  var _static = {
    getPencil: function(color) {
      // Checking if the object is null (undefined in this case)
      if (typeof colors[color] == 'undefined') {
        colors[color] = new Pencil;
      }
      return colors[color];
    }
  };
  return _static;
})();
  • Virtual proxy: Initially, only a proxy object is created. It has the same interface as the original object. The original object is loaded once any of the proxy object’s methods is called. Let’s see a similar JS example of what a virtual proxy is:
var PencilList = function() {
    // Some code for creating the object
};
PencilList.prototype = {
    getPencil: function(...) {
        // Code goes here
    },
    findPencil: function(...) {
        // Code goes here
    },
    addPencil: function(...) {
        // Code goes here
    },
    ...
    // Other functions may go here
};

var PencilsProxy = function() {
    // The Pencil list is set to null initially
    this.pencilList = null;
};
PencilsProxy.prototype = {
    // The following function is called every time another function is 
    // called so the pencil list is properly initialized
    _init: function() {
        if (!this.pencilList) {
            this.pencilList = new PencilList();
        }
    },
    getPencil: function(...) {
        this._init();
        return this.pencilList.getPencil(...);
    },
    findPencil: function(...) {
        this._init();
        return this.pencilList.findPencil(...);
    },
    addPencil: function(...) {
        this._init();
        return this.pencilList.addPencil(...);
    },
    ...
    ...
    ...
}
  • Ghost: a very simple method where, at the beginning, the object is partially loaded containing only an identifier – one or more properties needed to distinguish the object among the others. Once one of its properties is called, the object is fully loaded. Example:
Public class Pencil
{
    private int id;
    private bool loaded = false;
    private string color;
    private double price;

    // Constructor
    public Pencil(int id)
    {
        this.id = id;
    }

    // Calling this method when all the data is needed
    private void Initialize()
    {
        if(!loaded)
        {
            var pencil = db.Pencils.SingleOrDefault(p => p.PencilId = id);
            color = pencil.Color;
            price = pencil.Price;
            loaded = true;
        }
    }

    public string Color
    {
        get
        {
            Initialize();
        }
        return color;
    }

    public int Price
    {
        get
        {
            Initialize();
        }
        return price;
    }
}
    
  • Value holder: a generic object is created that appears in the place of the lazy loaded object’s data fields. A Java example:
public class ValueHolder<T> { 
    private T myValue; 
    private readonly Func<object, T> valueAccess; 
  
    // Constructor 
    public ValueHolder(Func<object, T> valueAccess) 
    { 
        valueAccess = this.valueAccess; 
    } 
  
    // Call this method when the value is needed
    public T GetMyValue(object parameter) 
    { 
        if (myValue == null) 
            myValue = valueAccess(parameter); 
        return myValue; 
    } 
} 

Lazy Loading Disadvantages:

Being generally a great practice, lazy loading contains very few disadvantages. For example, errors in the lazy loading implementation may cause critical information to be lazy loaded while it’s needed before the lazy loading occurs.

Was this post helpful?