Skip to main content
The @overload annotation allows you to define multiple type signatures for the same function, enabling type-safe handling of different parameter combinations.

Syntax

---@overload fun(<parameters>): <return_types>
Multiple overload annotations can be stacked before the main function definition.

Why Use Overloads?

Many Lua functions accept flexible parameter combinations. Overloads help the IDE understand all valid ways to call a function:
function add(x, y, z)
    return x + (y or 0) + (z or 0)
end

-- All calls work but IDE can't validate:
add(5)           -- ❌ IDE shows warning
add(5, 10)       -- ❌ IDE shows warning  
add(5, 10, 15)   -- ✅ Only this matches signature

Basic Overloads

1
Variable Argument Count
2
Define different signatures based on the number of arguments:
3
---@overload fun(x: number): number
---@overload fun(x: number, y: number): number
---@overload fun(x: number, y: number, z: number): number
---@param x number
---@param y? number
---@param z? number
---@return number
function add(x, y, z)
    return x + (y or 0) + (z or 0)
end

-- Each call uses appropriate overload
local result1 = add(5)          -- Uses: fun(x: number): number
local result2 = add(5, 10)      -- Uses: fun(x: number, y: number): number
local result3 = add(5, 10, 15)  -- Uses: fun(x: number, y: number, z: number): number
4
Type-Specific Overloads
5
Handle different input types:
6
---@overload fun(value: string): string
---@overload fun(value: number): string
---@overload fun(value: boolean): string
---@param value any
---@return string
function toString(value)
    if type(value) == "string" then
        return value
    elseif type(value) == "number" then
        return tostring(value)
    elseif type(value) == "boolean" then
        return value and "true" or "false"
    else
        return "unknown"
    end
end

-- IDE validates based on input type
local str1 = toString(42)       -- Uses: fun(value: number): string
local str2 = toString("hello")  -- Uses: fun(value: string): string
local str3 = toString(true)     -- Uses: fun(value: boolean): string

Constructor Overloads

Provide flexible object creation:
---@class Vector
---@field x number
---@field y number
---@field z number
local Vector = {}

---@overload fun(): Vector
---@overload fun(x: number): Vector
---@overload fun(x: number, y: number): Vector
---@overload fun(x: number, y: number, z: number): Vector
---@param x? number
---@param y? number
---@param z? number
---@return Vector
function Vector.new(x, y, z)
    return setmetatable({
        x = x or 0,
        y = y or 0,
        z = z or 0
    }, {__index = Vector})
end

-- All constructor patterns supported
local vec1 = Vector.new()           -- Uses: fun(): Vector
local vec2 = Vector.new(1)          -- Uses: fun(x: number): Vector
local vec3 = Vector.new(1, 2)       -- Uses: fun(x: number, y: number): Vector
local vec4 = Vector.new(1, 2, 3)    -- Uses: fun(x: number, y: number, z: number): Vector

Parameter Type Disambiguation

Handle ambiguous parameter types:
---@overload fun(endpoint: string): table
---@overload fun(endpoint: string, options: {method?: string, headers?: table}): table
---@overload fun(endpoint: string, data: table): table
---@overload fun(endpoint: string, data: table, options: {method?: string, headers?: table}): table
---@param endpoint string
---@param dataOrOptions? table
---@param options? table
---@return table
function apiRequest(endpoint, dataOrOptions, options)
    local finalOptions = {}
    local data = nil
    
    if dataOrOptions then
        if options then
            -- apiRequest(endpoint, data, options)
            data = dataOrOptions
            finalOptions = options
        elseif dataOrOptions.method or dataOrOptions.headers then
            -- apiRequest(endpoint, options)
            finalOptions = dataOrOptions
        else
            -- apiRequest(endpoint, data)
            data = dataOrOptions
        end
    end
    
    return performRequest(endpoint, data, finalOptions)
end

-- IDE validates each usage pattern
local response1 = apiRequest("/users")
local response2 = apiRequest("/users", {method = "POST"})
local response3 = apiRequest("/users", {name = "John"})
local response4 = apiRequest("/users", {name = "John"}, {method = "POST"})

Generic Overloads

Combine overloads with generics:
---@overload fun<T>(items: T[]): T[]
---@overload fun<T>(items: T[], predicate: fun(item: T): boolean): T[]
---@generic T
---@param items T[]
---@param predicate? fun(item: T): boolean
---@return T[]
function filter(items, predicate)
    if not predicate then
        -- Return copy of original array
        local copy = {}
        for i, v in ipairs(items) do
            copy[i] = v
        end
        return copy
    end
    
    local result = {}
    for _, item in ipairs(items) do
        if predicate(item) then
            table.insert(result, item)
        end
    end
    return result
end

-- Type information preserved through generic overloads
local numbers = {1, 2, 3, 4, 5}
local copy = filter(numbers)  -- Uses: fun<T>(items: T[]): T[], returns number[]

