Invoking JavaScript Functions Without Parentheses: Tagged Templates, new, and What Actually Calls a Function

Most JavaScript developers learn early that calling a function means writing its name followed by parentheses: greet(), fetch(url), console.log("hi"). So when you see code like tag`Hello ${name}` or new Date and no parentheses anywhere near the function, it can look like a trick or even a bug. It is neither. There are real, well-defined mechanisms in the language that execute functions without a parenthesized argument list, and understanding them makes you a better reader of modern JavaScript — especially template-heavy code, DSLs, and library APIs. This guide walks through the main ways a function runs without (), clarifies the common confusion between referencing a function and invoking it, and shows where these patterns are genuinely useful and where they hurt readability.

Referencing a Function Is Not Calling It

Before looking at the exceptions, it is worth locking down the rule they deviate from. A bare function identifier evaluates to a function reference — a value you can pass around, store, and compare — but it does not execute anything.

function greet(name) {
  return `Hello, ${name}!`;
}

const ref = greet;      // no execution happens here
console.log(ref);       // logs the function object itself
console.log(ref("Ada")); // only now does the function run

This distinction is the source of a classic bug: forgetting the parentheses when you meant to call something. setTimeout(greet, 1000) schedules greet to run later; setTimeout(greet(), 1000) runs it immediately and schedules its return value instead. Neither line uses magic — one passes a reference, the other passes a result.

The same distinction explains why aliasing works. If you write const logMessage = console.log, you have copied a reference. Calling logMessage("hello") afterwards absolutely does invoke the original function, because the alias points at the same callable object. Note one caveat: methods like console.log may lose their expected this context when detached this way, though for console.log in practice this rarely causes problems.

A related misconception shows up in callback registration. Consider a slightly mangled arrow function like number => number * 2(). Creating this arrow function does not execute its body. However, map invokes its callback synchronously while processing the array. When the callback runs, number * 2() attempts to call the numeric value 2 as a function, which throws a TypeError. By contrast, registering an event listener normally defers execution until an event occurs. Passing a callback does not by itself tell you whether the receiving API invokes it immediately or later.

Tagged Template Literals: The Main Parenthesis-Free Call

The most important and intentional way to invoke a function without parentheses is a tagged template literal. When you place a function name directly before a template literal, JavaScript calls that function with a very specific set of arguments: an array of the literal string pieces, followed by the interpolated values.

function highlight(strings, ...values) {
  return strings.reduce((result, str, i) => {
    const value = i < values.length ? `[${values[i]}]` : "";
    return result + str + value;
  }, "");
}

const name = "Ada";
const role = "admin";

const message = highlight`User ${name} has role ${role}`;
// strings  => ["User ", " has role ", ""]
// values   => ["Ada", "admin"]
// message  => "User [Ada] has role [admin]"

When the tagged template is evaluated, the tag function executes. The first argument is a frozen array of the processed (or "cooked") string chunks between interpolations, and the remaining arguments are the evaluated interpolation values. The MDN documentation on template literals describes this tag mechanism in detail, including the raw property on the strings array that exposes the unprocessed source text (so \n stays as backslash-n rather than a newline).

You have almost certainly used a tag without realizing it. styled-components in the React ecosystem uses tagged templates for its familiar CSS syntax:

import styled from "styled-components";

const Button = styled.button`
  background: rebeccapurple;
  color: white;
  padding: 0.5rem 1rem;
`;

Here styled.button is a function (or callable object), and the template literal after it is the tag argument. The library receives the CSS text and interpolations and returns a React component. GraphQL clients such as Apollo use the same trick with the gql tag, and i18n libraries often use tags for translated strings. The pattern is popular because it lets a library intercept both the static structure and the dynamic values of a string at once — something a normal function call with a single concatenated string cannot do.

new Constructor Without an Argument List

The second parenthesis-free invocation is the argument-less new form. When the constructor call has no arguments, JavaScript allows you to omit the parentheses entirely:

const now = new Date;      // equivalent to new Date()
const set = new Set;       // equivalent to new Set()
const map = new Map;       // equivalent to new Map()

Per the MDN documentation on the new operator, both forms construct a new instance with no arguments passed to the constructor. It is worth being precise about what is happening here: construction is a distinct operation from an ordinary function call. new creates a fresh object, sets its prototype chain from the constructor's prototype property, runs the constructor body with this bound to the new object, and returns that object (unless the constructor explicitly returns an object). An ordinary call does none of that.

Two practical notes. First, this shorthand only works when you genuinely pass no arguments — new Date(2024, 0, 1) obviously needs its parentheses. Second, arrow functions cannot be constructors. They lack the internal machinery new requires, so new MyArrow() throws a TypeError regardless of whether you include parentheses:

const MakeThing = () => ({ ready: true });
// new MakeThing();  // TypeError: MakeThing is not a constructor

Style-wise, most teams prefer keeping the parentheses even for zero-argument construction, because new Date reads ambiguously to developers who do not know the shorthand. Linters and style guides frequently mandate new Date() for consistency.

