Using cursors to navigate collections

Updated on Wed, 2013-03-27 13:52

Cursoring

The Twitter REST API utilizes a technique called ‘cursoring’ to paginate large result sets. Cursoring separates results into pages (the size of which are defined by the count request parameter) and provides a means to move backwards and forwards through these pages.

To retrieve cursored results, you initially pass a cursor with a value of -1 to the endpoint. By default, an API endpoint that supports cursoring will assume -1 was passed as cursor if you do not provide one. The response to a cursored request will contain previous_cursor, next_cursor, previous_cursor_str and next_cursor_str. As is the case with most ID values in Twitter’s APIs, the _str values are provided for languages that cannot support large integers (e.g. JavaScript).

The next_cursor is the cursor that you should send to the endpoint to receive the next batch of responses, and the previous_cursor is the cursor that you should send to receive the previous batch. You will know that you have requested the last available page of results when the API responds with a next_cursor = 0.

Pseudocode

The pseudocode to iterate over all responses from a cursored endpoint is:

  1. cursor = -1
  2. api_path = "https://api.twitter.com/1.1/endpoint.json?screen_name=targetUser"
  3.  
  4. do {
  5.     url_with_cursor = api_path + "&cursor=" + cursor      
  6.     response_dictionary = perform_http_get_request_for_url( url_with_cursor )
  7.     cursor = response_dictionary[ 'next_cursor' ]
  8. }
  9. while ( cursor != 0 )

Example

Let’s see a real-life example of cursors in action. Consider the common scenario where we want to obtain the list of IDs who follow a user who has a large amount of followers. Instead of returning all IDs in one response set, the endpoint will instead return pages of results.

Request
GET https://api.twitter.com/1.1/followers/ids.json?screen_name=theSeanCook
Response (truncated)
  1. {
  2.     "ids": [
  3.         385752029, 
  4.         602890434, 
  5.         ...
  6.         333181469, 
  7.         333165023
  8.     ], 
  9.     "next_cursor": 1374004777531007833, 
  10.     "next_cursor_str": "1374004777531007833", 
  11.     "previous_cursor": 0, 
  12.     "previous_cursor_str": "0"
  13. }

We now have a means to move forwards through our dataset, via the next_cursor. To get the next batch of at least 5,000 results, perform the same request, but set the cursor to the value of the next_cursor.

Request
GET https://api.twitter.com/1.1/followers/ids.json?screen_name=theSeanCook&cursor=1374004777531007833
Response (truncated)
  1. {
  2.     "ids": [
  3.         333156387, 
  4.         333155835, 
  5.         ...
  6.         101141469, 
  7.         92896225
  8.     ], 
  9.     "next_cursor": 1323935095007282836, 
  10.     "next_cursor_str": "1323935095007282836", 
  11.     "previous_cursor": -1374003371900410561, 
  12.     "previous_cursor_str": "-1374003371900410561"
  13. }

Notice that we now have a next_cursor as well as a previous_cursor. This means that we can now move back and forth through our results. Let's keep advancing through the data with the next cursor.

Request
GET https://api.twitter.com/1.1/followers/ids.json?screen_name=theSeanCook&cursor=1323935095007282836
Response (truncated)
  1. {
  2.     "ids": [
  3.         73635015, 
  4.         61175149, 
  5.         ...
  6.         18300955, 
  7.         32684766
  8.     ], 
  9.     "next_cursor": 0, 
  10.     "next_cursor_str": "0", 
  11.     "previous_cursor": -1323886329961827926, 
  12.     "previous_cursor_str": "-1323886329961827926"
  13. }

Huzzah. The next_cursor is now 0, which indicates that there are no more remaing pages. We’ve now completed iterating over the accounts’ followers.