This post explains a quiz originally shared as a LinkedIn poll.
πΉ The Question
const api = {
baseURL: "https://api.example.com",
fetchUser: (id) => {
return `${this.baseURL}/users/${id}`;
},
fetchPost(id) {
return `${this.baseURL}/posts/${id}`;
}
};
console.log(api.fetchUser(1));
console.log(api.fetchPost(2));
Hint: Arrow functions don't create their own this β they inherit it from the enclosing lexical scope. What is this at the top level of a script?
πΉ Solution
Correct Answer: B) undefined/users/1 / https://api.example.com/posts/2
π§ How this works
The core mechanism here is lexical this binding in arrow functions versus dynamic this binding in regular methods.
When you define a method using the arrow function syntax (fetchUser: (id) => { ... }), the arrow function does not get its own this. Instead, it captures this from the enclosing lexical scope β which in this case is the top-level script scope.
At the top level of a non-module script:
- In a browser:
thisis thewindowobject - In Node.js REPL:
thisis theglobalobject
In both cases, this.baseURL is undefined because there is no baseURL property on the global object.
When you define a method using shorthand syntax (fetchPost(id) { ... }), it's a regular function. When called as api.fetchPost(2), this is dynamically bound to api β the object before the dot. So this.baseURL correctly resolves to "https://api.example.com".
Key insight: The object literal { ... } is not a scope. Arrow functions defined inside an object literal do not capture this as the object β they capture this from whatever scope the object literal is being evaluated in.
π Line-by-line explanation
const api = { ... }β An object literal is created. This is an expression, not a scope boundary. Thethisvalue inside is inherited from the surrounding context.baseURL: "https://api.example.com"β A simple string property on the object.fetchUser: (id) => { ... }β An arrow function is assigned to thefetchUserproperty. At this point, the arrow function permanently capturesthisfrom the enclosing scope (the top-level script).thishere is the global object (orundefinedin strict mode / ES modules).fetchPost(id) { ... }β A regular method defined using shorthand syntax. It will receivethisdynamically based on how it's called.api.fetchUser(1)β The arrow function runs.thisis the global object (captured at definition time).this.baseURLisundefined. The template literal produces"undefined/users/1".api.fetchPost(2)β The regular method runs.thisisapi(becauseapi.is beforefetchPost).this.baseURLis"https://api.example.com". The template literal produces"https://api.example.com/posts/2".
The misleading part: It looks like both methods are defined "inside" the api object, so you'd expect both to have this === api. But object literals don't create a this context β only functions, classes, and the global scope do.
πΉ Key Takeaways
Arrow functions capture
thislexically β from the enclosing function or script scope, not from the object they're defined in. Object literals are not scopes.Use shorthand method syntax for object methods (
method() { }) when you needthisto reference the object. Arrow functions are the wrong tool for this job.Arrow functions shine in callbacks β inside a regular method, arrow functions correctly inherit
thisfrom that method, making them ideal for.map(),.filter(),setTimeout, and event handlers inside methods.This bug is silent β
undefinedcoerces to a string without throwing, so broken arrow-function methods often produce subtly wrong output rather than crashing, making them hard to detect without tests.ESLint can catch this β The
no-invalid-thisrule and theclass-methods-use-thisrule can help flag arrow functions that referencethisin unexpected scopes.
Follow me for JavaScript puzzles and weekly curations of developer talks & insights at Talk::Overflow: https://talkoverflow.substack.com/
Top comments (1)
The result is
undefined/users/1andhttps://api.example.com/posts/2.fetchUseris an arrow function, so it doesnβt have its ownthis. It inheritsthisfrom the outer (global) scope, wherebaseURLdoesnβt exist, so it becomesundefined.fetchPostis a regular method, so when itβs called asapi.fetchPost(2),thiscorrectly points to theapiobject and the URL is built properly.Greetings :)