Skip to content
Studio 3T - The professional GUI, IDE and client for MongoDB
  • Tools
    • Aggregation Editor
    • IntelliShell
    • Visual Query Builder
    • Export Wizard
    • Import Wizard
    • Query Code
    • SQL Query
    • Connect
    • Schema Explorer
    • Compare
    • SQL ⇔ MongoDB Migration
    • Data Masking
    • Task Scheduler
    • Reschema
    • More Tools and Features
  • Solutions
  • Resources
    • Knowledge Base
    • MongoDB Tutorials & Courses
    • Tool/Feature Documentation
    • Blog
    • Community
    • Testimonials
    • Whitepapers
    • Reports
  • Contact us
    • Contact
    • Sales Support
    • Feedback and Support
    • Careers
    • About Us
  • Store
    • Buy Now
    • Preferred Resellers
    • Team Pricing
  • Download
  • My 3T
search

Studio 3T® Knowledge Base

  • Documentation
  • Tutorials
  • Workshops
Take the fastest route to learning MongoDB. Cover the basics in two hours with MongoDB 101, no registration required.
Start the free course

Peculiarities with MongoDB Arrays

Posted on: 13/07/2018 (last updated: 04/08/2021) by Thomas Zahn

Introduction

Coming from a traditional RDBMS to a NoSQL database like MongoDB can be a truly liberating experience.

For example, not being forced into a fixed schema and being able to dynamically add fields along the way as and where you need them is just fantastic.

For me, personally, another great aspect has always been the very expressive and flexible JSON-based query language in MongoDB. Compare that to the rigidness of the SQL syntax!

That said, while working on our MongoDB GUI, I have come across a few very tricky update and query corner cases that stem from the peculiar way arrays are handled by the MongoDB search engine.

In this post, I will talk about the repercussions that these seemingly isolated peculiarities can create for certain common update commands.

If you work with MongoDB data with lots of arrays and embedded fields, make sure to check out Studio 3T’s Table View. It lets you step into array-valued columns, show nested fields next to parent columns, and more, so you can explore your collections much faster and easier.

The $type query operator and arrays

Let’s begin by revisiting arrays and queries.

Consider the following collection “address” that lists persons and their (multiple) addresses:

{ "_id" : 1, "first_name" : "Peter", "address" : "100 Main St, Boston, MA" }, { "_id" : 2, "first_name" : "Paula", "address" : { "street" : "1234 Broad St", "city" : "New York, NY" } }, { "_id" : 3, "first_name" : "Natalie", "address" : { "street" : "200 High St", "city" : "Miami, FL" } }, { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] }, { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] }, { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] }

In MongoDB, the $type query operator lets you query for fields that have a certain type.

Say, for example, you wanted to find all documents in your “address” collection where the field “first_name” is of type String. You would trivially issue a query like this:

