This is not pointless when you’re using it as a constructor (there are many other optimal ways to use __index
, but considering all of the examples above, I will dissect precisely what is happening in their typical examples, then showcase other examples where the __index
metamethod will be used.
Consider this script for instance:
local SomeModule = {}
SomeModule.__index = SomeModule
function SomeModule.new()
local self = {}
self.ThingToPrint = ""
return setmetatable(self, SomeModule)
end
function SomeModule:SetThingToPrint(NewThingToPrint)
self.ThingToPrint = NewThingToPrint
end
function SomeModule:GetThingToPrint()
return self.ThingToPrint
end
function SomeModule:PrintThing()
print(self:GetThingToPrint())
end
local PrinterOne = SomeModule.new()
local PrinterTwo = SomeModule.new()
local PrinterThree = SomeModule.new()
PrinterOne:SetThingToPrint("Hello!")
PrinterTwo:SetThingToPrint("Goodbye!")
PrinterThree:SetThingToPrint("Some greeting!")
PrinterOne:PrintThing()
PrinterTwo:PrintThing()
PrinterThree:PrintThing()
If you run this code, this will print the following:

Behind the scenes, this is what is happening:
I will use PrinterOne
as an example, though PrinterTwo
and PrinterThree
will undergo the same exact processes.
Let’s start with
local PrinterOne = SomeModule.new()
SomeModule
is a reference to the actual SomeModule
table, which would look something like this:
(I have cleared out the functions in this case to make it more clear to read):
local SomeModule = {
["new"] = (function() end)
["__index"] = {
["new"] = (function() end)
["__index"] = . . . (SomeModule)
["SetThingToPrint"] = (function() end)
["GetThingToPrint"] = (function() end)
["PrintThing"] = (function() end)
}
["SetThingToPrint"] = (function() end)
["GetThingToPrint"] = (function() end)
["PrintThing"] = (function() end)
}
Notice that __index
references itself, therefore the table inside of __index
will always be itself continously forever, hence why it is represented with . . .
Okay. So we’ve called SomeModule.new()
and set its return value to equal PrinterOne
.
As we call SomeModule.new()
, internally, the execution instruction set goes through line by line of SomeModule.new()
It first creates the self
table, which is empty and not populated at the time.

Then, it populates ThingToPrint
inside of self
as an empty string value.

Lastly, it returns the metatable of self
to SomeModule
. In order to do so, the instruction set needs to index SomeModule. It first checks whether the __index
metamethod is valid. Because our __index
metamethod is, in fact, valid, it points to SomeModule["__index"]
instead of directly SomeModule
.
This will allow us to get copy the self
value that was created in the module over to PrinterOne
, and us being able to independently change self
for every time we call SomeModule.new()
without having the values associated in self
being overwritten.
It would be bad if we wanted to do this without an __index
metamethod:
local PrinterOne = SomeModule.new()
local PrinterTwo = SomeModule.new()
as later in our code, we won’t be able to seperate our changes from PrinterOne
or PrinterTwo
.
Consider this example without __index
, for instance:
local SomeModule = {}
function SomeModule.new()
local self = {}
self.ThingToPrint = ""
return self
end
function SomeModule:SetThingToPrint(NewThingToPrint)
self.ThingToPrint = NewThingToPrint
end
function SomeModule:GetThingToPrint()
return self.ThingToPrint
end
function SomeModule:PrintThing()
print(self:GetThingToPrint())
end
local PrinterOne = SomeModule.new()
local PrinterTwo = SomeModule.new()
local PrinterThree = SomeModule.new()
PrinterOne:SetThingToPrint("Hello!")
PrinterTwo:SetThingToPrint("Goodbye!")
PrinterThree:SetThingToPrint("Some greeting!")
PrinterOne:PrintThing()
PrinterTwo:PrintThing()
PrinterThree:PrintThing()
The defintions for PrinterOne, PrinterTwo, and PrinterThree would run fine but the moment it hits the methods, it will error.
This is because we no longer have access to the methods of SomeModule
, only whatever is inside of self
, which is ThingToPrint
.
With or without __index
, you will always end up having this:
local PrinterOne = {
["ThingToPrint"] = ""
}
(Remember the instructions that are being made are roughly this):
local PrinterOne
PrinterOne = SomeModule.new() = self = {
["ThingToPrint"]
}
__index
allows PrinterOne
to access and call methods from SomeModule
without filling its table. You can now probably see why its problematic if you leave out the __index
.
After called SomeModule.new()
and setting it to the value of PrinterOne
, we then call PrinterOne:SetThingToPrint("Hello!")
By this point, we have already defined PrinterOne, PrinterTwo, and PrinterThree, and SomeModule as follows: (note that the memory address of SomeModule
is exactly the same as all __index
's present inside of SomeModule
, as it is a reference of itself.)

Continuing on, "Hello!"
gets passed through as NewThingToPrint = "Hello!"
and self.ThingToPrint = NewThingToPrint
(Note that the memory address of self
does not point to SomeModule
, but points to PrinterOne
.)

This calls the function PrintThing
from SomeModule
. PrinterOne
is a table, similar to SomeModule
, However, PrinterOne
does not actually contain any functions inside of it. PrinterOne
is a table consist of only ThingToPrint
inside of it.
Then it continues doing the same operations for the other method calls:



Then finally, the PrintThing()
method is called (which just prints self.ThingToPrint
)



Let me know if you have any questions on this, I’ll be happy to break it down even further.