Getters, Proxies, and Other Indirect Executions

Beyond the two canonical cases, there are situations where code you did not write with () ends up running a function. Accessor properties (getters) execute their backing function whenever the property is read:

const config = {
  get env() {
    return process.env.NODE_ENV ?? "development";
  }
};

console.log(config.env); // getter function runs, no parentheses in sight

Similarly, a Proxy with a get trap can run arbitrary logic when a property is accessed, and getters on classes work the same way as object getters. These are not "invocation without parentheses" in the language-grammar sense — the call happens internally when the property lookup occurs — but they produce the same observable effect: reading a property triggers code execution. This is powerful for building clean APIs, but it is also why side effects hidden in getters are a common source of surprising bugs. If reading a property performs I/O, mutates state, or throws conditionally, most reviewers will not expect it.

Readability, Trade-offs, and When to Use Each Pattern

Each parenthesis-free mechanism carries a different readability cost, and choosing deliberately matters more than memorizing syntax.

Tagged templates are the most defensible choice in production code, but only when the tag genuinely benefits from separating static text from interpolated values. Formatting, SQL/GraphQL query construction, CSS-in-JS, and localization are ideal. Using a tag purely to avoid typing parentheses — for example, writing String.raw-style tags where a normal call would do — confuses readers who expect template tags to do something special with the string structure. Also remember the argument shape: the first parameter is always the strings array, so a tag function is not drop-in compatible with a function expecting a normal first argument.

Argument-less new is safe and correct but stylistically divisive. It saves two characters and gains nothing else. If your team's style guide or linter (many ESLint configurations, via rules like new-parens, enforce this) requires parentheses, follow it. Consistency beats brevity in shared codebases.

Getters with side effects deserve the most caution. A getter that computes a value from existing state is fine; a getter that fetches data, writes logs, or changes other state violates the expectations of everyone reading the call site. When in doubt, use an explicit method so the parentheses signal "something happens here."

One anti-pattern to avoid entirely: any recipe that uses these mechanisms to smuggle code past filters or sanitizers — for instance, treating tagged templates as a way to build strings for later evaluation. The language provides no security benefit here, and constructing executable strings for eval-style consumption is a well-known injection risk regardless of how the string was assembled.

Frequently Asked Questions

Does writing a function’s name without parentheses call it?

No. A bare function name evaluates to a reference to the function object. Nothing executes until that reference is called — with `()`, with `new`, or as a tagged template. This is why `setTimeout(myFunc, 1000)` schedules the function while `setTimeout(myFunc(), 1000)` runs it immediately.

How does a tagged template literal pass arguments to its tag function?

The tag receives an array of the literal string pieces as its first argument, followed by one argument per interpolated expression. For “ tag`a${x}b${y}c` “, the argument values are conceptually similar to `tag([“a”, “b”, “c”], x, y)`, but an actual tag receives a special frozen strings array whose identity is reused for the same template site. The strings array also has a `raw` property containing the unescaped source text.

Is `new Date` exactly the same as `new Date()`?

Yes, when no arguments are involved. JavaScript permits omitting the parentheses in a `new` expression with an empty argument list, and both forms construct an instance by calling the constructor with no arguments. With any arguments, parentheses are required.

Can arrow functions be used with `new`?

No. Arrow functions do not have the internal methods required for construction, so `new arrowFn()` throws a `TypeError`. Use a regular function declaration, function expression, or class if you need a constructor.

If I alias a function like `const log = console.log`, does calling the alias run the original?

Yes. The alias holds a reference to the same function object, so `log(“hello”)` invokes `console.log`. The general caveat is that detaching a method from its object can change its `this` binding, which matters for methods that rely on it.

Conclusion

Parentheses are the most common signal for "this function runs now," but they are not the only one. Tagged template literals invoke a tag function with a structured mix of string pieces and values, argument-less new performs construction without an argument list, and getters execute implicitly on property access. Just as important is the negative case: a bare function reference does nothing until something calls it, and forgetting that distinction is behind many scheduling and callback bugs. The practical takeaway is to use tagged templates where separating static text from interpolation genuinely helps, keep new parentheses for consistency unless your style guide says otherwise, and reserve side-effecting getters for cases where implicit execution is the whole point. A good next step is to open a codebase that uses styled-components or gql and trace exactly what arguments the tag function receives — it makes the mechanism concrete in minutes.


Transparency Note: Links in this article are not affiliate links. I recommend these tools and platforms based on their relevance to the topic, and no compensation is received for including them.

Privacy Overview

This website uses cookies so that we can provide you with the best user experience possible. Cookie information is stored in your browser and performs functions such as recognising you when you return to our website and helping our team to understand which sections of the website you find most interesting and useful.

Necessary

Strictly Necessary Cookie should be enabled at all times so that we can save your preferences for cookie settings.

Analytics

This website uses Google Analytics to collect anonymous information such as the number of visitors to the site, and the most popular pages.

Keeping this cookie enabled helps us to improve our website.