# Invoking functions directly

Inngest's `step.invoke()` function provides a powerful tool for calling functions directly within your event-driven system. It differs from traditional event-driven triggers, offering a more direct, RPC-like approach. This encourages a few key benefits:

- Allows functions to call and receive the result of other functions
- Naturally separates your system into reusable functions that can spread across process boundaries
- Allows use of synchronous interaction between functions in an otherwise-asynchronous event-driven architecture, making it much easier to manage functions that require immediate outcomes
- Enables [synchronous sub-agent delegation](/docs-markdown/ai-patterns/sub-agent-delegation#delegate-synchronously-with-stepinvoke) in AI agent systems

## Invoking another function

> **Callout:** When should I invoke?Use step.invoke() in tasks that need specific settings like concurrency limits. Because it runs with its own configuration,
> distinct from the invoker's, you can provide a tailored configuration for each function.If you don't need to define granular configuration or if your function won't be reused across app boundaries, use step.run() for simplicity.

```ts
// Some function we'll call
const computeSquare = inngest.createFunction(
  { id: "compute-square", triggers: { event: "calculate/square" } },
  async ({ event }) => {
    return { result: event.data.number * event.data.number }; // Result typed as { result: number }
  }
);

// In this function, we'll call `computeSquare`
const mainFunction = inngest.createFunction(
  { id: "main-function", triggers: { event: "main/event" } },
  async ({ step }) => {
    const square = await step.invoke("compute-square-value", {
      function: computeSquare,
      data: { number: 4 }, // input data is typed, requiring input if it's needed
    });

    return `Square of 4 is ${square.result}.`; // square.result is typed as number
  }
);
```

In the above example, our `mainFunction` calls `computeSquare` to retrieve the resulting value. `computeSquare` can now be called from here or any other process connected to Inngest.

## Referencing another Inngest function

If a function exists in another app, you can create a reference that can be invoked in the same manner as the local `computeSquare` function above.

```ts
// @/inngest/computeSquare.ts
import { referenceFunction } from "inngest";
import { z } from "zod";

// Create a reference to a function in another application.
export const computeSquare = referenceFunction({
  appId: "my-python-app",
  functionId: "compute-square",
  // Schemas are optional, but provide types for your call if specified
  schemas: {
    data: z.object({
      number: z.number(),
    }),
    return: z.object({
      result: z.number(),
    }),
  },
});
```

```ts
import { computeSquare } from "@/inngest/computeSquare";

// square.result is typed as a number
const square = await step.invoke("compute-square-value", {
  function: computeSquare,
  data: { number: 4 }, // input data is typed, requiring input if it's needed
});
```

References can also be used to invoke local functions without needing to import them (and their dependencies) directly. This can be useful for frameworks like Next.js where edge and serverless handlers can be mixed together and require different sets of dependencies.

```ts
import { inngest, referenceFunction } from "inngest";
import { type computeSquare } from "@/inngest/computeSquare"; // Import only the type

const mainFunction = inngest.createFunction(
  { id: "main-function", triggers: { event: "main/event" } },
  async ({ step }) => {
    const square = await step.invoke("compute-square-value", {
      function: referenceFunction<typeof computeSquare>({
        functionId: "compute-square",
      }),
      data: { number: 4 }, // input data is still typed
    });

    return `Square of 4 is ${square.result}.`; // square.result is typed as number
  }
);
```

For more information on referencing functions, see [TypeScript -> Referencing Functions](/docs-markdown/functions/references).

> **Callout:** When should I invoke?Use step.Invoke() in tasks that need specific settings like concurrency limits. Because it runs with its own configuration,
> distinct from the invoker's, you can provide a tailored configuration for each function.If you don't need to define granular configuration or if your function won't be reused across app boundaries, use step.Run() for simplicity.

```go
import (
	"context"
	"fmt"

	"github.com/inngest/inngestgo"
	"github.com/inngest/inngestgo/step"
)

// Some function we'll call
inngestgo.CreateFunction(
	client,
	inngestgo.FunctionOpts{Name: "compute-square"},
	inngestgo.EventTrigger("calculate/square", nil),
	func(ctx context.Context, input inngestgo.Input[map[string]any]) (any, error) {
		number, ok := input.Event.Data["number"].(float64)
		if !ok {
			return nil, fmt.Errorf("invalid number")
		}

		return map[string]any{
			"result": int(number * number),
		}, nil
	},
)

// In this function, we'll call the compute-square function
inngestgo.CreateFunction(
	client,
	inngestgo.FunctionOpts{Name: "main-function"},
	inngestgo.EventTrigger("main/event", nil),
	func(ctx context.Context, input inngestgo.Input[map[string]any]) (any, error) {
		square, err := step.Invoke(ctx, "compute-square-value", &inngestgo.InvokeOpts{
			Function: "compute-square",
			Data: map[string]any{
				"number": 4,
			},
		})
		if err != nil {
			return nil, err
		}

		result := square.Data["result"].(int)
		return fmt.Sprintf("Square of 4 is %d.", result), nil
	},
)

```

In the above example, our `mainFunction` calls `computeSquare` to retrieve the resulting value. `computeSquare` can now be called from here or any other process connected to Inngest.

> **Callout:** When should I invoke?Use step.invoke() in tasks that need specific settings like concurrency limits. Because it runs with its own configuration,
> distinct from the invoker's, you can provide a tailored configuration for each function.If you don't need to define granular configuration or if your function won't be reused across app boundaries, use step.run() for simplicity.

```py
import inngest
from src.inngest.client import inngest_client

# Some function we'll call
@inngest_client.create_function(
    fn_id="compute-square",
    trigger=inngest.TriggerEvent(event="calculate/square")
)
async def compute_square(ctx: inngest.Context):
    return {"result": ctx.event.data["number"] * ctx.event.data["number"]}  # Result typed as { result: number }

# In this function, we'll call compute_square
@inngest_client.create_function(
    fn_id="main-function",
    trigger=inngest.TriggerEvent(event="main/event")
)
async def main_function(ctx: inngest.Context):
    square = await ctx.step.invoke(
        "compute-square-value",
        function=compute_square,
        data={"number": 4}  # input data is typed, requiring input if it's needed
    )

    return f"Square of 4 is {square['result']}."  # square.result is typed as number
```

In the above example, our `mainFunction` calls `compute_square` to retrieve the resulting value. `compute_square` can now be called from here or any other process connected to Inngest.

## Creating a distributed system

You can invoke Inngest functions written in any language, hosted on different clouds.  For example, a TypeScript function on Vercel can invoke a Python function hosted in AWS.

By starting to define these blocks of functionality, you're creating a smart, distributed system with all of the benefits of event-driven architecture and without any of the hassle.

## Similar pattern: Fan-Out

A similar pattern to invoking functions directly is that of fan-out - [check out the guide here](/docs-markdown/guides/fan-out-jobs). Here are some key differences:

- Fan-out will trigger multiple functions simultaneously, whereas invocation will only trigger one
- Unlike invocation, fan-out will not receive the result of the invoked function
- Choose fan-out for parallel processing of independent tasks and invocation for coordinated, interdependent functions