> db.address.find({"first_name": {$type: 2}}) { "_id" : 1, "first_name" : "Peter", "address" : "100 Main St, Boston, MA" } { "_id" : 2, "first_name" : "Paula", "address" : { "street" : "1234 Broad St", "city" : "New York, NY" } } { "_id" : 3, "first_name" : "Natalie", "address" : { "street" : "200 High St", "city" : "Miami, FL" } } { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

Since all documents have a String in their respective “first_name” fields, you are returned all documents.

Ok, suppose now you wanted to find all documents where the field “address” holds an array, i.e. where the person has several addresses. A quick glance at the documentation of $type shows that type “4” indicates an array.

So, then:

> db.address.find({"address": {$type: 4}}) { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

What just happened? Why are Tim and Sara not showing up?

If you keep reading the documentation on the $type operator, you will learn that in case of an array field, “the $type operator performs the type check against the array elements and not the field”.

In other words,

db.address.find({"address": {$type: 4}})

won’t return the documents where the field “address” holds an array, but only those documents where “address” holds (at least) two-dimensional arrays (i.e. arrays-in-arrays or second-level arrays).

This has always struck me as a really peculiar design choice. For exactly these semantics where you want something applied to the elements of an array, there is afterall the $elemMatch operator.

So, if I had really wanted to find all two-dimensional address arrays, I would have put the query like so:

> db.address.find({"address": {$elemMatch: {$type: 4}}}) { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

And as you can see, the same result set is returned.

$type array inconsistencies

Note, however, that the behavior of the $type query operator is unfortunately inconsistent between top-level arrays and concretely-named deeper-level arrays.

Previously, we have learnt that for array fields, $type is actually executed against the elements rather than the array field itself.

Ok, we groked that, so what should the following query then return?

db.address.find({"address.0": {$type: 4}})

Well, in each document, it should look at the first element in the address array (if the document has such an array, of course) and check whether it is again of type Array.

In that case, though, by the definition in the documentation and as observed above, the $type operator should be applied to the array elements of address.0 instead of directly to the array field address.0.

So, we would expect to find documents where the first element of the address array again has elements that are themselves arrays. In other words, we expect to find three-dimensional arrays.

Let’s run it then:

> db.address.find({"address.0": {$type: 4}}) { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

That is bizarre. Instead of an empty result set (as we don’t have any three-dimensional address arrays in our collection), we see Jake’s two-dimensional address array. It appears as though this time, $type was directly applied to the array field “address.0” instead of to its elements.

Maybe this is only an artefact of {$type: 4} ? Let’s investigate this further. Let’s begin by finding all documents that have an address object:

> db.address.find({"address": {$type: 3}}) { "_id" : 2, "first_name" : "Paula", "address" : { "street" : "1234 Broad St", "city" : "New York, NY" } } { "_id" : 3, "first_name" : "Natalie", "address" : { "street" : "200 High St", "city" : "Miami, FL" } } { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } >

As expected, that finds Paula’s and Natalie’s address objects, as well as object elements in Tim’s and Sara’s address arrays.

So, let’s try the same thing on a concretely-named array element:

> db.address.find({"address.0": {$type: 3}}) { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } >

Tim’s and Sara’s address.0 fields are indeed objects, so they are correctly returned. However, Jake’s address.0 field is an array that has elements of type object, and so should be – if the behavior of $type were consistent – also returned.

These observable inconsistencies strongly suggest that $type is only executed against array elements for top-level array fields. For nested array fields, $type seems to be no longer executed against the array elements, but instead directly against the (nested) array field.

By the same token, one could now argue that the observed correct execution of db.address.find({“address”: {$elemMatch: {$type: 4}}}) in the previous section is actually also inconsistent. One could expect that when $elemMatch goes and checks the individual element fields of “address”, the $type would be executed on each first-level array element.

If such a first-level array element is itself an array again, by the description of $type, {$type: 4} should not be applied to that first-level array element directly but instead to its elements. So, it should really return those elements that have elements of type array. In other words, {$elemMatch: {$type: 4}} should also only return three-dimensional arrays.

Detecting MongoDB array fields

How do you detect array fields then?

The official recommendation in the documentation of $type is to revert to JavaScript and run

> db.address.find( { $where : "Array.isArray(this.address)" } ) { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

That works and returns all matching documents. However, this method comes with a huge performance penalty, as the JavaScript code now needs to be executed against each document in the collection (and also can’t take advantage of any indices).

To gauge this performance penalty, I set up a quick testbed on my Early 2013 MacBook Pro running at 2.7 GHz and locally running a 2.6.1 MongoDB server. I created a simple “phones” collection with 1,000,000 documents of the following form:

{ "_id" : 38000425113, "components" : { "country" : 3, "area" : 800, "prefix" : 42, "number" : 425113 }, "display" : "+3 800-425113" }>

and manually added two stripped-down documents with two-dimensional arrays:

{ "_id" : 38007654321, "components" : { "number" : [ [ 8, 0, 0 ], [ 7, 6, 5, 4, 3, 2, 1 ] ] } }, { "_id" : 38001234567, "components" : { "number" : [ [ 8, 0, 0 ], [ 1, 2, 3, 4, 5, 6, 7 ] ] } }

So, let’s compare the JavaScript-based query to the $type query:

> db.phones.find({"components.number": {$type: 4}}) { "_id" : 38007654321, "components" : { "number" : [ [ 8, 0, 0 ], [ 7, 6, 5, 4, 3, 2, 1 ] ] } } { "_id" : 38001234567, "components" : { "number" : [ [ 8, 0, 0 ], [ 1, 2, 3, 4, 5, 6, 7 ] ] } } > db.phones.find({"components.number": {$type: 4}}).explain() { "cursor" : "BasicCursor", "isMultiKey" : false, "n" : 2, "nscannedObjects" : 1000001, "nscanned" : 1000001, "nscannedObjectsAllPlans" : 1000001, "nscannedAllPlans" : 1000001, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 7812, "nChunkSkips" : 0, "millis" : 447 "server" : "Thomass-MacBook-Pro.local:27061", "filterSet" : false } > db.phones.find( { $where : "Array.isArray(this.components.number)" } ) { "_id" : 38007654321, "components" : { "number" : [ [ 8, 0, 0 ], [ 7, 6, 5, 4, 3, 2, 1 ] ] } } { "_id" : 38001234567, "components" : { "number" : [ [ 8, 0, 0 ], [ 1, 2, 3, 4, 5, 6, 7 ] ] } } > db.phones.find( { $where : "Array.isArray(this.components.number)" } ).explain() { "cursor" : "BasicCursor", "isMultiKey" : false, "n" : 2, "nscannedObjects" : 1000001, "nscanned" : 1000001, "nscannedObjectsAllPlans" : 1000001, "nscannedAllPlans" : 1000001, "scanAndOrder" : false, "indexOnly" : false, "nYields" : 7831, "nChunkSkips" : 0, "millis" : 21623 "server" : "Thomass-MacBook-Pro.local:27061", "filterSet" : false }

Roughly half a second for the $type operator-based query vs over 21 seconds for the JavaScript-based work-around.

While just one type of query is of course by no means a statistically sound proof, it does suffice to demonstrate that the performance of these query types will differ by orders of magnitude. That may be OK for a one-off query but certainly is not feasible for production-level code in your application.

So, {$type: 4} doesn’t work, the JavaScript workaround is way too slow. What to do?

On SERVER-1475, a few very clever workarounds have been suggested. You could for example test for the existence of a an embedded field “0”, like so:

> db.address.find({"address.0": {$exists: 1}}) { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

That’s nice and simple but might for the (admittedly extreme) corner case, where you have a document where the field in question actually occurs as an embedded document which happens to have a field “0”, return false positives. It would also fail to detect empty arrays.

Another very clever workaround that was suggested there uses $elemMatch to check for the existence of any element:

> db.address.find({"address": {$elemMatch: {$exists: 1}}}) { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

(Note: If you also want to catch empty arrays, $or it with a {$size: 0}:

db.address.find({$or: [{"address": {$size: 0}}, {"address": {$elemMatch: {$exists: 1}}}]})

Caveat: This is a really slick workaround to detect array fields. However, note that prior to 2.6, this workaround only works for top-level arrays but not for concretely-named deeper-level arrays.

Let me demonstrate what I mean by that. Suppose you wanted to find all documents where the first element of “address”, i.e. “address.0”, is itself an array again.

2.4.10: > db.address.find({"address.0": {$elemMatch: {$exists: 1}}}) > 2.6.1: > db.address.find({"address.0": {$elemMatch: {$exists: 1}}}) { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

See SERVER-1264 for more details.

In 2.4.x, for deeper-level arrays, you could of course revert to using the $type operator again and exploit its inconsistent behavior as described above, but that is neither here nor there ?

Why does that matter? Who would need to find fields of type array anyway?

Common operations impacted by arrays

So why is this important? Why would I ever need to detect arrays? Because there are a number of surprisingly common operations that are directly impacted by the presence of arrays.

Adding a field to documents where the field is missing

Suppose we have just realized that we need a “country” field in the embedded “address” documents (i.e. for Paula and Natalie). No worries, there is of course no fixed schema here, so we can easily add the field:

> db.address.update({},{$set: {"address.country": "US"}}, {multi: true}) WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0, "writeError" : { "code" : 16837, "errmsg" : "cannot use the part (address of address.country) to traverse the element ({address: "100 Main St, Boston, MA"})" } })

Right, so that failed because Peter’s “address” field is a String and so, obviously, the DB doesn’t know how to add a field to that. Fair enough. So, let’s refine our update query to consider only those documents where “address” is indeed of type Object:

> db.address.update({"address": {$type: 3}},{$set: {"address.country": "US"}}, {multi: true}) WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0, "writeError" : { "code" : 16837, "errmsg" : "cannot use the part (address of address.country) to traverse the element ({address: [ { street: "400 Michigan Ave", city: "Chicago, IL" }, { street: "120 Ocean Dr", city: "Miami, FL" } ]})" } })

Ok, so why did this fail now? Well, remember that for an array field, $type is actually applied to the array elements. So, {“address”: {$type: 3}} will also return Tim’s and Sara’s documents because they both have address array elements that are of type Object. Trying then to add a field “country” to their respective “address” array fields obviously does not make sense. Hence, the complaining from the DB.

Ok, so let’s refine our update query further. Now, we’ll only try to add the field to documents where “address” is an embedded object and at the same time not an array:

> db.address.update({$and: [ {"address": {$type: 3}}, {"address": {$not: {$elemMatch: {$exists: 1}}}} ]}, {$set: {"address.country": "US"}}, {multi: true}) WriteResult({ "nMatched" : 2, "nUpserted" : 0, "nModified" : 0 }) >

Quite some hoops we had to jump through in order to be able to do something as innocent-sounding as adding a missing field to some documents ?

Finding outlier documents with a certain field type

As a developer, a very common QA task is to make sure that your application always writes consistent data. So, suppose your application is to always store any address of a person in an “address” array (people with only one address would then only have a one-element address array). We could now easily find outliers:

> db.address.find({"address": {$not: {$elemMatch: {$exists: 1}}}}) { "_id" : 1, "first_name" : "Peter", "address" : "100 Main St, Boston, MA" } { "_id" : 2, "first_name" : "Paula", "address" : { "street" : "1234 Broad St", "city" : "New York, NY" } } { "_id" : 3, "first_name" : "Natalie", "address" : { "street" : "200 High St", "city" : "Miami, FL" } } >

Finding specific array elements

A probably less common case where the detection of an array is important is finding specifically named (i.e. index-based) array elements across documents. This is something that we encountered while working on our Schema Explorer tool. Suppose you wanted to find all second elements in the address array fields across the documents in your collections.

If you naively ran:

db.address.find({"address.1": {$exists: 1}})

You would get those documents, but you could in theory also get some false positives in the case where there happened to be address objects that had a field named “1” in them. Granted, that is probably an extreme edge case, but a real concern when designing a generic tool that operates on unknown collections. So, in order to guard against those false positives, you would need to run a query like this:

> db.address.find({$and: [{"address.1": {$exists: 1}}, {"address": {$elemMatch: {$exists: 1}}}]}) { "_id" : 4, "first_name" : "Tim", "address" : [ { "street" : "400 Michigan Ave", "city" : "Chicago, IL", "country" : "US" }, { "street" : "Berliner Str. 3", "city" : "München" } ] } { "_id" : 5, "first_name" : "Sara", "address" : [ { "street" : "120 Ocean Dr", "city" : "Miami, FL", "country" : "US" }, { "street" : "Pariser Str. 10", "city" : "Berlin" } ] } { "_id" : 6, "first_name" : "Jake", "address" : [ [ { "street" : "456 Broad St", "city" : "Providence, RI" }, { "street" : "1000 Marina Dr", "city" : "Naples, FL" } ], [ { "street" : "Hauptstr. 12", "city" : "Berlin" } ] ] } >

Conclusion

In this blog post, we have seen how the presence of MongoDB arrays can impact a number of fairly common update operations and how to handle those cases.

Overall, I think it would be really great to have a dedicated $isArray query operator. The peculiar (and at times inconsistent) way arrays are handled by the MongoDB search engine currently make it unnecessarily complex to guard one’s update queries against the tricky intricacies of arrays.

If you enjoyed reading this article, we’re sure you’ll find other helpful MongoDB tips and tricks like how to prevent connection timeouts and best practices for UUID data.

Download Studio 3T here, available for Windows, Mac, and Linux.


How helpful was this article?
This article was hideous
This article was bad
This article was ok
This article was good
This article was great
Thank you for your feedback!

About The Author

Thomas Zahn

Having grown up with a living room that was essentially the office of his mother’s software start-up in the 80s, Thomas is a dyed-in-the-wool software engineer. In the past, he has worked for large outfits such as Microsoft Research and Nokia as well as for specialised engineering shops and start-ups. He lives in Berlin with his wife and two kids, and loves tennis and hiking (though, bizarrely, he constantly seems to find no time to do much of either those two). Thomas holds a Ph.D. in Computer Science from the Freie Universität Berlin.

Article navigation

Related articles

  • How to Query MongoDB Arrays Without Using the mongo Shell
  • Test your skills: Querying Arrays Using MongoDB $elemMatch
  • Studio 3T and MongoDB Arrays: Webinar
  • Querying Arrays Using MongoDB $elemMatch
  • Querying Embedded Documents in MongoDB Arrays

Studio 3T

MongoDB Enterprise Certified Technology PartnerSince 2014, 3T has been helping thousands of MongoDB developers and administrators with their everyday jobs by providing the finest MongoDB tools on the market. We guarantee the best compatibility with current and legacy releases of MongoDB, continue to deliver new features with every new software release, and provide high quality support.

Find us on FacebookFind us on TwitterFind us on YouTubeFind us on LinkedIn

Education

  • Free MongoDB Tutorials
  • Connect to MongoDB
  • Connect to MongoDB Atlas
  • Import Data to MongoDB
  • Export MongoDB Data
  • Build Aggregation Queries
  • Query MongoDB with SQL
  • Migrate from SQL to MongoDB

Resources

  • Feedback and Support
  • Sales Support
  • Knowledge Base
  • FAQ
  • Reports
  • White Papers
  • Testimonials
  • Discounts

Company

  • About Us
  • Blog
  • Careers
  • Legal
  • Press
  • Privacy Policy
  • EULA

© 2023 3T Software Labs Ltd. All rights reserved.

  • Privacy Policy
  • Cookie settings
  • Impressum

We value your privacy

With your consent, we and third-party providers use cookies and similar technologies on our website to analyse your use of our site for market research or advertising purposes ("analytics and marketing") and to provide you with additional functions (“functional”). This may result in the creation of pseudonymous usage profiles and the transfer of personal data to third countries, including the USA, which may have no adequate level of protection for the processing of personal data.

By clicking “Accept all”, you consent to the storage of cookies and the processing of personal data for these purposes, including any transfers to third countries. By clicking on “Decline all”, you do not give your consent and we will only store cookies that are necessary for our website. You can customize the cookies we store on your device or change your selection at any time - thus also revoking your consent with effect for the future - under “Manage Cookies”, or “Cookie Settings” at the bottom of the page. You can find further information in our Privacy Policy.
Accept all
Decline all
Manage cookies
✕

Privacy Preference Center

With your consent, we and third-party providers use cookies and similar technologies on our website to analyse your use of our site for market research or advertising purposes ("analytics and marketing") and to provide you with additional functions (“functional”). This may result in the creation of pseudonymous usage profiles and the transfer of personal data to third countries, including the USA, which may have no adequate level of protection for the processing of personal data. Please choose for which purposes you wish to give us your consent and store your preferences by clicking on “Accept selected”. You can find further information in our Privacy Policy.

Accept all cookies

Manage consent preferences

Essential cookies are strictly necessary to provide an online service such as our website or a service on our website which you have requested. The website or service will not work without them.

Performance cookies allow us to collect information such as number of visits and sources of traffic. This information is used in aggregate form to help us understand how our websites are being used, allowing us to improve both our website’s performance and your experience.

Google Analytics

Google Ads

Bing Ads

Facebook

LinkedIn

Quora

Hotjar

Reddit

Functional cookies collect information about your preferences and choices and make using the website a lot easier and more relevant. Without these cookies, some of the site functionality may not work as intended.

HubSpot

Social media cookies are cookies used to share user behaviour information with a third-party social media platform. They may consequently effect how social media sites present you with information in the future.

Accept selected