incrementOne() - Atomic increment/decrement field value

Method overview

incrementOne is a convenience method for atomically incrementing or decrementing a field value in a single document, simplifying the use of updateOne({ $inc }).

Why is incrementOne needed?

Traditional way (using updateOne):

//❌ Need to build $inc update object
await collection('users').updateOne(
  { userId: 'user123' },
  { $inc: { loginCount: 1 } }
);

Use incrementOne:

//✅ More concise and intuitive
await collection('users').incrementOne(
  { userId: 'user123' },
  'loginCount'
);

Core Advantages

AdvantagesDescription
Atomic operationsConcurrency safety, no race conditions
Code simplicityReduce boilerplate code by 60%
Intuitive and easy to readClear semantics
return resultoptionally return the document before/after update

Method signature

async incrementOne(
  filter: Object,
  field: string | Object,
  increment?: number,
  options?: {
    returnDocument?: 'before' | 'after',
    projection?: Object,
    maxTimeMS?: number,
    comment?: string
  }
): Promise<IncrementOneResult>

interface IncrementOneResult {
  acknowledged: boolean;
  matchedCount: number;
  modifiedCount: number;
  value: Document | null;  //Document after (or before) update
}

Parameter description

ParametersTypeRequiredDescription
filterObjectQuery conditions
fieldstring | ObjectField name (single field) or field-increment object (multiple fields)
incrementnumberIncrement (default 1, negative number means decrease)
optionsObjectOperation Options
options.returnDocumentstringReturn timing ('before' | 'after', default 'after')
options.projectionObjectField Projection
options.maxTimeMSnumberOperation timeout (milliseconds)
options.commentstringQuery comments

Basic example

Example 1: Increment (default +1)

await collection('users').incrementOne(
  { userId: 'user123' },
  'loginCount'
);
//loginCount increments by 1

Example 2: Specify increment

await collection('users').incrementOne(
  { userId: 'user123' },
  'points',
  50
);
//points increased by 50

Example 3: Decrement (negative number)

await collection('users').incrementOne(
  { userId: 'user123' },
  'credits',
  -30
);
//credits reduced by 30

Example 4: Simultaneous operation of multiple fields

await collection('users').incrementOne(
  { userId: 'user123' },
  {
    loginCount: 1,    // +1
    points: 20,       // +20
    credits: -10      // -10
  }
);

Example 5: Return updated document

const result = await collection('users').incrementOne(
  { userId: 'user123' },
  'points',
  50
);

console.log(result.value.points);  //updated value

Real scene example

Scenario 1: Statistics of login times

async function recordLogin(userId) {
  const result = await collection('users').incrementOne(
    { userId },
    'loginCount'
  );

  console.log(`Number of user logins: ${result.value.loginCount}`);
  return result;
}

Scenario 2: Points System

//Complete tasks and earn points
async function earnPoints(userId, points) {
  const result = await collection('users').incrementOne(
    { userId },
    'points',
    points
  );

  console.log(`Current points: ${result.value.points}`);
  return result;
}

//Redeem goods and deduct points
async function spendPoints(userId, points) {
  const result = await collection('users').incrementOne(
    { userId },
    'points',
    -points
  );

  if (result.value.points < 0) {
    throw new Error('Not enough points');
  }

  return result;
}

Scenario 3: Article views

async function incrementViews(articleId) {
  await collection('articles').incrementOne(
    { articleId },
    'views'
  );
}

Scenario 4: Inventory Management

//Restock
async function addStock(productId, quantity) {
  const result = await collection('products').incrementOne(
    { productId },
    'stock',
    quantity
  );

  return result.value.stock;
}

//Ship
async function reduceStock(productId, quantity) {
  const result = await collection('products').incrementOne(
    { productId },
    'stock',
    -quantity,
    { returnDocument: 'before' }
  );

  //Check if there is enough stock
  if (result.value.stock < quantity) {
    throw new Error('Insufficient stock');
  }

  return result;
}

Scenario 5: Multi-dimensional statistics

