Solving 1,782 Leetcode questions in one day

Posted February 21, 2024 • 4 min read
Bionic reading mode (b)
banner image

TLDR🔗

  • Example account we leveled here. It's not the original one, but alas, it gives an idea of what we did.
  • Try Leetcode yourself here.
  • Unfortunately, we opted to keep the GitHub repository private. Message me for inquires.

The motivation🔗

Like most software kids, I was told DSA (data structures and algorithms) were pivotally important to becoming a good developer, and consequently, getting a well-paying job. Hence, I created a Leetcode account, and began to solve questions with the goal of solving many questions.

This was all fine and dandy, except, it took a long time — time I didn't have balancing five other university courses! So, I decided on writing a bot to auto-solve the questions for me. I worked on it with one friend.

Reversal

Would making this bot help me with learning the material Leetcode questions were supposed to teach? Absolutely not, and thus, I definitely do them manually as well for practice, but, nonetheless, finding a way to solve them automatically was a fun endeavour.

How the bot works🔗

1. Grabbing a user's session

This acts similarly to regular users "logging in" to their Leetcode accounts. Upon starting the CLI tool, we prompt users to provide us their credentials (secure, I know):

Username: some_username
Password: ********

Then, we downloaded the official Chromium Chrome Driver so that we could use the aforementioned credentials to boot up a headless Chrome instance and login to a user's account by automating the entry of their username and password. We found that Leetcode surprisingly let these feeble authentication attempts work!

Next, we pulled the authenticated user's cookies that we needed to make API requests on their behalf:

# assumes the headless Chrome instance has
# been authenticated and is sitting on 
# Leetcode's home screen
cookies = login_driver.get_cookies()
for  cookie  in  cookies:
	if (cookie['name'] ==  'csrftoken'):
		csfr_token  =  cookie['value']
	if (cookie['name'] ==  'LEETCODE_SESSION'):
		session_id  =  cookie['value']
		
# prints the cookies
print(csfr_token, session_id) 

2. Getting the user's solving preferences

After we authenticate a user and store their cookies, we allow them to select from the CLI their preference of:

  • Solving language (ex: Java, Python, etc.).
  • Solving difficulty (ex: easy, medium, hard, random).

We do this via simple input collection via the standard Python input function.

3. Answering the questions

Finally, with our prerequisite data acquired, we now could execute the steps below to solve n number of questions. These all utilize the user's solving preferences and credential tokens to authorize the API requests.

First, we poll the questions a user has yet to solve via:

questions  =  get_questions("https://leetcode.com/api/problems/"  + question_type  +  "/?status=notstarted")

Then, we parse this to retrieve the identifiers of unsolved questions.

Using the parsed question identifiers, the bot proceeds to iterate through each unsolved question, filtering out those that are either already solved ("status": "ac"), require payment ("paid_only": True), or do not align with the user's preferences regarding difficulty and attempt status. For each remaining eligible question, the bot constructs a URL to fetch potential solutions using the user's preferred programming language:

def  format_question_url(question_name, language_type) -> str:
	return  "https://leetcode.com/problems/"  +  question_name  +  "/solutions/?orderBy=most_votes&languageTags="  +  language_type

# `question_stats` is the returned data
# we got from checking all a user's questions
# and then filtering on x, y, z above
question_name = question_stats["stat"]["question__title_slug"]
solution_url = format_question_url(question_name, language_type)

The bot then scrapes the Leetcode solutions page (AKA: discussions) for the most voted solutions using a headless browser session. Once a promising solution is found, the bot prepares it for submission.

It was crucial for us to traverse these pages carefully, as the bot needed to interpret the page's dynamic content correctly to extract viable code solutions. Specifically, we did this by checking for Markdown-embedded code blocks.

Before submitting the solution, the bot rigorously tests it against the question's example test cases. This step is vital to ensure that the solution not only compiles but also passes all preliminary tests, significantly increasing the likelihood of a successful submission (~95% success with cases vs <50% without). This used Leetcode's GraphQL endpoint to fetch test cases, run them on our new-found solution, and then listen for when they finish executing and we get a response.

This utilizes endpoints like:

POST https://leetcode.com/graphql/
POST https://leetcode.com/problems/<QUESTION_NAME>/interpret_solution/
GET https://leetcode.com/submissions/detail/<TESTING_ANSWER_RESP_ID>/check/

If the solution passes all test cases, the bot proceeds to submit the solution on behalf of the user. This submission process mimics a user's action by sending a POST request to the appropriate Leetcode endpoint. Of course, this is completed with the necessary headers, cookies, and CSRF tokens to authenticate the request (as we do with all other API requests above):

if test_result["run_success"] and test_result["total_correct"] == test_result["total_testcases"]:
	# internally:
	# POST https://leetcode.com/problems/<QUESTION_NAME>/submit/
    submit_solution(possible_solution, language_type, question_id)

The bot handles submissions carefully, abiding by rate limits (which we found very lenient) and handling errors gracefully to avoid any negative impact on the user's account.

After a successful submission, the bot logs the outcome and moves on to the next question, continuing this process until all eligible questions have been attempted.

... and that's how you solve 1,782 Leetcode questions in one day! Presto ✨