2619. Array Prototype Last
Problem Description
This problem asks you to extend the functionality of all JavaScript arrays by adding a custom method called last()
.
The requirements are:
- The
last()
method should be available on any array instance - When called, it should return the last element of the array
- If the array is empty (has no elements), it should return
-1
- The arrays you're working with can be assumed to be valid JavaScript arrays (as if they were produced by
JSON.parse
)
For example:
- If you have an array
[1, 2, 3]
and callarr.last()
, it should return3
- If you have an empty array
[]
and callarr.last()
, it should return-1
The solution involves:
- Extending the global
Array
interface in TypeScript to include the newlast()
method signature - Adding the actual implementation to
Array.prototype
so all array instances inherit this method - Using the built-in
at(-1)
method to access the last element (negative indices count from the end) - Checking the array length to determine whether to return the last element or
-1
The implementation this.length ? this.at(-1) : -1
uses a ternary operator to check if the array has any elements. If this.length
is truthy (greater than 0), it returns this.at(-1)
which gets the last element. Otherwise, it returns -1
.
Intuition
The key insight here is that we need to modify JavaScript's built-in Array
type to add custom behavior. In JavaScript, all arrays inherit from Array.prototype
, which means if we add a method to Array.prototype
, every array instance will automatically have access to that method.
To get the last element of an array, we have a few options to consider:
- We could use
this[this.length - 1]
, which directly accesses the element at the last index - We could use the newer
at()
method with a negative index:this.at(-1)
The at()
method is particularly elegant here because it handles negative indices naturally - when you pass -1
, it automatically counts from the end of the array and returns the last element. This avoids the need to manually calculate length - 1
.
For the edge case of an empty array, we need to check if the array has any elements before trying to access the last one. The simplest check is this.length
- if it's 0
(falsy), we know the array is empty and should return -1
. If it's any positive number (truthy), we can safely access the last element.
The TypeScript type declaration interface Array<T> { last(): T | -1; }
is necessary to tell TypeScript that arrays now have this new method, and that it returns either an element of type T
(the array's element type) or the literal value -1
. This ensures type safety when using the method in TypeScript code.
Solution Approach
The implementation consists of two main parts: the TypeScript type declaration and the actual prototype extension.
Step 1: Type Declaration
declare global {
interface Array<T> {
last(): T | -1;
}
}
This declares globally that the Array
interface now includes a last()
method. The generic type T
represents the type of elements in the array, and the return type T | -1
indicates the method returns either an element of that type or the literal value -1
.
Step 2: Prototype Extension
Array.prototype.last = function () {
return this.length ? this.at(-1) : -1;
};
This adds the actual implementation to Array.prototype
. Breaking down the logic:
-
this.length
checks if the array has any elements- If the length is 0 (empty array), this evaluates to
false
- If the length is greater than 0, this evaluates to
true
- If the length is 0 (empty array), this evaluates to
-
Using the ternary operator
condition ? valueIfTrue : valueIfFalse
:- When the array is not empty:
this.at(-1)
is executed - When the array is empty:
-1
is returned
- When the array is not empty:
-
The
at(-1)
method is a built-in array method that:- Accepts negative indices to count from the end
-1
refers to the last element-2
would refer to the second-to-last element, and so on
Step 3: Export Statement
export {};
This empty export statement makes the file a module in TypeScript, which is necessary for the global type augmentation to work properly.
The beauty of this solution is its simplicity - it leverages JavaScript's prototype chain and the modern at()
method to create a clean, readable implementation that handles both normal cases and edge cases in a single line of logic.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through how the last()
method works with different array scenarios:
Example 1: Non-empty array with numbers
const nums = [5, 10, 15, 20]; const result = nums.last(); // Step-by-step execution: // 1. this.length = 4 (truthy) // 2. Since length is truthy, execute this.at(-1) // 3. at(-1) counts from the end: index -1 refers to the last element // 4. Returns 20
Example 2: Empty array
const empty = []; const result = empty.last(); // Step-by-step execution: // 1. this.length = 0 (falsy) // 2. Since length is falsy, skip this.at(-1) // 3. Return -1 directly // 4. Returns -1
Example 3: Single element array
const single = ["hello"]; const result = single.last(); // Step-by-step execution: // 1. this.length = 1 (truthy) // 2. Since length is truthy, execute this.at(-1) // 3. at(-1) on a single-element array returns that element // 4. Returns "hello"
Example 4: Mixed type array
const mixed = [true, 42, "test", null, undefined]; const result = mixed.last(); // Step-by-step execution: // 1. this.length = 5 (truthy) // 2. Since length is truthy, execute this.at(-1) // 3. at(-1) returns the element at index 4 (5th element) // 4. Returns undefined
The key insight is how the ternary operator this.length ? this.at(-1) : -1
elegantly handles both cases: when we have elements (use at(-1)
to get the last one) and when we don't (return -1
as specified).
Solution Implementation
1# Extend the list class to add a last() method
2class ExtendedList(list):
3 """
4 Extended list class that includes a last() method
5 """
6
7 def last(self):
8 """
9 Returns the last element of the list
10 :return: The last element, or -1 if the list is empty
11 """
12 # Check if list has elements
13 # If yes, return the last element using index -1
14 # If no, return -1
15 return self[-1] if len(self) > 0 else -1
16
17
18# Alternative approach: Monkey-patching the built-in list class
19# Note: This modifies the global list type
20def _last(self):
21 """
22 Returns the last element of the list
23 :return: The last element, or -1 if the list is empty
24 """
25 # Check if list has elements
26 # If yes, return the last element using index -1
27 # If no, return -1
28 return self[-1] if len(self) > 0 else -1
29
30# Add the method to the list class
31list.last = _last
32
33
34# Example usage:
35# Using ExtendedList
36# arr = ExtendedList([1, 2, 3])
37# print(arr.last()) # Returns 3
38
39# Using monkey-patched list
40# arr = [1, 2, 3]
41# print(arr.last()) # Returns 3
42
43# Empty list example
44# empty_arr = []
45# print(empty_arr.last()) # Returns -1
46
1import java.util.ArrayList;
2import java.util.List;
3
4/**
5 * Custom ArrayList class that extends the standard ArrayList
6 * to provide additional functionality for retrieving the last element
7 */
8public class ExtendedArrayList<T> extends ArrayList<T> {
9
10 /**
11 * Returns the last element of the list
12 * @return The last element of type T, or Integer -1 if the list is empty
13 * Note: Returns Object type to accommodate both T and -1
14 */
15 public Object last() {
16 // Check if the list has elements
17 if (this.size() > 0) {
18 // If yes, return the last element using size() - 1 as index
19 return this.get(this.size() - 1);
20 } else {
21 // If no, return -1
22 return -1;
23 }
24 }
25
26 /**
27 * Example usage demonstration
28 */
29 public static void main(String[] args) {
30 // Create an instance of ExtendedArrayList with Integer type
31 ExtendedArrayList<Integer> arr = new ExtendedArrayList<>();
32
33 // Add elements to the list
34 arr.add(1);
35 arr.add(2);
36 arr.add(3);
37
38 // Call last() method - Returns 3
39 System.out.println(arr.last()); // Output: 3
40
41 // Example with empty list
42 ExtendedArrayList<String> emptyArr = new ExtendedArrayList<>();
43 System.out.println(emptyArr.last()); // Output: -1
44 }
45}
46
1#include <vector>
2#include <iostream>
3
4// Template class that extends std::vector to add a last() method
5template<typename T>
6class ExtendedVector : public std::vector<T> {
7public:
8 // Use base class constructors
9 using std::vector<T>::vector;
10
11 /**
12 * Returns the last element of the vector
13 * @return A struct containing either the last element or -1 if empty
14 */
15 struct LastResult {
16 bool has_value;
17 T value;
18 int error_code;
19
20 // Constructor for valid value
21 LastResult(T val) : has_value(true), value(val), error_code(0) {}
22
23 // Constructor for empty case
24 LastResult() : has_value(false), value(T()), error_code(-1) {}
25
26 // Conversion operator to allow returning T or -1
27 operator T() const {
28 return has_value ? value : T(-1);
29 }
30 };
31
32 /**
33 * Returns the last element of the vector
34 * Implementation of the last() method
35 */
36 LastResult last() const {
37 // Check if vector has elements
38 if (this->size() > 0) {
39 // If yes, return the last element using back()
40 return LastResult(this->back());
41 } else {
42 // If no, return -1 (through LastResult)
43 return LastResult();
44 }
45 }
46};
47
48/**
49 * Alternative implementation using a free function template
50 * This approach doesn't require inheritance
51 */
52template<typename T>
53T last(const std::vector<T>& vec) {
54 // Check if vector has elements
55 // If yes, return the last element
56 // If no, return -1 cast to type T
57 return vec.empty() ? static_cast<T>(-1) : vec.back();
58}
59
60/**
61 * Example usage:
62 * ExtendedVector<int> arr = {1, 2, 3};
63 * auto result = arr.last(); // Returns 3
64 *
65 * std::vector<int> vec = {1, 2, 3};
66 * int last_element = last(vec); // Returns 3
67 */
68
1// Extend the global Array interface to include the last() method
2declare global {
3 interface Array<T> {
4 /**
5 * Returns the last element of the array
6 * @returns The last element of type T, or -1 if the array is empty
7 */
8 last(): T | -1;
9 }
10}
11
12// Implementation of the last() method for Array prototype
13Array.prototype.last = function<T>(this: T[]): T | -1 {
14 // Check if array has elements
15 // If yes, return the last element using at(-1)
16 // If no, return -1
17 return this.length ? this.at(-1)! : -1;
18};
19
20/**
21 * Example usage:
22 * const arr = [1, 2, 3];
23 * arr.last(); // Returns 3
24 */
25
26// Export empty object to make this file a module
27export {};
28
Time and Space Complexity
Time Complexity: O(1)
The implementation uses the at()
method with index -1
to access the last element of the array. The at()
method provides direct indexed access to array elements, which is a constant-time operation. The length check this.length
is also O(1)
since JavaScript arrays maintain their length property. Therefore, the overall time complexity is constant.
Space Complexity: O(1)
The function does not create any additional data structures or use recursive calls. It only returns either the last element of the array (which already exists in memory) or the constant value -1
. No extra space proportional to the input size is allocated, making the space complexity constant.
Common Pitfalls
1. Type Safety Issues with Mixed Return Types
The method returns either an element of type T
or the number -1
. This creates a union type T | -1
which can lead to type checking complications, especially when the array contains numbers.
Problem Example:
const nums = [1, 2, 3]; const lastNum = nums.last(); // Type: number | -1 // You need to narrow the type before using it in calculations const doubled = lastNum * 2; // TypeScript error: can't multiply -1 literal type
Solution: Use type guards or handle the special case explicitly:
const lastNum = nums.last();
if (lastNum !== -1) {
const doubled = (lastNum as number) * 2;
}
// Or use a different sentinel value like null/undefined
2. Collision with Existing Properties
If an array already has a last
property (perhaps from another library or custom code), this will overwrite it, potentially breaking existing functionality.
Problem Example:
// Some library already defined Array.prototype.last differently
Array.prototype.last = function() { return this[this.length - 1]; }
// Your code overwrites it with different behavior (returning -1 for empty)
Solution: Check for existing implementations before overriding:
if (!Array.prototype.hasOwnProperty('last')) {
Array.prototype.last = function() {
return this.length ? this.at(-1) : -1;
};
}
3. Inconsistent Behavior with undefined Elements
Arrays can have undefined elements, and the distinction between "empty array" and "array with undefined last element" becomes ambiguous.
Problem Example:
const arr = [1, 2, undefined];
console.log(arr.last()); // Returns undefined (not -1)
const emptyArr = [];
console.log(emptyArr.last()); // Returns -1
// Both cases might need similar handling but return different values
Solution:
Consider using null
or undefined
as the sentinel value instead of -1
, or document this edge case clearly:
Array.prototype.last = function() {
return this.length ? this.at(-1) : undefined;
};
4. Python's Negative Indexing Gotcha
In the Python implementation, accessing self[-1]
on an empty list will raise an IndexError
, not return -1
.
Problem Example:
empty_list = [] # This will raise IndexError: list index out of range result = empty_list[-1] if empty_list else -1 # Wrong order of evaluation
Solution: Always check the length first before accessing the index:
def last(self):
# Correct: Check length before accessing
return self[-1] if len(self) > 0 else -1
# Or more Pythonic:
return self[-1] if self else -1
5. Browser Compatibility with at()
Method
The at()
method is relatively new (ES2022) and might not be available in older browsers or JavaScript environments.
Problem Example:
// In older browsers, this.at(-1) will throw: // TypeError: this.at is not a function
Solution: Use traditional array indexing for better compatibility:
Array.prototype.last = function() {
return this.length ? this[this.length - 1] : -1;
};
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!