async function recordArticleInteraction(articleId, action) {
  const increments = {};

  if (action === 'view') increments.views = 1;
  if (action === 'like') increments.likes = 1;
  if (action === 'share') increments.shares = 1;

  await collection('articles').incrementOne(
    { articleId },
    increments
  );
}

Detailed explanation of option parameters

returnDocument - return timing

//Return updated document (default)
const result = await collection('users').incrementOne(
  { userId: 'user123' },
  'count',
  5,
  { returnDocument: 'after' }
);
console.log(result.value.count);  // 15

//Return to the document before update
const result2 = await collection('users').incrementOne(
  { userId: 'user123' },
  'count',
  5,
  { returnDocument: 'before' }
);
console.log(result2.value.count);  //10 (value before update)

projection - field projection

const result = await collection('users').incrementOne(
  { userId: 'user123' },
  'points',
  50,
  { projection: { points: 1, name: 1 } }
);
//Only return _id, points, name fields

Performance Notes

Atomic guarantee

incrementOne uses MongoDB’s $inc operator to ensure atomicity:

  • ✅ Concurrency safety
  • ✅ No race conditions
  • ✅ No transactions required

Performance comparison

MethodOperation stepsConcurrency safetyPerformance
incrementOne1 step (atomic)⭐⭐⭐⭐⭐
find + update2 steps (non-atomic)⭐⭐⭐

Error handling

Error type

Error typeError codeTrigger condition
Parameter errorINVALID_ARGUMENTfilter/field/increment is invalid
Timeout ErrorQUERY_TIMEOUTExceeded maxTimeMS

Error handling example

try {
  const result = await collection('users').incrementOne(
    { userId: 'user123' },
    'points',
    50
  );

  if (result.matchedCount === 0) {
    console.log('User does not exist');
  }
} catch (error) {
  if (error.code === 'INVALID_ARGUMENT') {
    console.error('Parameter error:', error.message);
  } else {
    console.error('Unknown error:', error);
  }
}

Best Practices

  1. Use incrementOne instead of find + update

    //✅ Recommended: Atomic operations
    await collection('users').incrementOne(
      { userId: 'user123' },
      'count',
      1
    );
    
    //❌ Avoid: Non-atomic operations (race conditions)
    const user = await collection('users').findOne({ userId: 'user123' });
    await collection('users').updateOne(
      { userId: 'user123' },
      { $set: { count: user.count + 1 } }
    );
  2. Check return value

    const result = await collection('users').incrementOne(
      { userId: 'user123' },
      'points',
      50
    );
    
    if (result.matchedCount === 0) {
      throw new Error('User does not exist');
    }

❌ Things to avoid

  1. Avoid using in loops
    //❌ Avoid: N operations
    for (const userId of userIds) {
      await collection('users').incrementOne({ userId }, 'count');
    }
    
    //✅ Recommendation: use updateMany
    await collection('users').updateMany(
      { userId: { $in: userIds } },
      { $inc: { count: 1 } }
    );

Compare with other methods

vs updateOne({ $inc })

dimensionsincrementOneupdateOne({ $inc })
Lines of code1-2 lines2-3 lines
Readability⭐⭐⭐⭐⭐⭐⭐⭐
FunctionEquivalentEquivalent

FAQ

Q1: What is the difference between incrementOne and updateOne?

A: incrementOne is a convenience method of updateOne({ $inc }), with clearer semantics and simpler code.

Q2: Does it support concurrency?

A: ✅ Yes! incrementOne is an atomic operation and is concurrency safe.

Q3: Can it be reduced?

A: ✅ Yes! Just use negative numbers:

await collection('users').incrementOne({ userId: 'user123' }, 'credits', -10);

Q4: What happens when the field does not exist?

A: MongoDB will automatically create fields, starting from 0 and increasing.

Q5: How to operate multiple fields at the same time?

A: Use object form:

await collection('users').incrementOne(
  { userId: 'user123' },
  { count: 1, points: 10 }
);

See also