local evens = filter(numbers, function(n)
    return n % 2 == 0
end)  -- Uses: fun<T>(items: T[], predicate: fun(item: T): boolean): T[]

Before/After Comparison

Without @overload

function create(x, y, z)
    return {
        x = x or 0,
        y = y or 0,
        z = z or 0
    }
end

-- IDE issues warnings:
create()         -- ⚠️ Missing arguments
create(1)        -- ⚠️ Missing arguments
create(1, 2)     -- ⚠️ Missing arguments
create(1, 2, 3)  -- ✅ Only this is valid
Problems:
  • IDE shows incorrect warnings
  • Valid calls appear as errors
  • No signature assistance
  • Poor developer experience

With @overload

---@overload fun(): table
---@overload fun(x: number): table
---@overload fun(x: number, y: number): table
function create(x, y, z)
    return {
        x = x or 0,
        y = y or 0,
        z = z or 0
    }
end

-- All patterns validated:
create()         -- ✅ Valid
create(1)        -- ✅ Valid
create(1, 2)     -- ✅ Valid
create(1, 2, 3)  -- ✅ Valid
Benefits:
  • No false warnings
  • All valid calls recognized
  • Complete signature hints
  • Better developer experience

Real-World Example

---@class HttpClient
local HttpClient = {}

---@class RequestOptions
---@field method? string HTTP method (default: "GET")
---@field headers? table<string, string> Request headers
---@field timeout? number Request timeout in seconds
---@field body? string Request body

---@class Response
---@field status number HTTP status code
---@field headers table<string, string> Response headers
---@field body string Response body

---Simple GET request
---@overload fun(url: string): Response

---GET request with options
---@overload fun(url: string, options: RequestOptions): Response

---POST/PUT request with body
---@overload fun(url: string, body: string): Response

---Full request with method, body, and options
---@overload fun(url: string, method: string, body: string, options: RequestOptions): Response

---@param url string Request URL
---@param methodOrBodyOrOptions? string | RequestOptions
---@param bodyOrOptions? string | RequestOptions
---@param options? RequestOptions
---@return Response
function HttpClient.request(url, methodOrBodyOrOptions, bodyOrOptions, options)
    local method = "GET"
    local body = nil
    local opts = {}
    
    -- Parse parameters based on types
    if type(methodOrBodyOrOptions) == "string" then
        if bodyOrOptions then
            -- request(url, method, body, options)
            method = methodOrBodyOrOptions
            body = bodyOrOptions
            opts = options or {}
        else
            -- request(url, body)
            method = "POST"
            body = methodOrBodyOrOptions
        end
    elseif type(methodOrBodyOrOptions) == "table" then
        -- request(url, options)
        opts = methodOrBodyOrOptions
        method = opts.method or "GET"
        body = opts.body
    end
    
    -- Perform request
    return performHttpRequest(url, method, body, opts)
end

-- All usage patterns work with full type checking:

-- Simple GET
local response1 = HttpClient.request("https://api.example.com/users")

-- GET with options
local response2 = HttpClient.request("https://api.example.com/users", {
    headers = {["Authorization"] = "Bearer token"},
    timeout = 30
})

-- POST with body
local response3 = HttpClient.request(
    "https://api.example.com/users",
    '{"name": "John"}'
)

-- Full request
local response4 = HttpClient.request(
    "https://api.example.com/users",
    "PUT",
    '{"name": "Jane"}',
    {
        headers = {["Content-Type"] = "application/json"},
        timeout = 60
    }
)

-- IDE knows response1-4 are all Response type
print(response1.status)  -- Autocomplete works
print(response1.body)    -- Type checking enabled

Best Practices

List more specific signatures first, followed by more general ones.
Use @param and @return on the actual function, not just in overloads.
Don’t overuse overloads for minor variations—optional parameters might be clearer.
Too many overloads can be confusing. Consider refactoring if you have more than 5-6.
Ensure your implementation actually handles all the declared overload signatures.

Common Patterns

Optional Middle Parameters

---@overload fun(a: number, c: number): number
---@param a number
---@param b? number
---@param c? number
---@return number
function calculate(a, b, c)
    if not b and c then
        -- Skipped middle parameter
        b = 0
    end
    return a + (b or 0) + (c or 0)
end

Config Object vs Individual Parameters

---@overload fun(config: {host: string, port: number, timeout?: number}): Connection
---@overload fun(host: string, port: number, timeout?: number): Connection
function connect(hostOrConfig, port, timeout)
    local config
    if type(hostOrConfig) == "table" then
        config = hostOrConfig
    else
        config = {host = hostOrConfig, port = port, timeout = timeout}
    end
    return createConnection(config)
end

See Also

  • @param - Document function parameters
  • @return - Document return values
  • @generic - Create generic functions

Build docs developers (and LLMs) love